拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

嵌入式Linux故障排查方法论:从现象到本质的破案思维

嵌入式Linux故障排查方法论:从现象到本质的破案思维

1. 为什么说嵌入式工程师都是柯南

干了八年嵌入式,我越来越觉得这行跟侦探没什么两样。一个Bug摆在面前,现象就是“尸体”,日志就是“现场痕迹”,你得从一堆看似无关的线索里,推理出真凶到底藏在哪一行代码、哪一个寄存器、甚至哪一根走线上。标题说“嵌入式工程师都是柯南”,真不是自嘲,是写实。

嵌入式开发和纯软件开发最大的区别在于:纯软出问题,你至少有完整的调用栈、有core dump、有断点随便打。嵌入式呢?板子跑飞了,串口可能都没输出,JTAG可能连不上,你手上只有一块发热的芯片和一个“昨天还好好的”的传说。这时候你靠什么?靠逻辑推理、靠经验直觉、靠对系统每一层的理解,一层一层缩小嫌疑范围,最后精准定位。

这篇文章我想聊的就是这套“破案”方法论。不管你是刚入行的嵌入式新人,还是干了几年还在跟Bug搏斗的老兵,我都会把我在Linux嵌入式开发中积累的故障分析思路、常用命令、排查套路,尽可能完整地摊开来讲。涉及Linux常用命令、内核日志分析、硬件接口调试、进程与内存问题定位这些核心场景,也会穿插一些真实的“破案”案例。目标很简单:让你下次面对一个诡异Bug时,不再靠玄学重启,而是有一套可复用的推理路径。

2. 破案前的现场保护与信息采集

2.1 第一反应决定破案效率

很多新手遇到问题的第一反应是“重启试试”。我理解这种冲动,但在嵌入式领域,重启等于破坏现场。你想想,柯南到了案发现场,第一件事是把所有东西复原一遍吗?当然不是,他先观察、先记录、先保护证据。

嵌入式系统跑飞之后,最宝贵的信息往往在易失性存储里——寄存器状态、内存残留、内核环形缓冲区里的最后几条日志。你一断电,这些全没了。所以我的习惯是:只要系统还能响应,哪怕只是部分响应,先别动电源,先把能抓的信息抓下来。

具体来说,我会按这个优先级采集信息:

  • 串口输出:如果串口还有输出,立刻打开终端软件开始记录,哪怕只是乱码也有价值。乱码本身可能说明波特率变了或者时钟出了问题。
  • 内核日志:如果系统还能登录,第一时间执行dmesg > /tmp/dmesg.log,把内核环形缓冲区的内容保存下来。这个缓冲区大小有限,新的日志会覆盖旧的,晚了就没了。
  • 进程状态:ps aux > /tmp/ps.log保存当前进程快照,看看有没有僵尸进程、有没有CPU占用异常的进程。
  • 内存信息:cat /proc/meminfo > /tmp/meminfo.log和cat /proc/slabinfo > /tmp/slabinfo.log,内存泄漏和内核对象泄漏的重要线索。
  • 网络状态:如果涉及网络,netstat -anp > /tmp/netstat.log和ip addr > /tmp/ip.log也一并保存。

这些操作加起来不超过三十秒,但可能就是破案的关键。我踩过的坑是:有一次系统偶发死机,我重启之后再也复现不了,后来才知道是某个驱动在特定时序下会踩内存,而那个时序跟开机时长有关。如果当时保留了现场,可能半天就定位了,结果花了两周才重新构造出触发条件。

2.2 日志系统的搭建与使用

说到信息采集,就不得不提日志系统。嵌入式Linux里最常见的日志方案是syslog或者rsyslog,但在资源受限的板子上,很多人直接用printk往串口打。这两种方式各有优劣,我的建议是分场景使用。

printk的好处是简单直接,不需要额外的守护进程,而且可以在系统还没完全启动起来的时候就用。坏处是日志级别混乱、没有时间戳(除非你配了)、输出量大了会阻塞系统。我一般会在驱动调试阶段大量使用printk,但产品化阶段会把它收敛到关键路径上。

