ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MTK平台看门狗超时与hang_detect机制全解析:从原理到实战

MTK平台看门狗超时与hang_detect机制全解析:从原理到实战 1. Hang_detect 到底在检测什么一次 WDT 超时背后的事件链我手里这台机器跑着跑着自己重启客户反馈一天能掉三四次看起来毫无规律。丢到实验室里开机第一件事就是查/proc/boot_reason结果指向 WDT看门狗超时。这个结果很多做 BSP 的兄弟都见过但说实话光知道是看门狗没用——看门狗只是那个最后拉电闸的人真正的问题在它超时之前就已经埋下了。幸运的是MTK 平台默认会把一套叫 hang_detect 的机制打开它能在看门狗真正复位之前把每个 CPU 的现场、当前线程、栈回溯和关中断状态全部记录下来。这篇文章就是围绕这套机制从原理、代码路径到实际调试把完整链路讲清楚顺便把我在项目里踩过的坑也一并列出来。做 Linux 内核、BSP、驱动或者系统稳定性的人都值得看一遍尤其是刚接手 MTK 项目、卡死问题不知道怎么下手的工程师。1.1 先搞清楚看门狗为什么会响看门狗这个东西本质上就是个倒计时器。你可以把它理解成家里炖汤时定的闹钟正常情况下你得定期去关掉闹钟重新再设置一次告诉它“我还活着不用报警”。内核里的 watchdog 驱动就是干这个的一个内核线程或者 tick 中断路径会周期性地去写 WDT 寄存器把计数器重新填满这个动作叫 kick也就是喂狗。问题来了一旦 CPU 发生死锁、中断被长时间关闭、调度器卡死、或者某个驱动在临界区里瞎忙活不松手喂狗路径就再也走不到了。硬件计数器一路减到零WDT 模块就会按照配置触发复位整机直接重启。这一段链条里最关键的问题是复位之前系统根本没机会告诉你“我卡在哪了”。所以很多裸奔平台的死机问题最后只能看到一次干干净净的重启现场被断电冲得一点痕迹都没有。MTK 做 hang_detect 这套东西目的就是把这个“没机会告诉你”变成“有证据地告诉你”。它不是单纯等计数器归零之后复位而是在超时触发的中断里先去把各个 CPU 正在执行的地址、栈、任务信息全部抓下来存到预先规划好的内存区域或者日志缓冲区里然后再让系统复位。用户下次开机时内核把这些信息从保留区域里恢复到/proc/last_log之类的节点工程师就能拿到一份“案发现场记录”。1.2 MTK 的“先取证、再复位”和普通看门狗的区别传统看门狗的工作模式非常单纯超时复位结束。这种模式适合做“最后一层保障”但它对问题定位几乎没有任何帮助。MTK 的 hang_detect 特殊之处在于它把看门狗分成了两段来看待第一段是“疑似挂死”触发中断第二段是“确认挂死”执行复位。在第一步和第二步之间系统有一小段极其宝贵的窗口专门用来抢救现场。实际实现上WDT 超时先进入中断处理函数这个中断里会做几件事先关闭本地中断、抢下当前 CPU 的寄存器上下文然后通过某种方式例如 IPI处理器间中断去通知其他 CPU 也停下来再逐一读取它们的 PC、LR、SP 和任务指针把信息拼成一段可读的文本最后写入预留的内存缓冲。等这套动作完成内核才调用平台相关的复位接口完成真正的硬件复位。这个设计考虑得很现实如果复位立刻发生DDR 里的数据大概率还没刷到能保存下来的位置等于白忙活。理解这个设计对后续调试有直接帮助。你在日志里看到“Hang detect trigger”或者“Watchdog timeout”之类的关键字别以为它就是根因它只是告诉你喂狗链条断了hang_detect 替你抓了一份现场。真正要分析的是现场里各 CPU 停在什么位置、当前在跑哪个线程、关中断状态如何。这些信息拼在一起才能还原出“卡死之前发生了什么”。1.3 抓现场时内核做了什么从中断到 dump 的完整链我在调试中总结下来的完整链路大概是这样喂狗路径失效WDT 计数器归零。WDT 硬件产生中断前提是软件配置了使用中断模式而不是超时直接复位。中断处理函数识别出这是看门狗超时进入 hang_detect 派发流程。当前 CPU 先保存自己的现场然后通过机制让其他在线 CPU 进入“冻结”状态。逐个 CPU 收集当前任务的 task_struct 地址、进程名、PID以及 PC 寄存器值。尝试打印每个 CPU 的调用栈同时记录中断状态、是否持有锁等信息。把整理好的文本写进 last log 缓冲区。调用复位相关接口真正的 WDT 复位发生。这一套流程在实现层面有不少细节差异不同内核版本、不同平台的处理路径不完全一样但整体思路一致。实践里经常遇到的一个问题是抓现场耗时太长写 log 才写一半硬件复位就强制发生了。所以有些项目会把 WDT 超时参数调得相对宽裕比如 30 到 60 秒目的就是给“抓现场”留足时间。后面章节我会专门讲怎么调这个参数以及调的时候要避开的坑。2. 内核里与 hang_detect 相关的代码路径从 config、dts 到驱动回调原理听明白了接下来就要去代码里找这套机制到底在哪。很多新人拿到一个 MTK 项目不知道该从哪个目录下手这里我把最常用的几个入口都列出来。2.1 先确认你的内核到底开没开这套机制不同内核版本MTK 的看门狗实现位置差别不小。老一些的版本Linux 3.18 / 4.4 / 4.9 时代的 MTK 内核经常能在mediatek/kernel/drivers/watchdog/下看到一堆以wd_或者mtk_wdt开头的文件相关的编译开关可能叫CONFIG_MTK_WATCHDOG、CONFIG_MTK_HANG_DETECT、CONFIG_MTK_LAST_LOG之类的新一点的内核比如 kernel-4.14、4.19、5.x基本收编进了标准 watchdog 框架路径一般是drivers/watchdog/mtk_wdt.c。如果仓库路径不太一样也别慌先执行一下find drivers -iname *wdt*或者grep -r mtk_wdt --include*.c总能找到。拿到代码之后第一件事不是读那一大堆寄存器操作而是先确认编译配置./scripts/config --file your_defconfig -g CONFIG_MTK_WATCHDOG ./scripts/config --file your_defconfig -g CONFIG_MTK_HANG_DETECT量产版本里这些配置经常是被裁剪掉的所以很多死机问题反馈回来只有一句“机器自己重启了”连 log 都没有。如果你要接手稳定性问题第一步就是让工程机或者实验构建把这两个选项打开把复现环境准备好。没有现场再厉害的分析手段也是空谈。提示CONFIG_MTK_HANG_DETECT在某些新平台里可能不叫这个名字可能被并入了 watchdog 框架的其他配置。确认的方法很简单在内核根目录执行grep -r HANG_DETECT arch/arm64/configs/和grep -r HANG_DETECT drivers/watchdog/看看有没有相关宏。2.2 设备树节点与超时参数的设置逻辑MTK 平台看门狗的设备树节点通常长这样不同平台地址有差异以实际仓库为准watchdog10007000 { compatible mediatek,mt6785-wdt; reg 0x10007000 0x1000; interrupts GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH; timeout-sec 30; status okay; };这个节点里最值得关注的是timeout-sec。它直接决定从喂狗失败到 WDT 触发之间的窗口长度。想快速复现问题可以把它临时改小比如设成 5 或 10想把现场尽量留全就设大一点比如 60。但要注意这个值不是越大越好——如果系统已经死透了WDT 触发前你等得越久log 缓冲区被覆盖的风险越大而且现场信息可能在等待期间被其他中断进一步破坏。我一般的做法是复现阶段调短分析阶段调长拿到一次完整 dump 之后再调回正常值。interrupts那一行决定了 WDT 超时是走中断模式还是直接复位模式。这个中断必须正确打开如果中断号配错或者没配WDT 超时大概率直接硬件复位连 hang_detect 的现场抓取都会失效。所以配 dts 的时候不要只抄 pinctrl务必对着平台 TRM 核对中断号。2.3 驱动层的关键流程与喂狗实现drivers/watchdog/mtk_wdt.c里最重要的几个点我按阅读顺序排一下mtk_wdt_probe()初始化寄存器、注册中断、注册 watchdog 设备。这里会读取 dts 里的timeout-sec。mtk_wdt_start()/mtk_wdt_stop()使能或关闭 WDT 硬件。mtk_wdt_ping()喂狗函数向 WDT 寄存器写入特定序列让计数器重新满值。上层 watchdog 框架会周期性调用它。mtk_wdt_isr()WDT 中断处理函数。这里通常是 hang_detect 流程的入口。如果中断触发后需要走 soft reset / panic 路径也会在这里做分支判断。mtk_wdt_restart()真正复位整个 SoC 的地方一般会先写一个复位原因寄存器再触发硬件复位。从调试角度我建议在mtk_wdt_isr()里打一条 printk哪怕只是打印一下“进入 WDT 中断”也能帮你在 last log 里快速确认触发点。有些版本默认已经有类似打印但真要排查问题时多一点现场标记没坏处。加 log 的位置不要太靠后要放在关中断之后、抢锁之前因为后面一旦抢锁失败可能就打印不出来了。static irqreturn_t mtk_wdt_isr(int irq, void *arg) { struct mtk_wdt_dev *wdt arg; /* 打印要尽量简单不要在这个上下文里做复杂格式化 */ pr_emerg(mtk_wdt: watchdog timeout, dump all cpu info\n); /* 触发 hang_detect 或 panic 路径 */ ... return IRQ_HANDLED; }2.4 运行期的调试入口watchdog 接口与 debugfs正常系统起来之后还会暴露一些用户态接口调试时很有用。/dev/watchdog是标准的 watchdog 设备节点应用层可以打开它周期性写入任意字节来喂狗相当于把喂狗责任从内核线程转移到了用户态进程。很多产品会开一个单独的守护进程来做这件事。如果这个守护进程卡死而不再写数据WDT 照样会触发——这类问题在量产机上很常见排查时别光盯着内核还要看用户态喂狗线程是否还活着。/sys/class/watchdog/watchdog0/下面有几个属性文件比如timeout、bootstatus、state。读取它们能快速确认当前超时时间和工作状态adb shell cat /sys/class/watchdog/watchdog0/timeout adb shell cat /sys/class/watchdog/watchdog0/bootstatusbootstatus能告诉你上一次重启是不是 WDT 引起的非常关键。不过要注意有些平台这个值在 RAS 或 bootloader 阶段就被读走了需要在 bootloader 刚启动时就抓否则拿到的可能已经被清零。另外部分内核支持通过debugfs暴露看门狗内部状态例如mount -t debugfs none /d之后去/d/watchdog/下面看有没有额外信息不同平台差异较大但对排查很有帮助。3. 死机之后的第一现场读取和解析 dump 信息流机制和代码都摸清了现在进入正题机器死了一次重启回来了你怎么把现场拿到手并且把现场信息读明白。这一部分是整个调试流程里最考验手感的环节。3.1 第一步确认是不是 WDT 引发的重启开机后先看重启原因命令特简单adb shell cat /proc/boot_reason这个节点读出来的值在 MTK 平台上有统一含义的“大方向”但不同芯片的具体掩码可能会有差异。常见的有软件复位、WDT 复位、RTC 复位、按键开机、充电开机等。如果你的值确实指向 WDT那就说明这次的死机大概率是“内核假死”导致的而不是掉电、欠压或者物理复位。到这里你就能判断值得继续往 hang_detect 方向找。如果没有这个节点别急可以从 bootloader 日志里看。MTK 的 lkLittle Kernel阶段一般会记录上次复位原因串口按住音量键进入 bootloader 模式或者直接抓dmesg开头部分也能看到类似reboot reason: watchdog之类的信息。再不行用 devmem 直接读平台手册里标明的 WDT 状态寄存器也能判断出触发源。这一步的目的是确认“方向”所以不用太纠结具体寄存器能拿到是 WDT 触发的结论就够了。3.2 第二步把 last log 完整捞出来MTK 平台保存死机现场的地方不止一处我按优先级排列如下/sys/fs/pstore/console-ramoops或/sys/fs/pstore/dmesg-ramoops-0这个是标准 pstore/ramoops 路径需要内核配置了CONFIG_PSTORE和CONFIG_PSTORE_RAM。/proc/last_logMTK 维护的上一份完整内核日志很多旧平台习惯用这个。/proc/last_kmsg在老内核版本上经常能看到。串口日志如果从死机前一段时间就接着串口那这段日志最完整因为它连最后几秒的打印都能保存。实际操作里我建议先把 pstore 挂载出来adb shell mkdir -p /sys/fs/pstore adb shell mount -t pstore pstore /sys/fs/pstore adb shell ls -l /sys/fs/pstore/ adb pull /sys/fs/pstore/如果上面这条路没有内容再去看/proc/last_log。注意这些节点通常只有 root 权限才能读到量产机可能需要先adb root。拿回来的文件不要急着搜 “WDT”先整体浏览一遍从机器最后一次打印开始按时间顺序往下捋。有很多崩溃其实在最后几次打印里已经露出了端倪比如某个外设一直报超时、某个线程反复调度失败只是后面的硬复位把一切都盖住了。3.3 第三步跳进 dump 现场定位关键字段一份典型的 hang_detect dump 长什么样关键词分散在几行里我总结一个通用模板[ 286.495502] mtk_wdt: watchdog timeout, dump all cpu info [ 286.495512] CPU0: pc 0xffffffc000123456, lr 0xffffffc000123460, sp 0xffffffc0f0012340 [ 286.495520] CPU0: task: 0xffffffc0e1220000, comm: kworker/u16:3, pid: 1234 [ 286.495529] CPU1: pc 0xffffffc000789abc, lr 0xffffffc000789ac0, sp 0xffffffc0f0021340 [ 286.495537] CPU1: task: 0xffffffc0e1330000, comm: mtk_i2c_dma_thread, pid: 1350这类打印虽然不是每个平台都完全一样但基本要素就几个CPUx哪个核。pc/lr正在执行的指令地址以及函数返回地址。comm/task当前在这个核上跑的任务名字和 task_struct 地址。之后的Call trace:或Backtrace:段落该 CPU 的栈回溯。分析的第一步是看每个 CPU 落在哪个函数附近。如果多个核都停在_raw_spin_lock或者do_raw_spin_lock那八成是死锁如果某个核停在一个驱动轮询函数里那要重点看是不是长时间关中断或者 IO 卡住。还要注意看comm有时候问题的核心不是 PC 值有多特殊而是“为什么这个线程能一直占着这个核”。3.4 第四步把地址还原成代码行拿到了pc地址还得让它变成你能看懂的函数名和行号。这一步要用到带符号的 vmlinux。对应版本的编译产物里vmlinux是带符号的完整内核映像通常在out/target/product/project/obj/KERNEL_OBJ/vmlinux或者内核编译目录下。解析命令aarch64-linux-gnu-addr2line -f -e vmlinux 0xffffffc000123456如果地址多了可以批量处理grep pc last_log | awk {print $4} | xargs aarch64-linux-gnu-addr2line -f -e vmlinux注意日志里打印出来的 PC 是链接地址一般不需要减偏移直接用就行。解析不出来先检查三件事vmlinux 和机器上的内核是不是同一个版本vmlinux 有没有被 strip 过日志地址是不是被 KASLR 加过偏移如果开了 KASLR还要从 dmesg 里找Kernel Offset。这几年我遇到最多的坑就是 vmlinux 拿错解析出来的行号全不对浪费了大半天。4. 常见卡死场景分类排查死锁、长临界区与低功耗挂死在 MTK 平台上我见过的高频卡死场景其实就那么几类。每一类都有比较典型的 dump 特征把特征记住了排查速度能快不少。4.1 spinlock 死锁dump 里最常见的一类“卡住”死锁的 dump 特征非常鲜明多个 CPU 都停在自旋锁相关函数上例如_raw_spin_lock、do_raw_spin_lock而持有锁的那个 CPU 却可能在正常执行也可能停在更深处。你会在日志里看到一堆BUG: spinlock lockup suspected on CPU#n之类的告警。但量产版本通常没开 lockdep所以更多时候你看到的就是几个核停在锁上没人告诉你锁是谁拿着的。这里给一个具体例子。我去年调过一个随机重启问题dump 里 CPU0 停在mtk_i2c_poll0x1c8/0x400CPU1 和 CPU2 都停在_raw_spin_lock。再往前翻日志看到最后几条是mtk_i2c: timeout, bus 3说明 I2C 总线已经发生超时。进一步查代码发现这颗 I2C 控制器驱动在spin_lock_irqsave临界区里执行轮询式读取一旦总线拉死它会在锁内反复重试把中断一直关着其他核全部变成自旋等待。问题根因就是“临界区里放了一个无超时保护的轮询读取”。修复方案是给轮询加总超时上限然后把耗时的读取逻辑挪到锁外面或者改用mutex 完成量机制。如果你是开发阶段强烈建议把CONFIG_PROVE_LOCKING和CONFIG_DEBUG_SPINLOCK打开。lockdep 能在死锁发生瞬间直接打印锁依赖环告诉你 A - B 和 B - A 两条路径省掉大量靠肉眼猜的时间。代价是性能和日志量所以只开在 debug 构建里就好。4.2 中断关闭时间过长被饿死的不是 CPU是喂狗线程这一类问题比死锁隐蔽。死锁至少多个核全卡在锁上一眼就能看出来长临界区问题往往只有一个核停在一个看起来“人畜无害”的函数里其他核看起来也正常但系统就是不往前走。其实喂狗路径很可能依赖高优先级中断或者 tick中断一旦被长时间关闭喂狗自然中断。这种问题在 dump 里的特征就是PC 停在__delay、udelay、readl_poll_timeout或者某个驱动的_poll函数里而且周围有大量轮询等待的循环感。排查思路不是盯着 PC 发呆而是回溯调用栈找到谁把local_irq_save或者spin_lock_irqsave给打开了还没关。从栈顶往下翻找到临界区的入口分析里面每一行代码的耗时。这类问题根因往往相似驱动作者图省事在锁里做了一次慢速设备读然后这个读还带重试。慢速设备可能是一个挂在 I2C 上的触摸屏、传感器、PMIC 寄存器单次读可能几十微秒但如果一直读不到 ACK重试几百次锁就压了几毫秒甚至几十毫秒这在系统层面已经是灾难了。我的建议是临界区里只允许做寄存器或内存操作任何可能依赖外部设备的 IO一律放到中断上下文之外。4.3 调度器卡死与 RT 任务饿死喂狗线程排不上队还有一个场景多核里只有一两个核一直跑同一个任务且这个任务优先级特别高。如果你在 dump 里看到某个 CPU 的comm始终是一个实时线程的名字而且这个线程的调度策略是 SCHED_FIFO那要高度怀疑是 RT 任务把某个核霸占了喂狗线程优先级不够一直排不上队。我遇到过的一个真实案例是一个音频 DSP 相关的内核线程被设成 SCHED_FIFO优先级 99里面有一个死循环等待某个硬件标志位。正常情况它等几十微秒就退出但那一次硬件状态异常标志位一直不置位这个线程就一直在循环里转CPU 被它占满系统里其他低优先级线程包括喂狗和 RCU 进程全部饿死。日志里出现了rcu_sched self-detected stall接着就是 WDT 触发。这种问题一看 dump 就能锁死只要看到同一个comm长时间占着 CPU先查它的优先级和调度策略。修复手段不是简单降低优先级而是要检查那个等待循环有没有超时保护不能让一个硬件异常状态把软件拖进死循环。开发阶段打开CONFIG_SCHED_DEBUG复现时抓/proc/sched_debug能非常清楚地看到每个 RT 任务的总运行时间占比。4.4 suspend/resume 低功耗流程挂死被时钟和电源拖死低功耗挂死很恶心因为它不一定每次都能复现而且多发生在刚合盖或者刚唤醒的瞬间。这类 dump 的特征通常是 PC 停在suspend_enter、cpuidle、clk framework相关函数或者停在某个外设驱动的suspend/resume回调里。排查这类问题我有一套固定动作先看 dump 里最后一条有意义的打印。比如PM: suspend entry ...之后什么都没有那问题基本就在 suspend 流程中途。用echo device /sys/power/pm_test这个文件能让你控制 suspend 在哪一步停下来。把pm_test依次设成firmware、platform、process、devices就能二分定位挂死的阶段。打开CONFIG_PM_DEBUG和CONFIG_PM_SLEEP_DEBUG这时 dmesg 里会打印每个 device 的 suspend/resume 顺序看到最后一个执行到哪个驱动就查哪个驱动。如果怀疑某个时钟或电源域没配对在对应驱动的 suspend 和 resume 里各加一条 printk对比两次流程中设备引用的时钟开关状态。低功耗挂死的根因里clk 依赖问题占大头。比如 A 设备 resume 时等待 B 设备的时钟但 B 设备还没被 resume于是 A 的等待函数永远等不来。这种问题在休眠框架里很常见最好的办法就是加超时保护别裸等。另外MTK 平台的 cpuidle 路径在调试版里很容易因为关核、总线空闲切换和 cache 维护出问题所以遇到 PC 在mtk_idle相关地址时优先确认是不是最近改过低功耗状态机的参数。4.5 常见问题的快速对照表现象特征最可能的根因方向首选动作多个 CPU 停在_raw_spin_lock/do_raw_spin_lockspinlock 死锁或锁持有者被饿死开 lockdep 查锁依赖链比对几个 CPU 的互斥点单核 PC 停在轮询/延时函数其他核正常长临界区内做了慢速 IO翻调用栈找临界区入口给轮询加超时某个核长期跑同一 RT 线程伴有 RCU stallRT 任务优先级过高或死循环查调度策略给等待循环加超时保护日志停在PM: suspend entry后无下文suspend 流程中某个设备回调卡死用 pm_test 二分定位打开 PM_DEBUG 查顺序/proc/boot_reason是 WDT 但 pstore 为空现场没抓到或 log 缓冲区太小确认中断模式配置和CONFIG_PSTORE调大timeout-sec这张表是我日常处理问题时的第一跳板。大多数死机问题跑不出这几类如果特征对得上基本能少走一半弯路。5. 调这个问题的工具链以及几个让我少走弯路的习惯最后一章说点实操向的东西。工具链和调试习惯决定了你是花一个下午还是花三天才能定位一个 hang 的问题。5.1 串口、earlycon 与日志级别先让现场能“看见”如果条件允许死机复现时务必接着串口观察。串口日志能看到死机前最后一秒发生了什么这是任何事后 dump 都无法提供的。MTK 平台开启串口主要是两个层面bootloaderlk阶段默认通常已经打开了 uart内核阶段要在 kernel cmdline 里加earlycon或consolettyMT0,115200之类的参数具体参数名看平台串口驱动。建议把内核常用打印级别调高echo 8 /proc/sys/kernel/printk这个命令在每次调试开始时都要执行因为默认级别可能只显示 error 以上很多关键流程打印被过滤掉了。另外串口抓 log 的工具首推 minicom 配合-C参数直接存文件或用script命令把终端输出保存下来。别只开着终端看不落盘回头想找细节找不到就尴尬了。5.2 WDT 超时参数怎么调从复现到收网我在 2.2 节说过timeout-sec直接影响现场完整度。实际调参我推荐按阶段来阶段timeout-sec 取值目的快速复现5 到 10 秒让问题尽快冒出来抓完整现场60 秒左右给 hang_detect 留足抓取和写 log 时间确认修复30 秒平衡复现速度和现场保留改 dts 之后需要重编 boot 分区并烧录。如果不想重烧部分平台支持在用户态通过/dev/watchdog控制超时但很多实现并不支持运行时改 timeout所以最稳的做法还是改 dts。实验阶段可以把timeout-sec调大配合CONFIG_PSTORE确保现场能落到 pstore 区域。这里必须提醒一个操作陷阱如果你在调试过程中故意打开/dev/watchdog来手动喂狗记得要周期性地写数据而且调试完要关掉它。以前我见过有人开了/dev/watchdog不喂机器就像中了邪一样每隔几十秒重启一次查了半天才发现是自己挖的坑。5.3 用 devmem 直接读 WDT 状态寄存器有些场景下系统连/proc/boot_reason都读不到但你又想知道上次复位是不是 WDT 触发的。这时候可以直接读 WDT 硬件寄存器。MTK 平台的 WDT 基址各不相同例如部分平台在0x10007000附近有些新平台在别的总线地址上务必以芯片手册和mtk_wdt.h头文件为准。找到状态寄存器偏移后用 busybox 读adb shell busybox devmem 0x10007000 32读出来的位域含义参考头文件里的宏定义。这个方法的好处是不依赖内核节点和文件系统只要机器能进系统、有 devmem 权限就能读。但它只能给你一个“是不是 WDT”的结论看不到现场细节属于兜底手段。5.4 开发阶段就该养成的几个代码习惯最后分享几个让我在排查中少踩坑的习惯这些不是书本知识全是项目里换来的教训。第一临界区里不要放依赖外设响应的等待。锁内 I/O、锁内轮询、锁内重试这三件事看起来能省事实际是 hang 的第一大来源。如果一定要等外设请把等待条件改成超时保护的方式或者干脆挪出锁。第二开发版开启 lockdep量产版关闭。不会因为多了一条打印就丢多少性能但它能帮你把死锁从“几个小时才复现”变成“一条日志直接指出”。我见过太多团队在 release 版上死磕死锁问题费时费力打开 debug 选项之后一小时就定位了。第三内核线程设置实时优先级要克制。SCHED_FIFO 优先级 99 意味着这个线程几乎可以独占 CPU除非业务必须否则别给到顶格。给 RT 线程加一个最大的循环次数或者超时保护能避免很多硬件异常导致的软件死循环。第四每次死机排查完把 last log 归档。我习惯建一个按问题现象命名的样本库比如“i2c_bus3_timeout_reboot.log”因为同一类底层问题往往会在不同项目中以不同的表象反复出现。以后再碰到类似现象先在这个库里搜一遍关键词经常能省掉一整天的时间。最后再分享一个小技巧拿到一个没有任何头绪的 hang 问题别急着去翻代码先把日志里所有带timeout、fail、error、stall、retry关键字的行全部提取出来再结合每个 CPU 的 PC 值做一个时间轴。很多时候问题的发生顺序比问题本身的报错信息更能说明真相。排查这类问题耐心比聪明重要方法比蛮力重要。
返回列表