通过对Linux等待队列源码的深入分析,我们能够更好地理解其实现原理,并发现其在系统中的作用与价值
一、等待队列的数据结构 等待队列基于双循环链表的数据结构,由两种核心元素构成:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)
这两者通过list_head类型的task_list链接在一起,形成了一个双向链表
1.1 wait_queue_head_t 等待队列头包含了自旋锁(spinlock_t lock)和链表头(structlist_head task_list)
自旋锁用于互斥访问,保证多线程环境下对等待队列的操作是安全的
链表头则用于链接所有等待的进程
struct __wait_queue_head { spinlock_t lock; structlist_head task_list; }; typedef struct__wait_queue_headwait_queue_head_t; 可以使用宏DECLARE_WAIT_QUEUE_HEAD(name)来声明一个等待队列头
例如: DECLARE_WAIT_QUEUE_HEAD(my_wait_queue); 1.2 wait_queue_t 等待队列项包含了标志位(unsigned int flags)、指向等待进程的指针(void private)、唤醒函数(wait_queue_func_t func)和链表元素(struct list_headtask_list)
标志位用于标记等待队列项的状态,唤醒函数则用于在条件满足时唤醒进程
struct __wait_queue { unsigned int flags; voidprivate; wait_queue_func_t func; structlist_head task_list; }; typedef struct__wait_queuewait_queue_t; 可以使用宏DECLARE_WAITQUEUE(name, tsk)来声明一个等待队列项,并将某个进程(task_struct)关联到这个等待队列项
例如: DECLARE_WAITQUEUE(my_wait_queue_entry, my_task); 二、等待队列的初始化与操作 在使用等待队列之前,需要进行初始化操作
初始化可以通过静态和动态两种方式实现
2.1 静态初始化 静态初始化是通过宏DECLARE_WAIT_QUEUE_HEAD(name)和DECLARE_WAITQUEUE(name,tsk)实现的
静态初始化在编译时分配内存,并将等待队列头和等待队列项初始化为默认值
DECLARE_WAIT_QUEUE_HEAD(wqh); DECLARE_WAITQUEUE(wq_entry, tsk); 2.2 动态初始化 动态初始化则需要手动分配内存,并调用init_waitqueue_head函数进行初始化
wait_queue_head_t wqh; init_waitqueue_head(&wqh); 将进程添加到等待队列和从等待队列中移除进程的操作同样重要
可以使用add_wait_queue和remove_wait_queue函数实现这些操作
void add_wait_queue(wait_queue_head_twq_head, wait_queue_t wq_entry); void remove_wait_queue(wait_queue_head_twq_head, wait_queue_t wq_entry); 这两个函数分别将等待队列项添加到等待队列头和从等待队列头中移除
为了确保线程安全,这两个函数都使用了自旋锁进行保护
三、休眠与唤醒机制 等待队列的核心功能是支持进程的休眠与唤醒
休眠的进程不再被调度器调度,直到被某个事件唤醒
在Linux内核中,提供了多种宏来实现进程的休眠与唤醒
3.1 休眠 可以使用wait_event宏族来让进程休眠,直到某个条件满足
例如: wait_event(wq_head,condition); wait_event_timeout(wq_head, condition,timeout); wait_event_interruptible(wq_head,condition); wait_event_interruptible_timeout(wq_head, condition,timeout); - `wait_event`:使进程进入不可中断休眠状态,直到条件为真
- `wait_event_timeout`:使进程在指定的超时时间内等待条件为真
- `wait_event_interruptible`:使进程进入可中断休眠状态,直到条件为真或被信号打断
- `wait_event_interruptible_timeout`:使进程在指定的超时时间内等待条件为真,或在被信号打断时返回
这些宏的核心是调用`___wait_event`函数,它进行一系列的准备工作,如检查进程是否有待处理信号、将进程添加到等待队列等,然后调用`schedule()`函数使进程进入休眠状态
3.2 唤醒 可以使用wake_up宏族来唤醒等待队列中的进程
例如: wake_up(&wq_head); wake_up_interruptible(&wq_head); wake_up_interruptible_nr(&wq_head,nr); wake_up_interruptible_all(&wq_head); - `wake_up`:唤醒等待队列中的所有进程
- `wake_up_interruptible`:唤醒等待队列中处于可中断休眠状态的进程
- `wake_up_interruptible_nr`:唤醒指定数量的处于可中断休眠状态的进程
- `wake_up_interruptible_all`:唤醒等待队列中所有处于可中断休眠状态的进程
这些宏的核心是调用`__wake_up`函数,它通过遍历等待队列头中的链表,找到并唤醒符合条件的进程
四、应用场景:按键驱动的实现 等待队列在内核驱动中的应用非常广泛,下面以按键驱动为例,展示如何使用等待队列实现一个简单的异步事件通知机制
假设我们有一个按键设备,用户空间的应用程序需要知道按键何时被按下
如果按键事件是随机发生的,应用程序不能通过轮询的方式读取设备文件来检测按键事件,因为这样会导致大量的CPU资源浪费
使用等待队列,可以在按键事件发生时唤醒应用程序,从而避免不必要的轮询
以下是一个简单的按键驱动示例代码:
include