init_waitqueue_head()函数
你写的应该是:
init_waitqueue_head()
不是 init_waitqeue_head,中间是 queue。
它的作用是:
初始化一个等待队列头 wait_queue_head_t,让后续进程可以在这个队列上睡眠,等事件发生后再被 wake_up 唤醒。
1. 它解决什么问题?
在驱动里经常有这种场景:
用户进程 read() 设备
↓
当前没有数据
↓
不能一直死循环占 CPU
↓
让进程睡眠
↓
DMA 完成 / 中断来了 / 数据准备好
↓
中断函数唤醒睡眠的进程
这个“让进程睡眠、之后再唤醒”的机制,就常用 wait queue 等待队列。
Linux 内核文档中,wait_event() / wait_event_interruptible() 都是让进程在某个 wait queue 上睡眠,直到 condition 条件成立;并且要求在改变条件变量后调用 wake_up() 唤醒等待者。(Linux Kernel 文档)
2. 基本写法
一般在设备结构体里定义:
struct my_dma_dev {
wait_queue_head_t read_wq;
int data_ready;
};
然后在 probe() 或模块初始化里初始化:
init_waitqueue_head(&dev->read_wq);
dev->data_ready = 0;
含义是:
初始化 read_wq 这个等待队列头
以后进程可以睡在 read_wq 上
中断来了可以通过 wake_up(&dev->read_wq) 唤醒它
3. read() 里面等待数据
例如用户态调用:
read(fd, buf, len);
驱动的 read() 回调里可以这样写:
static ssize_t my_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
struct my_dma_dev *dev = file->private_data;
int ret;
ret = wait_event_interruptible(dev->read_wq, dev->data_ready == 1);
if (ret)
return ret; // 被信号打断,一般返回 -ERESTARTSYS
/*
* 能执行到这里,说明 data_ready == 1
* 也就是 DMA 数据已经准备好
*/
dev->data_ready = 0;
/*
* copy_to_user(buf, ...)
*/
return count;
}
wait_event_interruptible() 的意思是:当前进程进入 TASK_INTERRUPTIBLE 睡眠,直到条件为真,或者收到信号;如果被信号打断,会返回 -ERESTARTSYS,如果条件变为真则返回 0。(Linux Kernel 文档)
4. 中断函数里唤醒
当 FPGA DMA 完成后,触发 MSI 中断,驱动中断函数里:
static irqreturn_t dma_irq_handler(int irq, void *dev_id)
{
struct my_dma_dev *dev = dev_id;
u32 status;
status = ioread32(dev->bar0 + INTERRUPT_STATUS);
if (status & INT_C2H_DONE) {
dev->data_ready = 1;
wake_up_interruptible(&dev->read_wq);
}
return IRQ_HANDLED;
}
这几句的含义是:
1. 中断来了
2. 驱动确认 C2H DMA 完成
3. 设置 data_ready = 1
4. 唤醒睡在 read_wq 上的进程
5. read() 继续往下执行
5. 完整流程
1. 驱动 probe()
2. init_waitqueue_head(&dev->read_wq)
3. 用户调用 read()
4. read() 发现 data_ready == 0
5. wait_event_interruptible() 让进程睡眠
6. FPGA DMA 完成
7. FPGA 触发 MSI 中断
8. Linux 调用 irq_handler()
9. irq_handler 设置 data_ready = 1
10. irq_handler 调用 wake_up_interruptible()
11. 睡眠的 read() 被唤醒
12. read() 检查 data_ready == 1
13. copy_to_user() 把数据给用户态
14. read() 返回
6. init_waitqueue_head() 初始化了什么?
可以简单理解为它初始化两个东西:
1. 等待队列内部链表
2. 保护等待队列的锁
也就是把这个队列变成一个合法可用的等待队列。
如果不初始化,后面直接 wait_event_interruptible() 或 wake_up_interruptible(),可能导致内核异常、死机或者不可预期行为。
7. 和 DECLARE_WAIT_QUEUE_HEAD() 的区别
有两种常见初始化方式。
方式一:动态初始化
适合结构体成员:
struct my_dma_dev {
wait_queue_head_t read_wq;
};
init_waitqueue_head(&dev->read_wq);
这是驱动里最常见的方式。
方式二:静态定义并初始化
适合全局变量:
static DECLARE_WAIT_QUEUE_HEAD(read_wq);
它等价于:
定义 read_wq
同时完成初始化
老版 Linux Device Drivers 也说明,等待队列可以先定义 wait_queue_head_t,再用 init_waitqueue_head() 初始化;如果是静态声明,也可以用 DECLARE_WAIT_QUEUE_HEAD() 在编译期初始化。(LWN.net)
8. 在 PCIe DMA 驱动里的典型用法
你的 Block DMA 场景可以这样对应:
C2H DMA:FPGA → Host
用户态可能调用:
read()
然后驱动等待 C2H DMA 完成:
wait_event_interruptible(dev->c2h_wq, dev->c2h_done);
DMA 完成中断里:
dev->c2h_done = 1;
wake_up_interruptible(&dev->c2h_wq);
所以 init_waitqueue_head() 一般在 probe() 里做:
static int my_pcie_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct my_dma_dev *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
init_waitqueue_head(&dev->c2h_wq);
init_waitqueue_head(&dev->h2c_wq);
dev->c2h_done = 0;
dev->h2c_done = 0;
return 0;
}
中断里:
if (status & INT_BLC2H) {
dev->c2h_done = 1;
wake_up_interruptible(&dev->c2h_wq);
}
if (status & INT_BLH2C) {
dev->h2c_done = 1;
wake_up_interruptible(&dev->h2c_wq);
}
用户线程里:
ret = wait_event_interruptible(dev->c2h_wq, dev->c2h_done);
9. 最容易出错的点
第一,必须先改变条件,再 wake_up:
dev->data_ready = 1;
wake_up_interruptible(&dev->read_wq);
不要反过来:
wake_up_interruptible(&dev->read_wq);
dev->data_ready = 1;
因为被唤醒的线程会检查 condition,如果条件还没变成真,可能继续睡回去。内核文档也强调,改变会影响等待条件的变量后,需要调用 wake_up()。(Linux Kernel 文档)
第二,wait_event_interruptible() 的条件必须是真实状态,不要写成单纯等中断:
wait_event_interruptible(dev->read_wq, dev->data_ready == 1);
因为唤醒可能是虚假唤醒,醒来后还要再次判断条件。
第三,多个线程同时等同一个队列时,要考虑锁、状态变量和数据队列,否则可能一个 DMA 完成事件被多个线程同时消费。
10. 一句话总结
init_waitqueue_head(&dev->read_wq);
意思是:
初始化一个等待队列。后面 read/ioctl 等进程可以通过
wait_event_interruptible()睡在这个队列上;DMA 中断来了以后,驱动通过wake_up_interruptible()把它们唤醒。
在 PCIe DMA 驱动中,它通常用于:
用户进程等待 DMA 完成
中断函数通知 DMA 已完成
驱动唤醒等待的用户进程
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)