返回列表 发布新帖
查看: 132|回复: 0

内核驱动日志调试避坑指南

988

主题

0

回帖

833

积分

高级会员

积分
833
发表于 昨天 12:00 | 查看全部 |阅读模式
内核驱动里打日志这件事,看起来是最简单的事,实际写久了会发现,它几乎决定了你排查问题时是“顺藤摸瓜”,还是“满地找针”。很多驱动问题并不是每次都能复现,尤其是时序、并发、中断、休眠唤醒这类场景,现场一旦错过,日志就是唯一证据。所以我一直觉得,驱动日志不是调试时临时加几行 printk,而是代码设计的一部分。

最基础的一点,是日志级别一定要分清。普通路径上的成功信息不要随手 pr_info 一路刷屏,否则真正出问题时,关键信息会被淹没。初始化成功、设备探测到这类信息可以保留少量;异常路径建议用 pr_err 或 dev_err;可疑但不一定致命的情况用 pr_warn;高频路径上的信息尽量放到 pr_debug、dev_dbg,配合动态调试打开。尤其是中断处理、数据收发、轮询线程里,日志一多,不仅影响性能,还可能改变原本的时序,最后调出来的是被日志“扰动”后的问题。

我个人更推荐在有 struct device 的地方优先使用 dev_err、dev_info、dev_dbg 这一类接口。原因很简单,它会自动带上设备上下文,比单纯 printk 友好得多。一个系统里可能挂了多个同类设备,如果日志里只写 “init failed”,后面查起来会很痛苦。类似总线号、地址、通道号、队列号这些,也要在关键日志里带上。日志不是写给机器看的,是写给三天后已经忘了细节的自己看的。

还有一个常见技巧是给关键路径设计统一前缀。比如某个驱动涉及 probe、open、ioctl、irq、suspend/resume,可以让日志格式保持一致:函数名、状态码、关键参数都固定位置输出。不要今天写 “ret=%d”,明天写 “failed %d”,后天又只写 “error”。格式统一之后,用 dmesg、journalctl 或脚本过滤时会省很多力气。

错误日志一定要包含返回值和必要现场。比如申请 GPIO 失败,只写 “request gpio failed” 价值有限,最好带上 gpio 编号、返回码、设备名。如果是寄存器读写失败,要带寄存器地址、期望值、实际值。内核里很多错误码本身已经很有信息量,漏掉 ret 等于把线索扔了一半。对于 probe 阶段尤其如此,因为资源申请链条长,一处失败可能由设备树、时钟、电源、pinctrl、reset 任意环节引起。

高频日志要学会限流。Linux 内核提供了 pr_err_ratelimited、dev_warn_ratelimited 这类接口,遇到硬件异常连续上报、IRQ 风暴、通信超时反复发生时非常有用。否则一秒钟刷几千行,轻则影响系统响应,重则把环形缓冲区冲掉,最开始的根因反而看不到了。对于只想确认某个分支是否进入的日志,也可以临时加计数器,每隔固定次数输出一次。

调试阶段可以善用 dynamic debug。把调试信息写成 dev_dbg/pr_debug,平时不会污染日志,需要时通过 debugfs 动态打开某个文件、某个函数的输出,比反复改代码、重新编译模块舒服很多。配合 ftrace、trace_printk、tracepoints 效果更好。这里要提醒一句,trace_printk 不适合长期留在正式代码里,它更像手术灯,不是客厅灯。

最后说个容易被忽略的点:日志要克制,但不能吝啬。克制是指不要在正常路径上制造噪声;不吝啬是指异常发生时要尽量一次性把上下文交代清楚。好的驱动日志应该像案发现场记录,时间、地点、人物、结果都有,而不是一句“出错了”。真正线上排障时,你会感谢当初那个多打印了寄存器值和错误码的自己。
回复 转播

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关灯 在本版发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表