linux poll、epoll驱动层应用层示例,实现高精度任务处理

在 Linux 中,poll 和 epoll 的驱动层与应用层协作需要内核模块提供事件通知机制(如数据可读/可写),应用层通过文件描述符监控这些事件。以下是驱动层和应用层的完整实现示例及原理说明:


一、驱动层实现(内核模块)

1. 驱动核心逻辑
  • 目标: 创建一个字符设备 /dev/mypoll,当写入数据时触发可读事件。

  • 关键技术:

    • 实现 file_operations.poll 方法,通过等待队列(wait_queue_head_t)管理阻塞的进程。

    • 使用 wake_up_interruptible 唤醒等待队列,通知应用层事件就绪。

2. 完整代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/device.h>

#define DEVICE_NAME "mypoll"

static int major;
static struct class *cls;
static struct device *dev;

static DECLARE_WAIT_QUEUE_HEAD(wq); // 等待队列
static bool data_ready = false;     // 数据就绪标志
static char buffer[256];            // 模拟数据缓冲区

// ------------------------- 文件操作函数 -------------------------
static ssize_t dev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
    if (copy_to_user(buf, buffer, strlen(buffer))) 
        return -EFAULT;
    data_ready = false; // 读取后重置标志
    return strlen(buffer);
}

static ssize_t dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) {
    if (copy_from_user(buffer, buf, count))
        return -EFAULT;
    data_ready = true; // 写入后设置标志
    wake_up_interruptible(&wq); // 唤醒等待队列
    return count;
}

static unsigned int dev_poll(struct file *filp, poll_table *wait) {
    unsigned int mask = 0;
    poll_wait(filp, &wq, wait); // 将进程加入等待队列
    if (data_ready)
        mask |= POLLIN | POLLRDNORM; // 标记可读事件
    return mask;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = dev_read,
    .write = dev_write,
    .poll = dev_poll,
};

// ------------------------- 模块初始化/卸载 -------------------------
static int __init mypoll_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);
    cls = class_create(THIS_MODULE, DEVICE_NAME);
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    return 0;
}

static void __exit mypoll_exit(void) {
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, DEVICE_NAME);
}

module_init(mypoll_init);
module_exit(mypoll_exit);
MODULE_LICENSE("GPL");

二、应用层示例

1. 使用 poll 监控设备

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>

int main() {
    int fd = open("/dev/mypoll", O_RDWR);
    struct pollfd fds = { .fd = fd, .events = POLLIN };

    while (1) {
        int ret = poll(&fds, 1, 5000); // 5秒超时
        if (ret == -1) {
            perror("poll");
            break;
        } else if (ret == 0) {
            printf("Timeout\n");
            continue;
        }

        if (fds.revents & POLLIN) {
            char buf[256];
            ssize_t len = read(fd, buf, sizeof(buf));
            printf("Data received: %.*s\n", (int)len, buf);
        }
    }

    close(fd);
    return 0;
}
2. 使用 epoll 监控设备(边缘触发模式)

#include <stdio.h>
#include <fcntl.h>
#include <sys/epoll.h>

int main() {
    int fd = open("/dev/mypoll", O_RDWR | O_NONBLOCK);
    int epoll_fd = epoll_create1(0);
    struct epoll_event ev = { .events = EPOLLIN | EPOLLET }, events[1];

    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);

    while (1) {
        int nfds = epoll_wait(epoll_fd, events, 1, 5000);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        } else if (nfds == 0) {
            printf("Timeout\n");
            continue;
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].events & EPOLLIN) {
                char buf[256];
                ssize_t len;
                while ((len = read(fd, buf, sizeof(buf))) > 0) { // 必须读空缓冲区
                    printf("Data received (ET): %.*s\n", (int)len, buf);
                }
                if (len == -1 && errno != EAGAIN) {
                    perror("read");
                }
            }
        }
    }

    close(epoll_fd);
    close(fd);
    return 0;
}

三、交互测试步骤

1. 编译与加载驱动

# 编译内核模块
make
sudo insmod mypoll.ko

# 创建设备节点(若未自动创建)
sudo mknod /dev/mypoll c $(cat /proc/devices | grep mypoll | awk '{print $1}') 0
2. 运行应用程序

# 使用 poll 监控
gcc poll_app.c -o poll_app
./poll_app

# 使用 epoll 监控
gcc epoll_app.c -o epoll_app
./epoll_app
3. 触发事件
# 另开终端写入数据
echo "Hello from driver" | sudo tee /dev/mypoll

四、核心原理说明

层级关键动作协作逻辑
驱动层1. 实现 poll 方法,管理等待队列当应用层调用 poll/epoll 时,内核通过驱动层的 poll 检查设备状态。若数据未就绪,进程被加入等待队列。
2. 数据写入后调用 wake_up_interruptible驱动写入数据时唤醒队列,通知应用层事件就绪。
应用层1. 调用 poll/epoll_wait 阻塞等待事件内核检测到事件就绪后,唤醒应用层进程,返回就绪的文件描述符。
2. 处理就绪事件(如读取数据)应用层通过 read 读取驱动数据,驱动层重置状态标志。

五、关键注意事项

  1. 驱动层同步:

    • 使用 wait_queue_head_t 和自旋锁保护共享数据(如 data_ready)。

    • 避免在中断上下文中直接调用 wake_up,应使用 wake_up_interruptible

  2. 应用层非阻塞模式:

    • 边缘触发(ET)模式下,必须将文件描述符设置为非阻塞(O_NONBLOCK),并循环读取直到返回 EAGAIN

  3. 事件类型选择:

    • 驱动层返回 POLLIN 表示可读,POLLOUT 表示可写。

    • 异常事件使用 POLLERR 或 POLLHUP


通过以上实现,驱动层与应用层可通过 poll/epoll 实现高效的事件驱动交互,适用于传感器数据采集、异步通知等场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值