4. 等待队列初始化
等待队列在使用前必须完成初始化。Linux 内核提供了两种初始化方式:静态初始化(编译时完成)和动态初始化(运行时完成)。开发者应根据等待队列的生命周期和作用域选择合适的初始化方法。
4.1 静态初始化:DECLARE_WAIT_QUEUE_HEAD
静态初始化通过宏 DECLARE_WAIT_QUEUE_HEAD 在编译时完成等待队列的创建和初始化。该宏定义等待队列头变量并调用 __init_waitqueue_head 进行初始化。
宏定义原型:
#include <linux/wait.h>
/* 静态初始化宏 */
DECLARE_WAIT_QUEUE_HEAD(name);
使用示例:
/* 在文件作用域定义全局等待队列 */
DECLARE_WAIT_QUEUE_HEAD(my_wq);
/* 在函数内部定义静态等待队列 */
static DECLARE_WAIT_QUEUE_HEAD(irq_wq);
| 特性 | 说明 |
|---|---|
| 作用域 | 文件作用域或函数内部 static 作用域 |
| 生命周期 | 与程序/模块生命周期一致 |
| 初始化时机 | 编译时,无需运行时调用 |
| 典型场景 | 全局等待队列、驱动模块级等待队列 |
| 内存分配 | 静态分配,位于 .data 或 .bss 段 |
最佳实践:当等待队列在整个驱动生命周期内都存在且不需要动态创建/销毁时,优先使用静态初始化。它避免了运行时初始化遗漏的风险,且代码更简洁。
4.2 动态初始化:init_waitqueue_head
动态初始化通过调用 init_waitqueue_head 函数在运行时初始化等待队列头。适用于等待队列头嵌入在其他数据结构中,或需要动态分配内存的场景。
函数原型:
#include <linux/wait.h>
void init_waitqueue_head(struct wait_queue_head *wq_head);
使用示例:
/* 方式一:动态分配 + 初始化 */
struct wait_queue_head *dyn_wq;
dyn_wq = kmalloc(sizeof(struct wait_queue_head), GFP_KERNEL);
if (!dyn_wq)
return -ENOMEM;
init_waitqueue_head(dyn_wq);
/* 方式二:嵌入结构体 + 初始化 */
struct my_device {
int data;
spinlock_t lock;
struct wait_queue_head read_wq; /* 等待队列嵌入结构体 */
};
struct my_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
init_waitqueue_head(&dev->read_wq);
| 特性 | 说明 |
|---|---|
| 作用域 | 堆分配或嵌入结构体 |
| 生命周期 | 由开发者控制,可动态创建/销毁 |
| 初始化时机 | 运行时,必须在使用前调用 |
| 典型场景 | 设备结构体嵌入、每设备等待队列、动态模块 |
| 内存分配 | 动态分配(kmalloc/kzalloc)或静态嵌入 |
注意事项:
- 动态分配的等待队列在使用完毕后,必须使用
kfree释放内存,防止内存泄漏。 - 嵌入结构体的等待队列,其生命周期跟随父结构体,无需单独释放。
- 必须在任何等待/唤醒操作之前完成初始化,否则会导致内核崩溃。
4.3 两种初始化方式对比
| 对比维度 | 静态初始化 (DECLARE_WAIT_QUEUE_HEAD) | 动态初始化 (init_waitqueue_head) |
|---|---|---|
| 初始化时机 | 编译时 | 运行时 |
| 代码行数 | 1 行宏定义 | 声明 + 分配 + 初始化(至少 3 行) |
| 灵活性 | 低,固定作用域 | 高,可动态创建/销毁 |
| 内存位置 | .data / .bss 段 | 堆或父结构体内存 |
| 适用场景 | 全局等待队列、单例驱动 | 多设备实例、热插拔、模块化 |
| 初始化遗漏风险 | 无(编译时完成) | 有(需确保调用) |
4.4 等待队列头数据结构
理解等待队列头的内部结构有助于掌握初始化原理。等待队列头 struct wait_queue_head 定义在 <linux/wait.h> 中:
struct wait_queue_head {
spinlock_t lock; /* 保护等待队列的自旋锁 */
struct list_head head; /* 等待队列链表头 */
};
初始化操作的核心工作:
- 初始化自旋锁:
spin_lock_init(&wq_head->lock),确保并发安全。 - 初始化链表头:
INIT_LIST_HEAD(&wq_head->head),将链表头指向自身,形成空队列。
内核源码追踪:静态宏 DECLARE_WAIT_QUEUE_HEAD 展开后等价于:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
struct wait_queue_head name = { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.head = LIST_HEAD_INIT(name.head) \
}
动态函数 init_waitqueue_head 实现:
void init_waitqueue_head(struct wait_queue_head *wq_head)
{
spin_lock_init(&wq_head->lock);
INIT_LIST_HEAD(&wq_head->head);
}
4.5 典型应用场景示例
以下代码展示了一个字符驱动中两种初始化方式的典型用法:
/* 场景一:全局等待队列 - 静态初始化 */
DECLARE_WAIT_QUEUE_HEAD(global_event_wq);
/* 场景二:每设备等待队列 - 动态初始化 */
struct my_char_dev {
struct cdev cdev;
struct semaphore sem;
struct wait_queue_head read_wq; /* 嵌入结构体 */
atomic_t data_ready;
};
static int my_char_open(struct inode *inode, struct file *filp)
{
struct my_char_dev *dev;
dev = container_of(inode->i_cdev, struct my_char_dev, cdev);
filp->private_data = dev;
/* 动态初始化等待队列 */
init_waitqueue_head(&dev->read_wq);
return 0;
}
选择建议:
- 驱动中只有一个等待队列且全局可见 → 使用 DECLARE_WAIT_QUEUE_HEAD
- 驱动支持多个设备实例,每个设备有自己的等待队列 → 使用 init_waitqueue_head 嵌入设备结构体
- 等待队列在模块卸载时销毁 → 使用动态初始化 + 动态分配