6. Wakeup源调试方法:三板斧实战
调试Wakeup源,说白了就是跟「谁把系统唤醒了」这件事死磕。我做了这么多年电源管理,最头疼的就是半夜收到测试反馈——「手机待机一晚上掉了20%电」。嗯,这时候就该拿出我们的三板斧了。
6.1 /sys/kernel/debug/wakeup_sources:第一手现场
这个节点,是我每次排查问题的第一站。它就像系统的「黑匣子」,记录着所有Wakeup源的实时状态。
核心价值:不用改代码,不用加日志,直接cat就能看到谁在作妖。
// 查看当前所有wakeup源的状态
adb shell cat /sys/kernel/debug/wakeup_sources
// 输出示例(我简化了字段)
name active_count event_count wakeup_count expire_count
alarmtimer 12 45 8 0
msm_dwc3 3 12 3 0
qpnp_pon 1 2 1 0
pm8xxx_rtc_alarm 5 18 5 0
每个字段什么意思?我简单解释下:
- name:wakeup源的名字,一看就知道是谁
- active_count:这个源被激活的次数
- event_count:产生的事件总数
- wakeup_count:真正把系统唤醒的次数(这个最关键)
- expire_count:超时次数(一般不用管)
我的小技巧:重点关注wakeup_count和event_count的比值。如果event_count很高但wakeup_count很低,说明这个源虽然频繁触发,但系统大部分时间都在浅睡眠,问题不大。反过来,wakeup_count高就麻烦了。
我曾经遇到一个案子,手机一晚上wakeup_count飙到3000多次。一查,是某个第三方输入法的alarmtimer在疯狂设闹钟。嗯,这就是典型的「流氓应用」。
6.2 dmesg日志分析:时间线上的罪证
光看静态数据不够,我们还得看动态过程。dmesg里记录了每次系统 suspend/resume 的完整轨迹。
// 抓取suspend/resume相关日志
adb shell dmesg | grep -E "suspend|resume|PM:|wakeup"
// 典型输出
[ 123.456] PM: suspend entry 2024-01-15 22:00:00 UTC
[ 123.789] PM: Syncing filesystems ... done.
[ 124.012] PM: Freezing user space processes ... done.
[ 124.234] PM: Suspending console ... done.
[ 124.456] PM: suspend devices start
[ 124.678] msm_dwc3: wakeup event detected
[ 124.679] PM: resume devices start
[ 124.680] PM: resume from suspend
你看这个日志,从suspend entry到resume,只用了不到1毫秒。这说明系统刚睡下去就被唤醒了。谁干的?msm_dwc3——USB控制器。
避坑指南:我曾经被dmesg的时间戳坑过。有些内核版本的时间戳是系统启动后的秒数,不是绝对时间。你得先确认系统启动时间,才能换算成实际时间点。不然你看到「凌晨3点」的日志,其实是下午3点,那就白忙活了。
我个人习惯用这个命令组合:
// 只看最近一次suspend/resume的完整流程
adb shell dmesg | tail -n 200 | grep -E "PM:|wakeup|suspend|resume"
为什么要看最近200行?因为系统可能已经suspend/resume了很多次,太老的日志没意义。只看最近一次,定位最快。
6.3 trace event追踪:显微镜级别的分析
前两种方法够用吗?大部分时候够。但遇到疑难杂症,比如「系统偶尔睡不下去,但复现概率很低」,就得请出trace event了。
trace event能记录内核里每个函数的调用链,精确到微秒级。说白了,就是给系统做「心电图」。
// 开启wakeup相关trace
adb shell echo 1 > /sys/kernel/debug/tracing/events/power/suspend_resume/enable
adb shell echo 1 > /sys/kernel/debug/tracing/events/power/wakeup_source_activate/enable
adb shell echo 1 > /sys/kernel/debug/tracing/events/power/wakeup_source_deactivate/enable
// 抓取trace
adb shell cat /sys/kernel/debug/tracing/trace > trace.txt
// 输出示例
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
swapper/0-0 [000] ... 123.456: wakeup_source_activate: alarmtimer state=0
kworker/0:1-45 [000] ... 123.457: wakeup_source_deactivate: alarmtimer state=1
swapper/0-0 [000] ... 123.458: suspend_resume: suspend start
swapper/0-0 [000] ... 123.459: suspend_resume: suspend exit
关键点:看wakeup_source_activate和deactivate之间的时间差。如果activate后很久才deactivate,说明这个wakeup源一直没释放,系统就睡不下去。
我记得有一次,客户反馈手机偶尔睡死。用trace一抓,发现某个GPIO的wakeup源activate后deactivate等了整整2秒。查驱动代码,原来是中断处理函数里做了个msleep(2000)。嗯,这种低级错误,写代码的人自己都没意识到。
6.4 三招联用:实战流程
光说不练假把式。我总结了一个实战流程,你照着做就行:
- 第一轮:cat wakeup_sources,看wakeup_count最高的几个源
- 第二轮:dmesg抓最近一次suspend/resume,确认是哪个设备在搞鬼
- 第三轮:如果前两轮定位不准,开trace event,看函数调用链
这三步走下来,90%的问题都能找到根因。剩下的10%,嗯,那就要看硬件原理图了——可能是硬件设计问题,软件怎么调都没用。
我的经验:别一上来就开trace。trace日志量很大,抓一次可能几十MB,分析起来也费劲。先用前两种方法快速定位,实在不行再上trace。这就跟看病一样,先望闻问切,不行再拍CT。
6.5 知识体系图
下面这张图,把三种调试方法的关系和适用场景画清楚了:
6.6 避坑总结
最后,我把自己踩过的坑列出来,你遇到了直接对照:
| 问题现象 | 可能原因 | 推荐调试方法 |
|---|---|---|
| wakeup_count持续增长 | 某个源频繁唤醒系统 | wakeup_sources节点 |
| 系统睡下去马上醒来 | suspend流程中被中断 | dmesg看resume触发点 |
| 偶尔睡死,概率低 | 驱动中持锁或延时 | trace event看调用链 |
| 待机功耗异常高 | 多个源交替唤醒 | 三招联用,综合分析 |
重要提醒:调试wakeup源时,记得先关掉USB连接。因为USB插上时,系统根本不会深度睡眠。我见过太多人插着USB线调了一整天,最后发现所有数据都是假的——系统根本没进suspend。
好了,这一章的内容就到这里。调试wakeup源,说白了就是跟时间赛跑——谁先找到问题,谁就能早点下班。希望这三板斧能帮你少走弯路。