019驱动调试与性能优化:printk、动态调试、ftrace、perf工具链
从一次诡异的I2C超时说起上周排查一个车载IVI系统的触摸屏失灵问题现象是冷启动后触摸完全无响应但系统日志里没有任何错误信息。用逻辑分析仪抓I2C波形发现主机发了START信号后SCL就被拉低了——典型的从设备忙状态。但驱动代码里对应的错误分支根本没打印日志因为开发阶段为了“性能”把很多调试信息都关掉了。这种场景咱们都遇到过线上问题发生了日志却干干净净。今天就来聊聊嵌入式Linux驱动调试的那些事儿怎么在需要的时候拿到足够的信息又不在平时拖慢系统。printk最原始也最可靠先别瞧不起printk关键时刻能救命的往往是它。但很多人用printk的方式有问题// 别这样写级别不对查日志能累死printk(i2c transfer error\n);// 也别这样生产环境刷屏会被运维追杀printk(KERN_DEBUGaddr 0x%x reg 0x%x value 0x%x\n,addr,reg,val);// 建议这样分级控制dev_err(client-dev,i2c transfer timeout, slave 0x%x\n,client-addr);dev_dbg(client-dev,write reg 0x%02x 0x%02x\n,reg,val);printk的级别从0紧急到7调试是个好东西。通过/proc/sys/kernel/printk可以动态调整控制台输出级别比如线上环境设成4只显示警告以上信息调试时改成7什么细节都看得见。这里踩过坑某些嵌入式系统串口速度慢大量printk会拖慢启动过程。这时候可以用pr_cont()合并多行输出减少串口中断关键路径上使用printk_once系列宏实在要性能就用printk_deferred但注意可能丢失时序信息动态调试不用重新编译的魔法改代码-编译-烧写-重启这个循环太折磨人了。动态调试Dynamic Debug就是为此而生// 代码里正常加调试语句dev_dbg(dev,probe called, irq%d\n,irq);// 运行时动态控制echofile i2c-*.c p/sys/kernel/debug/dynamic_debug/control echomodule mydriver func probe -p/sys/kernel/debug/dynamic_debug/controlp是打开打印-p是关闭还能用func限定函数名、line指定行号。更实用的是条件打印# 只在传输失败时打印echofile i2c-core.c p/sys/kernel/debug/dynamic_debug/controlechoformat timeout p/sys/kernel/debug/dynamic_debug/control动态调试依赖CONFIG_DYNAMIC_DEBUG配置现在主流内核都默认开启了。有个细节dev_dbg()只在开启DEBUG或设置了CONFIG_DYNAMIC_DEBUG时才有效所以驱动Makefile里最好加上ccflags-y -DDEBUGftrace内核的“示波器”printk是看结果ftrace是看过程。那次I2C超时问题最终就是用ftrace找到的元凶# 先确认可用跟踪点cat/sys/kernel/debug/tracing/available_events|grepi2c# 开启i2c相关跟踪echo1/sys/kernel/debug/tracing/events/i2c/enableecho1/sys/kernel/debug/tracing/events/irq/enable# 设置缓冲区大小防止丢数据echo50000/sys/kernel/debug/tracing/buffer_size_kb# 开始记录echo1/sys/kernel/debug/tracing/tracing_on# 触发问题后停止并查看echo0/sys/kernel/debug/tracing/tracing_oncat/sys/kernel/debug/tracing/trace/tmp/trace.log分析trace文件发现I2C中断处理函数执行时间长达20ms进一步追踪发现是某个驱动在中断里做了内存拷贝。这就是ftrace的价值——告诉你“发生了什么”而不仅仅是“结果是什么”。function_graph跟踪器更强大能显示函数调用关系和时间echofunction_graph/sys/kernel/debug/tracing/current_tracerecho100/sys/kernel/debug/tracing/max_graph_depth注意嵌入式设备内存有限trace缓冲区别开太大一般50MB足够。另外ARM平台可能要用trace-cmd工具因为有些嵌入式文件系统不支持debugfs。perf性能瓶颈的显微镜perf能告诉你“为什么慢”。车载系统上有个CAN总线数据处理延迟问题用perf定位的# 采样CPU使用情况perf record-g-p$(pidof can_app)-operf.data --sleep30# 嵌入式设备上采集PC上分析需要vmlinux符号scptarget:/perf.data.perf report-iperf.data--symfs/path/to/rootfs火焰图是更直观的方式perf script|./stackcollapse-perf.pl|./flamegraph.plflame.svg嵌入式环境用perf要注意内核配置需要CONFIG_PERF_EVENTS交叉编译perf工具时需要目标机的内核源码和编译配置某些SoC支持PMU性能监控单元能统计缓存命中率、分支预测失败等硬件事件组合拳实战内存泄漏排查物联网设备最怕内存泄漏跑几天就OOM。上周查的一个GPIO驱动泄漏# 先用ftrace监控kmalloc/kfreeecho1/sys/kernel/debug/tracing/events/kmem/kmalloc/enableecho1/sys/kernel/debug/tracing/events/kmem/kfree/enable# 同时用perf统计分配热点perf record-ekmem:kmalloc-g-okmem.data# 发现某个函数频繁分配加上动态调试echofile gpio-xxx.c p/sys/kernel/debug/dynamic_debug/control# 最终定位到中断下半部里漏了释放个人工具箱配置建议printk生产环境保持默认级别但关键错误路径必须用dev_err()这些信息要进系统日志syslog动态调试驱动里多写dev_dbg()成本几乎为零。线上通过脚本管理开关#!/bin/bash# debug_i2c.shechofile i2c-*.c p/sys/kernel/debug/dynamic_debug/control# 复现问题echofile i2c-*.c -p/sys/kernel/debug/dynamic_debug/controlftrace准备几个常用脚本放设备里。比如跟踪调度的#!/bin/bashecho0tracing_onechonopcurrent_tracerecho10000buffer_size_kbecho1events/sched/enableecho1tracing_onperf交叉编译好perf连同依赖的libelf一起放到目标板。采集数据尽量用--call-graph dwarfARM平台栈回溯更准确。最朴素的招数有时候/proc/interrupts、/proc/vmstat、/proc/slabinfo比高级工具更直接。那个I2C问题其实看/proc/interrupts发现中断次数异常增长就已经指向了方向。调试就像破案工具是放大镜、指纹粉但最重要的是思路。先想清楚“可能是什么”再用工具验证别一头扎进数据里。嵌入式系统资源紧张现场可能只能保留一种工具的输出这时候选哪个工具就看你对问题性质的预判了。记住最好的调试是预防性的代码结构——关键路径加注释、错误处理要完整、数据结构要可追溯。真出了问题这些“代码习惯”比任何工具都管用。