syslog的好处是日志有级别、有时间戳、可以远程发送、可以轮转。坏处是需要配置,而且如果日志量太大,写flash会伤存储。我的做法是:在板子上挂一个tmpfs分区专门放日志,重启就丢,但运行期间可以随便写。需要持久化的关键日志再单独处理。

这里有个实操细节:printk的日志级别和syslog的级别不是一一对应的,很多人会搞混。printk的级别是0到7,数字越小优先级越高。KERN_EMERG是0,KERN_DEBUG是7。你可以在/proc/sys/kernel/printk里看到当前的控制台日志级别和默认日志级别。如果发现某些printk打不出来,先检查这个文件。

cat /proc/sys/kernel/printk # 输出类似:4 4 1 7 # 第一个数字是控制台日志级别,只有优先级高于这个值的才会打到控制台 # 第二个数字是默认消息日志级别 # 第三个是最低控制台日志级别 # 第四个是默认控制台日志级别

如果你想让所有printk都打到控制台,可以这样:

echo 8 > /proc/sys/kernel/printk

但注意,这会让日志量暴增,可能影响系统实时性。调试完记得改回去。

2.3 硬件层面的“不在场证明”

软件层面的信息采集很重要,但嵌入式工程师不能只盯着代码。很多时候,Bug的根源在硬件。电压不稳、时钟抖动、信号完整性差、温度漂移,这些都会表现为软件异常。所以破案的时候,硬件排查是必不可少的一环。

我一般会先确认几个基本问题:电源纹波是否在允许范围内?晶振频率是否准确?复位信号是否干净?这些用示波器一测就知道。如果手头没有示波器,至少用万用表量一下关键电压点。

还有一个容易被忽略的点:地线。我遇到过好几次系统偶发死机,最后发现是地线没接好,导致信号参考电平漂移。这种问题在实验室里可能不明显,一到现场就各种诡异现象。所以如果你的板子有多个接地点,确保它们都可靠连接。

另外,温度也是个大变量。有些芯片在低温下时序会变慢,高温下漏电流会增大。如果你的产品要在宽温范围内工作,高低温测试是必须的。我习惯在-40度和85度各跑一遍压力测试,很多常温下隐藏的问题会在这时候暴露出来。

3. 从现象到本质的推理链条

3.1 现象分类与初步判断

嵌入式系统的故障现象千奇百怪,但大致可以归为几类:系统完全不启动、启动到一半卡住、运行中死机、运行中重启、功能异常但系统还活着、性能下降。每一类现象对应的排查方向不同,先分类再动手,能省很多时间。

系统完全不启动:串口没有任何输出,电源指示灯可能亮也可能不亮。这种情况优先查硬件:供电是否正常、复位电路是否工作、启动模式引脚是否正确、晶振是否起振。如果硬件没问题,再考虑bootloader是否损坏。

启动到一半卡住:串口有输出,但停在某个位置不动了。这时候要看最后一条输出是什么。如果是内核启动阶段卡住,可能是设备树配置有问题、驱动初始化失败、根文件系统挂载失败。如果是用户空间启动卡住,可能是init脚本有问题、某个服务起不来。

运行中死机:系统突然不响应,串口也没输出。这种最麻烦,因为现场信息最少。我的经验是,先看门狗有没有触发。如果看门狗触发了,说明系统确实死了;如果没触发,可能是某个高优先级任务霸占了CPU,导致其他任务饿死。

运行中重启:系统自己重启了。先查重启原因寄存器,很多SoC都有这个功能,能告诉你上次重启是上电复位、看门狗复位、还是软件复位。然后查内核日志里有没有panic或者oops。

功能异常但系统还活着:比如网络不通、串口不收数据、某个外设不工作。这种相对好查,因为系统还在,你可以用各种工具去探测。

性能下降:系统变慢、响应延迟变大。这种通常是资源问题:CPU占用高、内存不足、IO瓶颈、中断风暴。

3.2 二分法与排除法实战

分类之后,下一步是缩小范围。我最常用的两种推理方法是二分法和排除法。

二分法的思路很简单:把系统分成两半,先确定问题在哪一半。比如系统启动卡住,你可以先确定是内核之前还是内核之后。怎么看?看串口输出。如果连bootloader的输出都没有,那问题在bootloader或者更早;如果有bootloader输出但没有内核输出,问题在内核加载阶段;如果有内核输出但没有用户空间输出,问题在内核初始化或者根文件系统。

