Linux内核唤醒源 wakeup_source 结构体解析:系统死活不睡?一条命令揪出元凶
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
合上笔记本盖子,风扇却还在呼呼转,屏幕暗下去之后机器始终没有真正睡过去——这种排障场景里最常被追问的就是:谁拦住了休眠?答案藏在 Linux内核唤醒源 wakeup_source 机制里。这篇基于 Linux 内核源码的文章,把唤醒源这条链路一次讲透:它如何被标记、结构体里每个字段各管什么、系统无法休眠时该敲哪条命令定位问题。
一、一次唤醒的完整旅程
你可以把每个 wakeup_source 理解成一张「请假条」:调用激活 API 是递条子,调用释放 API 是销假,只要抽屉里还有没销的假条,电源管理就不敢关灯。一条典型的链路按时间线展开:
- 事件发生:键盘被按下、网卡收到一个包、RTC 闹钟到点,某个硬件事件产生了。
- 中断上报:设备驱动的中断处理程序运行,判断「这个事件需要把系统从休眠里叫醒」,于是调用
pm_stay_awake()(内部走__pm_stay_awake())。 - 唤醒源被标记:
active置 1,start_time记下进入活跃的时刻,event_count加一,这个唤醒源从此处于「在岗」状态。 - 休眠被否决:suspend 流程走到检查环节时,发现名单上还有活跃条目,本轮睡眠直接中止,内核日志里会留下痕迹。
- 统计计数:下次调用释放时,
active_time累加本次在岗时长;如果这次事件真把系统从睡眠中拉了起来,wakeup_count再记一笔。
这五步走完,「谁拦了休眠」的完整账目就记进了那个结构体里,后面所有调试手段,本质都是在读这本账。
二、wakeup_source 结构体解析:11 个字段各管什么
定义在 include/linux/pm_wakeup.h,开头几行是这样的:
struct wakeup_source { const char *name; struct device *dev; struct list_head entry; };剩下字段按职责分三类:时间账、计数器、生命周期控制。
| 成员 | 类型 | 作用 |
|---|---|---|
| name | const char * | 名字,调试输出里的第一列就是它 |
| dev | struct device * | 关联的设备指针,可以传 NULL 表示无主 |
| entry | struct list_head | 链表节点,把唤醒源挂进内核全局名单 |
| start_time | unsigned long | 最近一次进入活跃的时间戳 |
| active_time | unsigned long | 累计活跃时长,睡不掉的头号线索 |
| event_count | unsigned int | 被标记活跃的次数 |
| wakeup_count | unsigned int | 真正阻止成功休眠的次数 |
| expires | unsigned int | 自动过期超时(毫秒),0 表示不过期 |
| timer | struct timer_list | 到点自动执行释放的内核定时器 |
| usage_count | atomic_t | 使用引用计数,降到 0 才允许注销 |
| active | atomic_t | 当前是否处于活跃状态 |
三、四个关键 API 的调用时机
wakeup_source_register():驱动 probe 或模块初始化时调用,把唤醒源挂进全局名单。dev 传 NULL 就得到一个通用唤醒源,比如 kernel/power/autosleep.c 里的 autosleep。__pm_stay_awake():要阻止休眠时调用,典型位置是中断处理函数里。可以带 expires 超时,到期由定时器自动帮你销假,避免长期挂着。__pm_relax():用完立刻配对调用。所有「系统从此不睡」的案例,十有八九是这里漏了。wakeup_source_unregister():驱动 remove 时调用,usage_count归零才真正释放。fs/eventpoll.c 销毁 eventpoll 时就是这么清理的。
除了直接调用,子系统也各有封装:kernel/power/wakelock.c 给用户态留了 /sys/kernel/wakeup_locks 入口;drivers/power/supply/power_supply_core.c 和 drivers/acpi/device_pm.c 则分别为电源框架与 ACPI 设备持有各自的唤醒源。
四、Linux系统无法休眠排查:系统死活不睡时该敲哪条命令
🔍 第一步永远是看全局名单:
$ cat /sys/kernel/debug/wakeup_sources count active event_count wakeup_count name 0 0 ms 1 0 mmc0 0 4520 ms 12 0 usb1怎么读:count非 0 表示此刻还有人在拦休眠;active是一直挂着的毫秒数,越大越可疑;event_count和wakeup_count则告诉你历史上是谁反复捣乱。顺着name找到对应设备或驱动,再回到第三节的四个 API 里定位调用点。内核内部如需遍历名单,用wakeup_sources_read_lock()加for_each_wakeup_source(ws)宏即可,写驱动时可直接参考现有实现。
五、唤醒源调试:常见坑与最佳实践
- 只激活不释放:中断或错误路径里调了 stay_awake,但某个分支忘了 relax,系统从此告别休眠。写法上把激活和释放严格配对,统一走同一个出口。
- 长期唤醒源不设超时:给
expires一个合理的自动过期值 ⏰,是防止「挂上去就忘」的最便宜保险。 - 忘记 unregister:remove 路径漏掉注销,
usage_count永远降不下来,还会被内存泄漏检测工具点名。
休眠不是玄学,是一本明账。读懂 wakeup_source 的字段、掌握这四个 API、会用那条 debugfs 命令,下次「系统不睡」你就能直接翻账。
相关资源:
- include/linux/pm_wakeup.h:wakeup_source 定义与 API
- kernel/power/autosleep.c:自动休眠的唤醒源
- kernel/power/wakelock.c:用户态 wakelock 接口
- Documentation/power/:电源管理文档
- drivers/base/power/:设备电源管理实现
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考