linux设备驱动程序中的阻塞机制

本文详细介绍了阻塞与非阻塞的概念及其在设备驱动程序中的应用,并深入探讨了等待队列的工作原理。此外,还通过具体的实例代码展示了如何在实际开发中实现这两种模式。

在 Linux 设备驱动中,​阻塞机制用于管理用户进程在等待设备资源(如数据可读/写、硬件事件)时的休眠与唤醒。通过阻塞机制,驱动可以避免进程忙等待,提升系统效率。以下是阻塞机制的实现原理与核心方法:


1. 阻塞与非阻塞模式

  • 阻塞模式(默认)​:当资源不可用时,进程进入休眠状态,直到资源就绪后被唤醒。
  • 非阻塞模式(O_NONBLOCK)​:资源不可用时立即返回 -EAGAIN,进程继续执行其他任务。

2. 核心数据结构:等待队列

​(1) 等待队列头

用于管理等待资源的进程列表:

#include <linux/wait.h>
#include <linux/sched.h>

// 定义并初始化等待队列头
static DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
​(2) 等待队列项

表示一个等待的进程:

typedef struct wait_queue_entry wait_queue_entry_t;

3. 阻塞机制实现步骤

​(1) 进程休眠

当资源不可用时,将当前进程加入等待队列并休眠:

// 在 read 方法中等待数据可读
ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
    struct mydev_private *priv = filp->private_data;

    // 非阻塞模式直接返回 -EAGAIN
    if (filp->f_flags & O_NONBLOCK && !priv->data_ready)
        return -EAGAIN;

    // 阻塞模式下等待数据就绪
    wait_event_interruptible(my_wait_queue, priv->data_ready);

    // 数据就绪后继续执行...
    return copy_to_user(buf, priv->buffer, count);
}
​(2) 进程唤醒

当资源可用时(如中断到达),唤醒等待队列中的进程:

// 中断处理函数或数据生产函数中唤醒进程
void data_available_handler(struct mydev_private *priv) {
    priv->data_ready = 1;
    wake_up_interruptible(&my_wait_queue);  // 唤醒等待队列
}

4. 关键函数与宏

​(1) 进程休眠函数
  • wait_event(queue, condition):不可中断休眠,直到 condition 为真。
  • wait_event_interruptible(queue, condition):可被信号中断的休眠。
  • wait_event_timeout(queue, condition, timeout):休眠最多 timeout 个时钟周期。
​(2) 进程唤醒函数
  • wake_up(queue):唤醒所有等待在队列中的进程。
  • wake_up_interruptible(queue):唤醒所有可中断休眠的进程。

5. 高级用法:轮询支持(poll 方法)​

为支持 select/poll/epoll 等多路复用接口,需实现 poll 方法:

unsigned int mydev_poll(struct file *filp, struct poll_table_struct *wait) {
    struct mydev_private *priv = filp->private_data;
    unsigned int mask = 0;

    poll_wait(filp, &my_wait_queue, wait);  // 将进程加入等待队列

    if (priv->data_ready)
        mask |= POLLIN | POLLRDNORM;  // 数据可读事件

    return mask;
}

// 在 file_operations 中注册
static struct file_operations mydev_fops = {
    .poll = mydev_poll,
};

6. 实现注意事项

​(1) 条件检查的原子性

在检查条件和进入休眠之间需使用锁(如自旋锁)防止竞态:

spin_lock(&priv->lock);
if (!priv->data_ready) {
    spin_unlock(&priv->lock);
    wait_event_interruptible(my_wait_queue, priv->data_ready);
} else {
    spin_unlock(&priv->lock);
}
​(2) 虚假唤醒处理

即使条件未满足,进程也可能被唤醒(如信号中断),需循环检查:

while (!priv->data_ready) {
    wait_event_interruptible(my_wait_queue, priv->data_ready);
}
​(3) 资源释放

在模块卸载时,需唤醒所有等待进程并清理队列:

void __exit mydev_exit(void) {
    wake_up_all(&my_wait_queue);
    // 其他清理操作...
}

7. 完整示例:带阻塞的字符设备驱动

#include <linux/module.h>
#include <linux/wait.h>
#include <linux/sched.h>

static DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
static int data_ready = 0;

ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
    // 非阻塞模式检查
    if (filp->f_flags & O_NONBLOCK && !data_ready)
        return -EAGAIN;

    // 阻塞等待数据就绪
    wait_event_interruptible(my_wait_queue, data_ready);

    // 模拟数据读取
    char data[] = "Hello from kernel!";
    data_ready = 0;  // 重置状态
    return simple_read_from_buffer(buf, count, pos, data, sizeof(data));
}

// 数据到达时唤醒进程(例如在中断中调用)
void data_arrived(void) {
    data_ready = 1;
    wake_up_interruptible(&my_wait_queue);
}

static struct file_operations mydev_fops = {
    .owner = THIS_MODULE,
    .read = mydev_read,
};

module_init(mydev_init);
module_exit(mydev_exit);

总结

  • 阻塞机制核心:等待队列 + 休眠/唤醒函数。
  • 非阻塞支持:检查 O_NONBLOCK 标志并返回 -EAGAIN
  • 竞态避免:原子性检查条件,使用锁保护共享数据。
  • 多路复用:通过 poll 方法支持 select/epoll

合理使用阻塞机制,可显著提升驱动性能和用户体验。

参考:

  1. Linux内核的驱动阻塞机制_内核怎么实现的阻塞-优快云博客 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩瀚之水_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值