排除法更适合功能异常的场景。比如网络不通,你可以按OSI模型从下往上排除:物理层(网线插了吗、PHY芯片工作吗)、数据链路层(MAC地址对吗、自协商成功吗)、网络层(IP配了吗、路由对吗)、传输层(端口监听了吗)、应用层(服务起来了吗)。一层一层排除,很快就能定位。

我举个真实的例子。有一次一块板子网口不通,我按这个思路查:先看PHY芯片的LED,发现link灯不亮,说明物理层就没通。换网线、换交换机都没用。后来用示波器量MDIO总线,发现时钟信号幅度不对,只有1.2V,正常应该是3.3V。查原理图发现MDIO的上拉电阻焊成了10K,而总线电容又比较大,导致上升沿太慢。换成1K之后问题解决。这个案例里,如果一上来就查TCP/IP协议栈,可能一天都查不出来。

3.3 时间线分析法

还有一种很有效的方法是时间线分析法。把故障发生前后的所有事件按时间顺序列出来,看看有没有相关性。比如系统每运行24小时就重启一次,那可能跟某个定时任务有关;比如每次插拔USB设备就死机,那可能跟USB驱动或者电源管理有关。

时间线分析的关键是精确的时间戳。如果你的日志没有时间戳,赶紧加上。内核日志可以用printk的KERN_DEBUG级别配合dmesg -T显示时间戳。用户空间日志可以用syslog的时间戳。如果精度不够,可以在关键路径上加ktime_get()或者clock_gettime()打点。

我遇到过一个案例:系统每隔一段时间就丢一帧串口数据。查了很久没头绪,后来把串口中断和系统定时器中断的时间戳都打出来,发现每次丢数据都发生在系统定时器中断处理时间超过某个阈值之后。原因是定时器中断处理里有一段代码关中断时间太长,导致串口中断被延迟,FIFO溢出。把那段代码优化之后问题解决。如果没有时间线分析,这种问题很难定位。

4. Linux嵌入式常用破案工具与命令

4.1 系统状态侦查命令

Linux给了我们很多现成的“侦查工具”,关键是要知道什么时候用什么。我按使用频率排个序,这些都是我几乎每天都会用到的。

dmesg是第一个要掌握的。内核的所有打印都在这里,包括驱动初始化、硬件探测、错误报告。常用参数:dmesg -T显示人类可读的时间戳,dmesg -l err只看错误级别,dmesg -w实时跟踪。

dmesg -T | tail -50 # 看最近50条带时间戳的内核日志 dmesg -l err,crit,alert # 只看错误及以上级别 dmesg -w # 实时监控内核日志

top和htop看CPU和内存占用。嵌入式板子上可能没有htop,但top一般都有。重点看几个指标:%CPU里sys和usr的比例,%MEM里哪个进程占得多,load average是否超过CPU核心数。

free看内存使用。注意buff/cache那一列,Linux会把空闲内存拿来做缓存,所以free少不代表内存不够。真正要看的是available那一列。

free -h # 关注 available 列,这才是应用程序真正能用的内存

vmstat看系统整体状态。vmstat 1每秒刷新一次,重点看r(运行队列长度)、b(阻塞进程数)、si/so(swap换入换出)、us/sy/id(CPU时间分布)。

iostat看IO状态。如果系统变慢,先排除是不是存储IO瓶颈。iostat -x 1看每个设备的%util和await。

netstat或ss看网络状态。ss -tulnp看所有监听的TCP/UDP端口和对应的进程。

lsof看文件打开情况。lsof -p <pid>看某个进程打开了哪些文件,lsof /dev/ttyS0看谁占用了串口。

4.2 进程与线程问题定位

进程相关的问题主要有几类:进程卡死、进程崩溃、进程占用资源过高、进程间通信异常。

进程卡死,先用ps看进程状态。D状态是不可中断睡眠,通常是等IO;R是运行;S是可中断睡眠;T是停止;Z是僵尸。如果是D状态,说明在等IO,可能是存储或者网络出了问题。如果是R状态但CPU占用不高,可能在等锁。

