27、系统稳定性分析:crash dump、kernel panic、watchdog

做嵌入式Android开发,最怕什么?

不是功能做不完,而是系统突然挂了。设备跑着跑着,黑屏了、重启了、或者干脆死在那不动了。用户骂娘,老板拍桌子,而你连问题出在哪都不知道。

我入行第三年,遇到过一台POS机,每天下午三点准时重启。排查了整整两周,最后发现是某个外设驱动在特定温度下触发了内核panic。嗯,从那以后,我对系统稳定性分析这块就格外上心。

今天咱们就来聊聊,当系统崩溃时,我们该怎么分析、怎么定位、怎么修复。

一、系统崩溃的三种典型形态

嵌入式Android系统崩溃,说白了就三种情况:

  • 应用层Crash:App挂了,但系统还在跑
  • 内核层Kernel Panic:内核崩了,整个系统完蛋
  • 硬件级Watchdog复位:系统卡死,看门狗咬人

这三种情况,严重程度依次递增。应用层Crash最多影响一个App,Kernel Panic直接让系统重启,而Watchdog复位——你连日志都来不及抓。

核心观点:系统稳定性分析,本质上就是回答三个问题——谁死了?怎么死的?为什么死?

二、Crash Dump:应用层崩溃的现场还原

应用层Crash,Android系统会生成一份tombstone文件。这玩意儿就像法医的尸检报告,记录了崩溃时的所有现场信息。

我个人习惯,拿到tombstone先看三样东西:

  1. 信号类型:SIGSEGV(段错误)、SIGABRT(主动终止)、SIGFPE(浮点异常)
  2. 崩溃地址:PC指针指向的地址,以及附近的指令
  3. 调用栈:从崩溃点一路回溯到入口函数

举个例子,我曾经遇到一个诡异的问题:App在播放视频时偶尔闪退。tombstone里显示的是SIGSEGV,崩溃地址在libstagefright.so里。我一看调用栈,发现是某个解码器在释放资源时访问了空指针。

// 典型的tombstone片段
pid: 12345, tid: 12346, name: VideoThread
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    r0 00000000  r1 00000001  r2 00000000  r3 00000000
    r4 b8c4e000  r5 00000000  r6 b8c4e000  r7 00000000
    r8 00000000  r9 00000000  sl 00000000  fp 00000000
    ip 00000000  sp bf8e5d80  lr b6f2a3b4  pc b6f2a3b8  cpsr 00000030

backtrace:
    #00 pc 0001a3b8  /system/lib/libstagefright.so (OMXCodec::freeBuffer+12)
    #01 pc 0001a5c0  /system/lib/libstagefright.so (OMXCodec::stop+36)
    #02 pc 0001a7d4  /system/lib/libstagefright.so (OMXCodec::~OMXCodec+20)

小技巧:用addr2line工具可以把地址反解成源码行号。比如:arm-linux-androideabi-addr2line -e libstagefright.so 0001a3b8,直接定位到freeBuffer函数的第12行。

三、Kernel Panic:内核崩溃的深度分析

Kernel Panic比应用层Crash严重得多。内核一崩,整个系统就停了,所有用户态进程全部陪葬。

你想想看,内核是操作系统的核心,它管理着内存、进程、设备驱动。如果内核自己挂了,那谁来收拾残局?

Kernel Panic的日志通常通过串口输出,或者保存在pstore/ramoops里。我建议你重点关注这几个字段:

字段 含义 常见原因
Kernel panic - not syncing 内核主动触发panic 驱动BUG、内存越界、死锁
Unable to handle kernel NULL pointer dereference 内核访问了空指针 驱动中未检查指针有效性
BUG: soft lockup CPU长时间被占用 中断处理函数死循环
Out of memory 内存耗尽 内存泄漏、配置不当

我记得有一次,客户反馈设备在运行12小时后必死。我通过串口抓到了Kernel Panic日志,发现是某个WiFi驱动在卸载时释放了已经释放过的内存。说白了,就是double free。

// Kernel Panic日志示例
[  123.456789] Unable to handle kernel NULL pointer dereference at virtual address 00000004
[  123.456790] pgd = c0004000
[  123.456791] [00000004] *pgd=00000000
[  123.456792] Internal error: Oops: 5 [#1] PREEMPT SMP ARM
[  123.456793] Modules linked in: wlan(O) [last unloaded: wlan]
[  123.456794] CPU: 0 PID: 1234 Comm: kworker/u:2 Tainted: G        W  O  3.10.73
[  123.456795] PC is at wlan_cleanup+0x14/0x30 [wlan]
[  123.456796] LR is at wlan_remove+0x28/0x3c [wlan]

注意:Kernel Panic日志里,PC指针指向的地址和模块名是关键。如果模块名后面有[last unloaded: wlan],说明问题出在驱动卸载时。这个细节我当年花了三天才搞明白。

四、Watchdog:最后的防线

Watchdog是什么?说白了就是一个硬件定时器。系统正常运行时,会定期"喂狗"——重置定时器。如果系统卡死了,没人喂狗,定时器溢出,硬件直接复位整个系统。

Watchdog复位是最难排查的问题,因为复位后所有内存数据都丢了,日志也抓不到。你只能看到系统重启了,但不知道为什么重启。

我建议你从这几个方向入手:

  • 检查硬件看门狗配置:超时时间设置是否合理?太短容易误触发,太长又起不到保护作用
  • 分析系统卡死点:通过pstore/ramoops保存最后一段内核日志
  • 排查中断关闭场景:如果某个驱动长时间关闭中断,看门狗就喂不了
  • 关注死锁问题:两个线程互相等待锁,谁也跑不动

我曾经遇到一个案例:设备在低温环境下频繁Watchdog复位。排查后发现,是某个LCD驱动在初始化时,因为温度低导致硬件响应慢,驱动在等待寄存器状态时进入了死循环。嗯,这个问题最后靠加了一个超时机制解决的。

避坑指南:我曾经在项目中遇到Watchdog复位,查了三天没头绪。后来发现是内核的RCU(Read-Copy Update)机制出了问题,某个CPU核长时间处于禁止抢占状态。建议你在内核配置中开启CONFIG_RCU_CPU_STALL_TIMEOUT,这样RCU卡住时会打印告警。

五、知识体系总览

下面这张图,是我自己总结的系统稳定性分析知识体系。你把它存下来,遇到问题对着查就行。

系统稳定性分析知识体系 Crash Dump Kernel Panic Watchdog tombstone文件分析 信号类型与调用栈 addr2line反解源码 Oops信息解读 PC指针与模块定位 pstore/ramoops日志 硬件定时器配置 系统卡死点排查 中断关闭与死锁 核心方法论:谁死了?怎么死的?为什么死? 从日志到源码,从现象到根因

六、实战建议

说了这么多,最后给你几条实在的建议:

  1. 日志要留好:生产环境的设备,一定要开启pstore/ramoops,否则Watchdog复位后你什么都查不到
  2. 复现要稳定:稳定性问题最难的就是复现。我习惯写一个压力测试脚本,让设备在高负载下运行,加速问题暴露
  3. 工具要趁手:addr2line、gdb、trace32这些工具要熟练。别等到出问题了才去查文档
  4. 心态要稳:系统稳定性问题往往很隐蔽,可能查一周都找不到原因。别急,一步一步来,从日志到源码,从现象到根因

最后说一句:系统稳定性不是靠测试测出来的,而是靠设计保证的。写驱动时多检查一次指针,多考虑一种异常情况,后面就能少熬几个通宵。这个道理,我是在被Watchdog折磨了整整一周后才真正明白的。


公众号:蓝海资料掘金营,微信deep3321