设备驱动开发中的等待队列实现:睡眠与唤醒机制的C语言模型
扫描二维码
随时随地手机看文章
在Linux设备驱动开发中,等待队列(Wait Queue)是实现进程睡眠与唤醒的核心机制,它允许进程在资源不可用时主动放弃CPU,进入可中断睡眠状态,待资源就绪后再被唤醒。本文通过C语言模型解析等待队列的实现原理,结合代码示例说明其关键机制。
一、等待队列的核心数据结构
等待队列的本质是一个包含进程描述符(task_struct)的链表,每个节点代表一个处于等待状态的进程。Linux内核通过wait_queue_head_t和wait_queue_t两个结构体管理等待队列:
c
// 内核源码中的简化定义(include/linux/wait.h)
struct __wait_queue_head {
spinlock_t lock; // 自旋锁保护队列操作
struct list_head task_list; // 等待进程链表
};
typedef struct __wait_queue_head wait_queue_head_t;
struct __wait_queue {
unsigned int flags; // 等待标志(WQ_FLAG_EXCLUSIVE等)
void *private; // 私有数据(通常指向等待条件)
struct list_head task_list; // 链表节点
wait_queue_func_t func; // 唤醒回调函数
};
typedef struct __wait_queue wait_queue_t;
二、等待队列的初始化与销毁
1. 静态初始化(编译时确定)
c
// 静态初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
// 静态初始化等待队列项
static wait_queue_t my_wait_item = {
.flags = 0,
.private = NULL,
.func = default_wake_function, // 内核默认唤醒函数
};
2. 动态初始化(运行时确定)
c
// 动态初始化等待队列头
wait_queue_head_t dynamic_queue;
init_waitqueue_head(&dynamic_queue);
// 动态初始化等待队列项
wait_queue_t *item = kmalloc(sizeof(wait_queue_t), GFP_KERNEL);
init_waitqueue_entry(item, current); // 绑定当前进程
三、睡眠与唤醒的核心机制
1. 进程睡眠模型
c
// 模拟设备驱动中的等待逻辑
void device_wait_example(wait_queue_head_t *queue) {
DEFINE_WAIT(wait); // 创建等待队列项并初始化
// 将当前进程添加到等待队列(未睡眠状态)
add_wait_queue(queue, &wait);
for (;;) {
// 设置进程状态为可中断睡眠
set_current_state(TASK_INTERRUPTIBLE);
// 检查条件是否满足(模拟设备就绪检查)
if (device_is_ready()) {
break;
}
// 主动调度让出CPU(进入睡眠)
schedule();
// 被唤醒后检查是否被信号中断
if (signal_pending(current)) {
printk("Process interrupted by signal\n");
remove_wait_queue(queue, &wait);
return -ERESTARTSYS;
}
}
// 恢复进程状态并移除等待项
__set_current_state(TASK_RUNNING);
remove_wait_queue(queue, &wait);
}
2. 唤醒机制模型
c
// 模拟设备中断唤醒等待进程
void device_wakeup_example(wait_queue_head_t *queue) {
// 遍历等待队列唤醒所有进程(非独占模式)
wake_up_interruptible_all(queue);
/* 实际内核实现(简化版):
struct list_head *tmp;
list_for_each(tmp, &queue->task_list) {
wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
if (curr->func(curr)) // 执行唤醒回调
try_to_wake_up(curr->private, TASK_INTERRUPTIBLE, 0);
}
*/
}
四、完整案例:字符设备驱动中的等待队列
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wait.h>
static wait_queue_head_t data_available;
static int device_ready = 0;
// 模拟设备就绪(如中断处理程序调用)
void trigger_device_ready(void) {
device_ready = 1;
wake_up_interruptible(&data_available); // 唤醒所有等待进程
}
// 阻塞式读取实现
ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
DECLARE_WAITQUEUE(wait, current);
int ret = 0;
add_wait_queue(&data_available, &wait);
while (!device_ready) {
set_current_state(TASK_INTERRUPTIBLE);
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
schedule(); // 睡眠
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out;
}
}
// 模拟数据传输
if (copy_to_user(buf, "Data", 4)) {
ret = -EFAULT;
} else {
ret = 4;
device_ready = 0; // 重置状态
}
out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&data_available, &wait);
return ret;
}
static int __init my_init(void) {
init_waitqueue_head(&data_available);
printk("Wait queue demo initialized\n");
return 0;
}
module_init(my_init);
MODULE_LICENSE("GPL");
五、关键注意事项
状态管理:
必须在调用schedule()前设置TASK_INTERRUPTIBLE/UNINTERRUPTIBLE
唤醒后必须恢复状态为TASK_RUNNING
竞态条件:
c
// 错误示例:检查与睡眠间存在竞态
if (!device_ready) { // A
schedule(); // B
// 竞态窗口:设备可能在A和B之间就绪
}
唤醒策略:
wake_up():唤醒所有非独占等待者
wake_up_interruptible():仅唤醒可中断等待者
wake_up_nr():限制唤醒数量
性能优化:
使用DEFINE_WAIT()替代手动初始化
考虑使用wait_event_*()宏简化代码
结论:等待队列是设备驱动中实现异步通知的核心机制,其正确实现需要严格管理进程状态、处理竞态条件并选择合适的唤醒策略。现代Linux内核提供了wait_event()、wake_up_poll()等高级抽象,但理解底层原理仍是调试复杂睡眠-唤醒问题的关键。开发者应通过内核文档(Documentation/core-api/wait.rst)和实际案例深入掌握其工作机制。