ps -eo pid,ppid,stat,pcpu,pmem,comm | grep -v "\[" # 查看所有进程的状态、CPU、内存

要看进程在干什么,用strace。strace -p <pid>附着到运行中的进程,看它正在执行什么系统调用。如果进程卡在某个系统调用上,一眼就能看出来。

strace -p 1234 -T -tt # -T 显示系统调用耗时,-tt 显示时间戳

如果strace不够,可以用gdb附着上去看调用栈。嵌入式板子上可能没有gdb,但可以在开发机上用交叉编译的gdb配合gdbserver。

# 板子上 gdbserver :1234 --attach <pid> # 开发机上 aarch64-linux-gnu-gdb (gdb) target remote <board_ip>:1234 (gdb) bt

线程问题用top -H -p <pid>看每个线程的CPU占用,或者ps -eLf看所有线程。如果某个线程CPU占用100%,用gdb附着上去看它在执行什么。

4.3 内存问题排查套路

内存问题是嵌入式Linux里最头疼的一类,因为现象往往很随机,而且定位困难。常见的内存问题有:内存泄漏、内存越界、内存碎片、OOM。

内存泄漏的排查思路:先确认是不是真的泄漏。用free看available内存是否持续下降,用cat /proc/meminfo看MemFree和MemAvailable的变化趋势。如果确认泄漏,再定位是哪个进程泄漏。

# 每隔一段时间记录一次内存信息 while true; do date >> /tmp/mem.log cat /proc/meminfo | grep -E "MemFree|MemAvailable|Slab|SReclaimable|SUnreclaim" >> /tmp/mem.log sleep 60 done

如果Slab里的SUnreclaim持续增长,说明内核对象泄漏,通常是驱动的问题。如果某个进程的RSS持续增长,那就是用户空间的内存泄漏。

用户空间内存泄漏可以用valgrind查,但嵌入式板子上跑valgrind比较重。轻量级的方法是mtrace,或者自己在malloc/free上加钩子。

内核内存泄漏用kmemleak。内核配置里打开CONFIG_DEBUG_KMEMLEAK,启动后echo scan > /sys/kernel/debug/kmemleak,然后cat /sys/kernel/debug/kmemleak看报告。

内存越界是最难查的,因为可能踩了别人的内存但当时不报错,过很久才出问题。KASAN(Kernel Address Sanitizer)是利器,但需要内核支持,而且性能开销大。用户空间可以用AddressSanitizer编译。

OOM(Out Of Memory)看内核日志里的oom-killer输出,它会告诉你哪个进程被杀了、当时内存什么情况。

dmesg | grep -i "oom\|out of memory"

4.4 硬件接口调试命令

嵌入式工程师经常要跟各种硬件接口打交道:I2C、SPI、UART、GPIO、MDIO。Linux给每个接口都提供了用户空间的操作方式,掌握这些命令能省很多事。

I2C用i2cdetect、i2cget、i2cset。先看总线上的设备地址:

i2cdetect -y -r 0 # 扫描I2C总线0上的设备 i2cget -y 0 0x50 0x00 # 读地址0x50的寄存器0x00 i2cset -y 0 0x50 0x00 0x12 # 写地址0x50的寄存器0x00为0x12

SPI用spidev接口,通常需要自己写个小程序或者用spi-tools。

UART用stty配置,echo和cat收发:

stty -F /dev/ttyS0 115200 cs8 -cstopb -parenb echo "test" > /dev/ttyS0 cat /dev/ttyS0

GPIO用sysfs或者gpiod:

# sysfs方式(旧) echo 12 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio12/direction echo 1 > /sys/class/gpio/gpio12/value cat /sys/class/gpio/gpio12/value # gpiod方式(新) gpiodetect gpioinfo gpiochip0 gpioset gpiochip0 12=1 gpioget gpiochip0 12

MDIO用mdio-tools或者直接操作寄存器。如果PHY不支持MDIO而用I2C控制,那就用I2C的命令去读写PHY寄存器。

5. 典型故障案例复盘

5.1 案例一:系统随机重启

