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 三招联用:实战流程

光说不练假把式。我总结了一个实战流程,你照着做就行:

  1. 第一轮:cat wakeup_sources,看wakeup_count最高的几个源
  2. 第二轮:dmesg抓最近一次suspend/resume,确认是哪个设备在搞鬼
  3. 第三轮:如果前两轮定位不准,开trace event,看函数调用链

这三步走下来,90%的问题都能找到根因。剩下的10%,嗯,那就要看硬件原理图了——可能是硬件设计问题,软件怎么调都没用。

我的经验:别一上来就开trace。trace日志量很大,抓一次可能几十MB,分析起来也费劲。先用前两种方法快速定位,实在不行再上trace。这就跟看病一样,先望闻问切,不行再拍CT。

6.5 知识体系图

下面这张图,把三种调试方法的关系和适用场景画清楚了:

Wakeup源调试三板斧 第一板斧 wakeup_sources节点 第二板斧 dmesg日志分析 第三板斧 trace event追踪 使用顺序:先简单后复杂,逐步深入 特点 • 实时查看所有源状态 • 关注wakeup_count • 快速定位高频源 • 适合第一轮排查 • 无法看调用链 • 无时间线细节 特点 • 查看suspend/resume流程 • 有时间戳和顺序 • 确认具体设备 • 适合第二轮排查 • 日志量大 • 需要过滤关键词 特点 • 微秒级精度 • 完整函数调用链 • 定位深层原因 • 适合疑难杂症 • 数据量巨大 • 分析门槛高

6.6 避坑总结

最后,我把自己踩过的坑列出来,你遇到了直接对照:

问题现象 可能原因 推荐调试方法
wakeup_count持续增长 某个源频繁唤醒系统 wakeup_sources节点
系统睡下去马上醒来 suspend流程中被中断 dmesg看resume触发点
偶尔睡死,概率低 驱动中持锁或延时 trace event看调用链
待机功耗异常高 多个源交替唤醒 三招联用,综合分析

重要提醒:调试wakeup源时,记得先关掉USB连接。因为USB插上时,系统根本不会深度睡眠。我见过太多人插着USB线调了一整天,最后发现所有数据都是假的——系统根本没进suspend。

好了,这一章的内容就到这里。调试wakeup源,说白了就是跟时间赛跑——谁先找到问题,谁就能早点下班。希望这三板斧能帮你少走弯路。