15. Wakeup源与唤醒锁的协同:从触发到保持的完整旅程
各位同学,今天我们来聊聊一个非常核心的话题——Wakeup源和唤醒锁是怎么配合工作的。说白了,就是系统从睡梦中被叫醒,然后怎么保证它不会马上又睡过去。
我在做功耗优化的时候,经常看到有人把这两个概念搞混。有人以为只要注册了Wakeup源,系统就不会休眠了。还有人觉得只要拿了唤醒锁,就能保证设备不睡。其实都不对。它们俩是搭档,各管一段。
15.1 分工明确:谁负责叫醒,谁负责保持
先理清楚各自的职责:
- Wakeup源:负责把系统从深度睡眠中唤醒。它是个“闹钟”。
- 唤醒锁:负责在系统被唤醒后,阻止它再次进入休眠。它是个“门闩”。
你想想看,如果只有Wakeup源没有唤醒锁,系统被叫醒后,处理完中断可能马上就又睡了。用户可能连屏幕都没看到,系统就睡过去了。反过来,如果只有唤醒锁没有Wakeup源,系统根本醒不过来,锁拿得再多也没用。
核心原则:Wakeup源决定“何时醒”,唤醒锁决定“醒多久”。
15.2 完整唤醒流程:一步一步来
我记得有一次调试一个蓝牙耳机的连接问题,耳机一断开,系统就睡死了。后来发现是唤醒锁释放得太早。嗯,这里正好拿这个场景来讲讲完整的唤醒流程。
假设现在系统处于深度休眠(Suspend-to-RAM),用户按了一下电源键:
- 硬件触发:电源键对应的GPIO产生一个电平跳变。这个GPIO被配置为Wakeup源。
- 中断控制器唤醒:中断控制器(比如GIC)检测到有效中断,向CPU发送唤醒信号。
- CPU退出休眠:CPU从WFI(Wait For Interrupt)状态恢复,开始执行中断向量表。
- 中断处理:内核执行电源键对应的中断处理函数。此时系统处于“半醒”状态。
- 获取唤醒锁:中断处理函数中,驱动会调用
pm_wakeup_event()或直接获取一个唤醒锁。这一步很关键——告诉电源管理子系统:“我正在处理事情,别睡!” - 恢复设备:逐个恢复外设的电源、时钟、寄存器状态。屏幕、触摸、Wi-Fi等依次上线。
- 用户空间唤醒:系统完全恢复,用户看到锁屏界面。此时唤醒锁可能还在持有中。
- 释放唤醒锁:当事件处理完毕(比如用户已经解锁屏幕),驱动释放唤醒锁。系统重新进入可休眠状态。
避坑指南:我曾经遇到过一个Wi-Fi驱动,在中断处理里忘了获取唤醒锁。结果每次收到网络包,系统刚醒过来就又睡了,连包都没来得及处理。最后Wi-Fi断流,用户投诉。排查了两天才找到原因——就是少了一行pm_wakeup_event()。
15.3 时序图:把流程画出来
光说不够直观,我画了一张时序图。这张图我调了好几次,确保每个阶段的时间点都对得上。
从这张图可以看得很清楚:Wakeup源触发后,系统从休眠状态跳出来。紧接着中断处理函数获取唤醒锁,系统进入“唤醒锁持有”状态。这个状态会一直持续,直到事件处理完毕、锁被释放。锁释放后,系统才回到“可休眠”状态。
15.4 代码层面的配合
在代码里,这种协同关系是怎么体现的呢?我拿一个简单的按键驱动来举例:
// 按键中断处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
struct key_dev *key = dev_id;
// 1. 通知电源管理子系统:有唤醒事件
pm_wakeup_event(key->dev, 0);
// 2. 获取唤醒锁(如果还没获取)
// 注意:pm_wakeup_event内部会短暂持有唤醒锁
// 如果需要长时间保持,需要显式调用 pm_stay_awake()
// 3. 处理按键事件
schedule_work(&key->work);
return IRQ_HANDLED;
}
// 工作队列处理函数
static void key_work_handler(struct work_struct *work)
{
struct key_dev *key = container_of(work, struct key_dev, work);
// 处理按键逻辑...
// 上报按键事件到用户空间
// 4. 事件处理完毕,释放唤醒锁
pm_relax(key->dev);
}
注意:pm_wakeup_event()和pm_stay_awake()的区别在于——前者是“短时唤醒”,内核会自动释放锁;后者是“长时保持”,需要手动调用pm_relax()释放。选哪个,取决于你的事件处理时间。我个人习惯:如果处理时间超过100ms,就用pm_stay_awake() + pm_relax(),更可控。
15.5 常见问题与排查思路
在实际项目中,Wakeup源和唤醒锁配合不好,会出现两类典型问题:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 系统无法唤醒 | Wakeup源未正确注册,或中断被屏蔽 | 检查/sys/kernel/debug/wakeup_sources,看设备是否在列表中 |
| 系统唤醒后立即休眠 | 中断处理中未获取唤醒锁,或锁释放过早 | 使用trace_printk跟踪pm_wakeup_event调用时机 |
| 系统无法休眠 | 唤醒锁未被释放,或存在异常持有者 | 查看/sys/power/wake_lock,找出谁在持有锁 |
我曾经排查过一个“手机放口袋自动唤醒”的bug。最后发现是距离传感器的Wakeup源配置错了——它被配置成了上升沿触发,而传感器在口袋里的抖动产生了多次触发。解决方案很简单:改成双边沿触发,并在中断处理中加了一个去抖动的延时。
15.6 小结
Wakeup源和唤醒锁的协同,说白了就是一句话:Wakeup源负责开门,唤醒锁负责不让门关上。两者缺一不可,时序上必须衔接好。
你在写驱动的时候,记住这个原则:中断来了,先通知电源管理(pm_wakeup_event),再处理业务逻辑。处理完了,记得释放锁。顺序搞反了,系统就会出各种奇怪的问题。
嗯,今天就讲到这里。下一章我们会深入聊聊唤醒锁的调试技巧,包括怎么用wakeup_sources文件系统来定位问题。
公众号:蓝海资料掘金营,微信deep3321