尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

3 分钟搞懂内核等待机制:字符设备驱动不空转入门指南

3 分钟搞懂内核等待机制:字符设备驱动不空转入门指南 3 分钟搞懂内核等待机制字符设备驱动不空转入门指南【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux你给字符设备驱动配了个读状态的程序结果它写死在while (1) read()里——设备半天没动静CPU 却先一步打满。问题不在轮询本身而在于内核早就给了另一套方案把进程挂进等待队列一条内核里的取号排队名单设备一有事件就把它叫醒。Linux 内核项目里wait_queue_head_t、poll_wait()和wake_up()这三个角色就是整套等待机制的全部骨架。一句话讲透内核等待机制前台登记加门铃把驱动想象成一家店。顾客进程来排队不是一直盯着货架而是在前台登记一个取号单然后坐下等店里驱动的等待队列就是那本登记簿柜台员工发现货到了时按一声门铃wake_up()叫号系统就把对应的顾客请回去办事。顾客登记一次、睡到天荒地老店不通知他就不跑一趟——这就是等待-唤醒替代轮询的全部含义CPU 的使用率自然降下来了。机制分步拆解睡下去、感知到、被叫醒进程是怎么睡下去的用户态调用poll()后内核走do_sys_poll()顺手构造一张poll_table可以理解为一张登记表上面写着顾客要等哪几类事件然后逐个回调驱动的poll方法。驱动检查完状态、发现现在没数据就返回 0。内核拿到 0 就明白没货睡吧。接着把进程状态改成TASK_INTERRUPTIBLE调用schedule()让出 CPU——注意此时登记簿上已经有它的取号单了醒来不会丢事件。驱动里注册等待队列的写法驱动侧只需要两件事。第一件事声明等待队列头static DECLARE_WAIT_QUEUE_HEAD(dev_wq); /* 等待队列头驱动里的登记簿 */第二件事在poll回调里调用poll_wait(file, dev_wq, wait)。poll_wait 的实现其实很薄它检查登记表的回调_qproc是否有效有效就借它把当前进程插入你给的队列随后紧跟一条smp_mb()内存屏障和事件路径里的wq_has_sleeper()配对防止事件发生了却看不见排队者这类竞态。内核态源码里能看到 HID 驱动的 poll 回调就是教科书式用法先poll_wait()再判断环形队列头尾是否相等不等就上报EPOLLIN。事件到达后驱动如何把进程叫醒设备真正干完活的地方中断处理程序或工作线程只做两步置一个状态标志再调wake_up_interruptible(dev_wq)。它遍历队列里所有节点把TASK_INTERRUPTIBLE的进程翻成TASK_RUNNING交给调度器重新排程。被叫醒的进程随后重跑一遍驱动的poll回调确认状态确认有数据才返回事件掩码给poll()。这里有个细节wake_up_interruptible()的等待者可以被信号打断wake_up()则不行——选哪个取决于你的场景。最小可运行示例驱动侧加用户态合计 30 行先给驱动侧的最小骨架状态标志加 poll 回调加中断唤醒static DECLARE_WAIT_QUEUE_HEAD(dev_wq); /* 等待队列头登记簿 */ static int has_event; /* 事件标志有没有货 */ static __poll_t dev_poll(struct file *f, poll_table *wait) { __poll_t m 0; poll_wait(f, dev_wq, wait); /* 第一步把进程登记进等待队列 */ if (has_event) m EPOLLIN | EPOLLRDNORM; /* 第二步有事件才上报可读 */ return m; } static irqreturn_t dev_irq(int irq, void *d) { has_event 1; /* 先置标志 */ wake_up_interruptible(dev_wq); /* 再按门铃唤醒等待进程 */ return IRQ_HANDLED; }用户态就是标准poll()循环int fd open(/dev/mydev, O_RDONLY); /* 打开设备 */ struct pollfd p { .fd fd, .events POLLIN }; /* 声明只关心可读 */ for (;;) { if (poll(p, 1, -1) 0) { /* 睡到 poll() 返回才算有事件 */ char b[64]; read(fd, b, sizeof b); /* 确认有数据才读 */ } }怎么验证跑通了strace -c盯着用户态程序正常时poll长期阻塞不返回、CPU 接近 0%手动触发设备事件后poll立刻返回 1——睡-醒闭环成立。新手避坑清单poll 不生效的常见 4 种原因现象根因一句话解决⚠️ 进程永远不醒strace里poll卡死事件路径漏了wake_up()只置了标志没按门铃在中断/工作线程里置标志 wake_up_interruptible()成对出现poll明明没数据却立刻返回 0进程空转驱动没调poll_wait()进程根本没排上队poll回调第一行就放poll_wait(file, wq, wait)偶发丢事件唤醒早于登记没人接收事件路径与poll_wait()登记之间存在竞态窗口事件路径里先wq_has_sleeper()确认再poll_wait()后复查状态标志想等可写却只收到了POLLINpoll回调里忘记返回EPOLLOUT掩码检查返回值里是否拼齐了EPOLLOUT / EPOLLWRNORM延伸阅读从源码路径继续深挖poll_wait()本体与poll_table结构定义include/linux/poll.hpoll()系统调用入口do_sys_poll()fs/select.c生产级驱动样例一HID 的 poll 实现看环形队列怎么映射成EPOLLIN生产级驱动样例二SCSI 控制器的 poll 实现看自旋锁保护事件标志的写法等待队列与wake_up家族的完整文档Documentation/core-api/wait_rst.rst动手建议打开fs/select.c从do_sys_poll()往下追一次pollwake()把它和上面两张流程图对上号——追通这一条链路等待机制在你脑子里就不再是黑盒了。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表