4. 等待队列初始化

等待队列在使用前必须完成初始化。Linux 内核提供了两种初始化方式:静态初始化(编译时完成)和动态初始化(运行时完成)。开发者应根据等待队列的生命周期和作用域选择合适的初始化方法。

等待队列初始化方式对比 静态初始化 DECLARE_WAIT_QUEUE_HEAD 编译时完成初始化 适用于全局/静态等待队列 无需额外调用初始化函数 定义即初始化,零开销 动态初始化 init_waitqueue_head 运行时调用初始化函数 适用于动态分配/嵌入结构体 必须在使用前调用 适合模块化、灵活场景 选择依据:等待队列的作用域、生命周期、是否嵌入其他数据结构

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 两种初始化方式对比

静态初始化 vs 动态初始化
对比维度 静态初始化 (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 嵌入设备结构体
  • 等待队列在模块卸载时销毁 → 使用动态初始化 + 动态分配