这是我早期遇到的一个经典案例。一块基于ARM的板子,运行几个小时到几天不等就会重启。重启时间不固定,负载高的时候更容易出现。

现场保护:先查重启原因寄存器。SoC有个PMU寄存器记录上次复位原因,读出来是“看门狗复位”。说明系统确实死了,看门狗没被喂。

信息采集:打开内核的panic和oops打印,确保串口能收到。同时把看门狗的超时时间从默认的60秒改成120秒,给系统更多时间打印信息。

推理过程:看门狗复位说明系统在某个时刻完全卡死了。可能的原因:中断风暴、死锁、内存耗尽、硬件故障。先排除硬件,电源和温度都正常。然后看内核日志,发现死机前没有任何异常打印,说明不是软件主动panic。

二分法:把系统负载降低,只跑最基本的服务,发现还是重启。说明问题在底层,不在应用层。然后关掉所有非必要驱动,只留串口和看门狗,还是重启。最后怀疑是看门狗驱动本身的问题。

定位:查看门狗驱动代码,发现喂狗是在一个内核定时器里做的。定时器回调里有一段代码会调用msleep,而msleep在原子上下文里是不允许的,会导致调度异常。在某些时序下,这个异常会导致系统卡死,看门狗超时复位。

解决:把msleep改成mdelay,或者把喂狗放到工作队列里。改完之后跑了一周没再重启。

经验:看门狗复位不一定是系统真的死了,也可能是喂狗逻辑本身有问题。另外,内核定时器回调里不能睡眠,这是基本规则,但很容易被忽略。

5.2 案例二:串口丢数据

一块板子通过串口跟外设通信,偶尔丢一帧数据。丢数据的时间间隔不固定,有时候一天几次,有时候几天一次。

现场保护:在串口驱动里加统计,记录接收中断次数、FIFO溢出次数、帧错误次数。同时用逻辑分析仪抓串口波形。

信息采集:统计发现FIFO溢出次数在丢数据的时候会增加。逻辑分析仪显示外设发送的数据是完整的,但板子这边没收到。

推理过程:FIFO溢出说明中断响应不及时。可能的原因:中断被关闭时间太长、中断优先级太低、中断处理函数执行时间太长。

时间线分析:在串口中断和系统定时器中断里都打时间戳,发现每次丢数据都发生在系统定时器中断处理时间超过200微秒之后。正常情况这个中断处理应该在50微秒以内。

定位:查系统定时器中断处理代码,发现里面有一段循环在等某个硬件状态位,没有超时机制。在特定条件下这个循环会执行很久,导致关中断时间过长。

解决:给循环加上超时,超时后直接返回错误。同时把串口中断优先级提高。改完之后丢数据现象消失。

经验:中断处理函数要尽可能短,不能有不确定的等待。如果必须等待,一定要有超时。

5.3 案例三:内存泄漏导致OOM

一个网关设备,运行几天后内存耗尽,OOM killer杀掉关键进程,系统功能异常。

现场保护:在OOM发生前,系统已经变慢,这时候登录进去保存了/proc/meminfo、/proc/slabinfo、ps aux等信息。

信息采集:/proc/meminfo显示SUnreclaim持续增长,从几十MB涨到几百MB。/proc/slabinfo显示某个内核对象数量异常多。

推理过程:SUnreclaim增长说明内核对象泄漏。查slabinfo里增长最快的对象,发现是某个网络驱动的skb相关对象。

定位:查网络驱动代码,发现在错误处理路径上,有些skb没有被正确释放。正常路径没问题,但特定错误条件下会漏掉。

解决:修复错误处理路径,确保所有skb都被释放。同时加上kmemleak定期扫描,防止类似问题再次出现。

经验:内核对象泄漏比用户空间内存泄漏更隐蔽,因为free命令看到的used可能不高,但Slab里的SUnreclaim会暴露问题。定期监控slabinfo是个好习惯。

6. 破案高手的自我修养

6.1 建立自己的工具箱

嵌入式调试工具很多,但你不必每个都精通。我的建议是:先精通几个最常用的,再根据需要扩展。

