当驱动等待数据、缓冲区空间、DMA 完成或硬件状态变化时,持续轮询会浪费 CPU,也会让功耗和实时性更差。Linux 用等待队列把“条件暂时不满足”的任务挂起来,设置为可中断或不可中断睡眠;事件发生后,生产者唤醒等待者,调度器再在合适时机让它继续执行。关键点不是调用一次 wake_up(),而是正确处理检查条件与进入睡眠之间的竞争。
为什么被唤醒后还要再检查条件
唤醒只是“值得再试一次”的通知,不是资源已经独占属于你的承诺。多个消费者可能同时被唤醒、生产者可能在你真正运行前再次改变状态、信号也可能打断可中断睡眠。因此等待者必须在循环里检查条件,只有条件成立才继续处理。
内核提供 wait_event()、wait_event_interruptible() 等宏,将“登记等待者、检查条件、设置状态、睡眠、再次检查”组合成经过验证的模式。手工拆开这些步骤很容易在“检查失败”与“真正睡眠”之间丢失一次唤醒。
1 | /* 消费者:条件在每次被唤醒后都要重新判断 */ |
生产者在更新 data_available 相关状态后再调用对应 wake_up*()。若状态跨 CPU 更新,还要用正确的锁或内存屏障保证等待者不会醒来却读到旧数据。
选择哪种睡眠语义
面向用户态的 read()、poll() 和长操作通常应可中断或可取消,让进程退出、关闭 fd、超时与信号有明确语义。设备故障、热拔插和卸载路径必须唤醒所有相关等待者,并让条件包含“设备已失效”,否则线程可能永远睡在一个再也不会产生事件的队列上。
实时系统还应给等待加 deadline:硬件没有在预算内完成时,不应无限等待再导致控制链路使用过期状态。等待队列是避免忙等的基础,但正确的超时、取消和错误状态才让它成为可靠的工程接口。