Linux 内核等待队列(wait queue)机制详解
本文介绍了Linux内核中的等待队列机制,该机制通过wait_queue_head和wait_queue_entry数据结构实现进程/线程的睡眠与唤醒。等待队列广泛应用于驱动、内存管理等领域,支持普通、独占和优先级等唤醒策略。文章详细讲解了等待队列的声明初始化、使用流程(睡眠/唤醒)、高级用法(条件等待宏)及内核实现原理,包括链表结构、并发保护等。最后总结了典型应用场景和使用建议,强调合理选择唤醒
1. 背景与意义
1.1 并发与同步的挑战
在 Linux 内核开发中,进程/线程经常需要等待某些条件成立(如 I/O 完成、资源可用、事件发生等),而不是一直占用 CPU 忙等。如何高效地让进程“睡眠”并在条件满足时被唤醒,是内核并发同步的核心问题之一。
1.2 等待队列的作用
等待队列(wait queue)是 Linux 内核提供的一种高效、灵活的睡眠与唤醒机制。它允许一个或多个进程/线程在某个条件未满足时主动挂起自己(进入睡眠),并在条件满足时被其他线程或中断处理程序唤醒。等待队列广泛应用于设备驱动、内存管理、文件系统、进程调度等内核子系统。
2. 核心数据结构
2.1 struct wait_queue_head
struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
-
lock:自旋锁,保护等待队列的并发访问。
-
head:链表头,挂载所有等待该队列的 wait_queue_entry。
2.2 struct wait_queue_entry
struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};
-
flags:标志位(如 WQ_FLAG_EXCLUSIVE 等),控制唤醒策略。
-
private:通常指向等待的 task_struct(进程/线程描述符)。
-
func:唤醒回调函数,决定如何唤醒等待者。
-
entry:链表节点,用于挂载到 wait_queue_head 的链表上。
3. 等待队列的实现原理
3.1 等待队列的基本思想
-
当进程需要等待某个条件时,将自身以 wait_queue_entry 的形式加入 wait_queue_head 链表,并进入睡眠状态(如 TASK_UNINTERRUPTIBLE)。
-
其他线程或中断处理程序在条件满足时,调用唤醒函数(如 wake_up),遍历等待队列并唤醒相应的进程。
-
支持多种唤醒策略(全部唤醒、只唤醒一个、优先级唤醒等)。
3.2 等待队列的类型
-
普通等待队列:所有等待者平等,唤醒时可全部唤醒或唤醒一个。
-
独占等待队列(exclusive):只有一个等待者被唤醒(如互斥锁、信号量等场景)。
-
优先级等待队列:根据等待者优先级决定唤醒顺序。
4. 等待队列的用法
4.1 声明与初始化
4.1.1 静态声明
DECLARE_WAIT_QUEUE_HEAD(my_queue);
4.1.2 动态初始化
struct wait_queue_head my_queue;
init_waitqueue_head(&my_queue);
4.2 等待与唤醒的基本流程
4.2.1 等待(睡眠)
1. 定义等待队列项
//定义名为wait的entry
DEFINE_WAIT(wait);
//指定entry的func为function
DEFINE_WAIT_FUNC(wait, function);
struct wait_queue_entry wait;
init_waitqueue_entry(&wait, current);
2. 加入等待队列并睡眠
add_wait_queue(&my_queue, &wait);
set_current_state(TASK_UNINTERRUPTIBLE);
schedule(); // 进入睡眠
3. 条件满足后移除等待队列
set_current_state(TASK_RUNNING);
remove_wait_queue(&my_queue, &wait);
4.2.2 唤醒
//唤醒一个等待者
wake_up(&my_queue);
//唤醒所有等待者
wake_up_all(&my_queue);
//唤醒可中断等待者
wake_up_interruptible(&my_queue);
4.3 等待队列的高级用法
4.3.1 条件等待宏
内核提供了大量基于等待队列的条件等待宏,简化常见用法。大家经常看到的wait_event_*系列就是基于wait_queue技术的一系列的宏定义。具体如下:
wait_event:进程在 condition 为假时睡眠,直到 condition 为真被唤醒。
wait_event(my_queue, condition);
wait_event_interruptible:可被信号中断的等待。
wait_event_interruptible(my_queue, condition);
wait_event_timeout:带超时的等待。
wait_event_timeout(my_queue, condition, timeout);
wait_event_interruptible_timeout:可被信号中断且带超时的等待。
wait_event_interruptible_timeout(my_queue, condition, timeout);
4.3.2 独占等待
-
add_wait_queue_exclusive
只唤醒一个等待者,常用于互斥锁、信号量等场景。
4.3.3 优先级等待
-
add_wait_queue_priority
按优先级插入等待队列,唤醒时优先级高的先被唤醒。
5. 内核实现细节
5.1 等待队列的链表结构
-
每个 wait_queue_head 维护一个链表,链表节点为 wait_queue_entry。
-
每个等待的进程/线程都以 wait_queue_entry 的形式挂在链表上。
5.2 睡眠与唤醒的流程
-
睡眠:进程设置自身状态为 TASK_UNINTERRUPTIBLE 或 TASK_INTERRUPTIBLE,加入等待队列,调用 schedule() 让出 CPU。
-
唤醒:其他线程或中断处理程序调用 wake_up/wake_up_all,遍历等待队列,调用每个 wait_queue_entry 的 func 回调,唤醒相应进程。
5.3 唤醒函数
-
default_wake_function:标准唤醒函数,将等待进程状态设置为 TASK_RUNNING。
-
autoremove_wake_function:唤醒后自动从等待队列移除。
-
自定义唤醒函数:可通过 func 字段指定自定义唤醒逻辑。
5.4 并发与锁保护
-
所有对 wait_queue_head 的操作都需持有其自旋锁 lock,保证多核/多线程安全。
6. 典型应用场景
6.1 设备驱动中的阻塞 I/O
驱动在无数据可读时让进程睡眠,有数据到来时唤醒:
static DECLARE_WAIT_QUEUE_HEAD(read_queue);
ssize_t mydev_read(...) {
wait_event_interruptible(read_queue, data_ready());
// 读取数据
}
void mydev_irq_handler(...) {
// 数据到来
wake_up_interruptible(&read_queue);
}
6.2 poll/epoll/select 支持
驱动的 f_op->poll
实现中,调用 poll_wait()
将当前进程加入等待队列,实现异步 I/O 支持。
6.3 进程同步与互斥
如信号量、互斥锁、条件变量等,底层都可用等待队列实现。
6.4 内存管理
如页回收、swap、OOM 等场景,进程等待内存资源可用时进入等待队列。
7.总结
-
wait_queue_head
是等待队列的头部,管理所有等待者。 -
wait_queue_entry
描述每个等待的进程/线程,支持自定义唤醒逻辑。 -
等待队列机制高效、灵活,适合各种同步、阻塞、事件通知场景。
-
推荐使用内核提供的 wait_event_xxx 宏,简化代码、减少错误。
-
注意并发安全、死锁防范和唤醒时机,保证系统健壮性。
7.1 超时与中断
-
等待队列支持超时等待和可中断等待,便于实现复杂同步逻辑。
-
推荐使用 wait_event_xxx 宏,避免手动管理队列和状态。
7.2 唤醒优化
-
对于只需唤醒一个等待者的场景,使用独占等待队列和 wake_up/wake_up_locked。
-
对于所有等待者都需唤醒的场景,使用 wake_up_all。
7.3 死锁与竞态防范
-
等待条件的判断和唤醒必须配合,避免丢失唤醒或死锁。
-
修改影响等待条件的变量后,必须立即调用 wake_up。
7.4 与信号、定时器协作
-
可结合信号处理和定时器,实现可中断和超时等待。
更多推荐
所有评论(0)