必备工具清单:

  • 串口终端:minicom、picocom、screen,至少会一个。
  • 逻辑分析仪:Saleae或者便宜的山寨版都行,抓I2C、SPI、UART时序非常有用。
  • 示波器:看电源纹波、时钟质量、信号完整性。
  • 万用表:量电压、通断。
  • JTAG调试器:OpenOCD + GDB,能单步调试bootloader和内核。
  • 交叉编译工具链:根据你的SoC选,ARM的一般用arm-linux-gnueabihf-或者aarch64-linux-gnu-。

软件工具:

  • perf:性能分析,看热点函数。
  • ftrace:内核跟踪,看函数调用关系和耗时。
  • bpftrace:动态跟踪,功能强大但学习曲线陡。
  • valgrind:用户空间内存检查。
  • kmemleak:内核内存泄漏检查。

6.2 培养系统思维

嵌入式系统是一个整体,软件和硬件相互影响。破案的时候不能只盯着一个层面,要有系统思维。

比如一个I2C通信失败,可能的原因包括:I2C控制器驱动问题、I2C从设备问题、上拉电阻问题、总线电容问题、电源问题、时钟问题。你得从系统层面去考虑,而不是只查驱动代码。

我习惯画一张系统框图,把CPU、内存、存储、外设、电源、时钟都画出来,然后标出信号流向。出问题的时候,沿着信号流向一段一段查,很快就能定位。

6.3 记录与复盘

每次破案之后,我都会写一份复盘报告。内容包括:问题现象、排查过程、根本原因、解决方案、经验教训。这份报告不仅是给自己看的,也是给团队看的。下次遇到类似问题,直接翻报告就行。

复盘的时候要问自己几个问题:这个问题能不能提前发现?有没有更好的排查方法?有没有类似的隐患?怎么防止再次发生?

我还会维护一个“坑列表”,把所有踩过的坑记下来。比如“内核定时器回调不能睡眠”、“中断处理不能等硬件状态位”、“错误处理路径容易漏释放资源”。这些坑看起来简单,但在实际开发中很容易再犯。

6.4 保持好奇心与耐心

最后说点虚的,但很重要。嵌入式调试很多时候是枯燥的,你可能花几天时间就为了找一个空指针。这时候好奇心很重要:为什么这里会空?什么条件下会空?怎么构造这个条件?有了好奇心,你才能坚持下去。

耐心也很重要。有些Bug就是很隐蔽,你急也没用。我的经验是:遇到卡住的时候,先放一放,去喝杯水,或者跟同事聊聊。很多时候换个思路,问题就迎刃而解了。

还有一点:不要怕犯错。嵌入式开发里,犯错是常态。关键是从错误中学习,下次不再犯同样的错误。我干了这么多年,还是经常踩坑,但踩过的坑越来越少,这就是进步。

7. 一些实用的排查技巧与避坑指南

7.1 快速定位问题的几个窍门

窍门一:先看日志,再看代码。很多人一遇到问题就翻代码,这是效率最低的做法。日志里往往已经告诉你了问题在哪,只是你没注意。我习惯先把dmesg从头到尾看一遍,把错误和警告都标出来,然后再看代码。

窍门二:从最近改动的地方查起。如果问题是最近才出现的,那大概率是最近的改动引起的。用git log看最近提交,用git diff看改了什么。我遇到过好几次,查了半天发现是同事改了一行配置。

窍门三:用二分法缩小范围。如果不知道问题在哪,就把它一分为二。比如系统启动卡住,先确定是内核之前还是之后;如果是内核之后,再确定是驱动初始化还是根文件系统挂载。每次排除一半,很快就能定位。

窍门四:构造最小复现。如果问题能稳定复现,就尝试构造一个最小的复现环境。关掉所有不必要的功能,只保留触发问题的最小集合。这样不仅能加快排查速度,还能帮你理解问题的本质。

窍门五:善用搜索引擎。你遇到的问题,大概率别人也遇到过。把错误信息的关键部分复制到搜索引擎里,往往能找到答案。但要注意甄别,有些答案可能不适用于你的场景。

7.2 常见误区与避坑

误区一:重启解决一切。重启只是掩盖问题,不是解决问题。而且重启会破坏现场,让问题更难查。除非万不得已,不要重启。

误区二:只看应用层,不看内核。很多问题根源在内核或者驱动,应用层只是受害者。遇到问题要往下查,不要停留在表面。

误区三:忽略硬件。嵌入式工程师不能只懂软件,硬件也要懂。很多软件问题其实是硬件问题引起的。

误区四:不记录不复盘。踩过的坑不记录,下次还会踩。建立自己的知识库,比什么都重要。

误区五:过度依赖调试工具。工具是辅助,不是万能。有时候最简单的printk比复杂的工具更有效。

7.3 一些容易忽略的细节

细节一:时钟配置。很多外设问题其实是时钟配置不对。比如I2C速率不对、SPI时钟相位不对、UART波特率不对。查问题的时候先确认时钟。

细节二:引脚复用。SoC的引脚往往有多个功能,配置错了就会导致外设不工作。查原理图和设备树,确认引脚复用正确。

细节三:电源域。有些外设有独立的电源域,如果电源没打开,外设就不工作。查电源管理配置。

细节四:复位时序。有些外设需要特定的复位时序,复位时间不够或者顺序不对都会导致问题。查数据手册。

细节五:中断优先级。中断优先级配置不对会导致中断丢失或者响应延迟。查中断控制器配置。

细节六:缓存一致性。DMA和CPU共享内存的时候,缓存一致性是个大问题。该刷缓存的时候要刷,该无效化的时候要无效化。

细节七:字节序。不同架构的字节序可能不同,通信的时候要注意转换。

细节八:对齐。有些架构要求内存访问对齐,不对齐会触发异常。

这些细节看起来琐碎,但每一个都可能导致诡异的问题。我踩过的坑里,至少有一半是这些细节引起的。

8. 从柯南到福尔摩斯的进阶之路

8.1 从解决问题到预防问题

新手关注的是怎么解决问题,老手关注的是怎么预防问题。预防问题比解决问题更有价值,因为问题不发生,就没有损失。

预防问题的方法包括:代码审查、静态分析、单元测试、集成测试、压力测试、长时间老化测试。这些手段能在问题上线之前就把它找出来。

我现在的习惯是:每写一个驱动,都要写测试用例。测试用例覆盖正常路径和异常路径,确保每个分支都走到。虽然写测试花时间,但比上线之后出问题再查要划算得多。

8.2 从个人能力到团队能力

一个人再厉害,也干不过一个团队。把个人的破案经验沉淀成团队的排查手册,价值会放大很多倍。

我所在的团队有一个共享的Wiki,里面记录了所有踩过的坑、排查方法、工具使用技巧。新人进来先看Wiki,能少走很多弯路。每次破案之后,当事人负责更新Wiki,把新的经验加进去。

我们还定期做故障复盘会,把最近的故障拿出来大家一起分析。不仅分析技术原因,也分析流程原因。比如为什么这个问题没在测试阶段发现?测试用例是不是有遗漏?流程上怎么改进?

8.3 保持学习与更新

嵌入式技术更新很快,新的SoC、新的内核版本、新的工具层出不穷。保持学习是必须的。

我的学习渠道包括:内核邮件列表、技术博客、开源项目、技术会议。不一定要每个都深入,但要知道有什么新东西,需要的时候能快速上手。

另外,基础知识要扎实。操作系统原理、计算机体系结构、数字电路、信号与系统,这些基础课的东西在工作中会反复用到。基础扎实了,学新东西就快。

8.4 一些个人体会

干了这么多年嵌入式,我最大的体会是:耐心比聪明重要,方法比经验重要,记录比记忆重要。

耐心比聪明重要,因为嵌入式调试很多时候就是体力活,你得一遍一遍试,一遍一遍查。聪明人可能找到捷径,但耐心的人一定能找到答案。

方法比经验重要,因为经验会过时,但方法不会。掌握了系统的排查方法,遇到新问题也能应对。

记录比记忆重要,因为人脑记不住那么多细节。把踩过的坑、用过的方法记下来,需要的时候翻一翻,比什么都强。

最后,嵌入式工程师确实像柯南,但柯南也不是天生的。每一个诡异的Bug背后,都有一套可复用的推理方法。掌握了这套方法,你也能成为破案高手。

返回列表