Skip to content

第一百五十三章:设备调试

合道期

涉及内核源码:

林小源走进设备山脉的一间暗室。暗室的墙壁上挂满了闪烁的指示灯——有些是绿色的,表示正常;有些是红色的,表示错误;还有一些是黄色的,表示可疑。

一个穿着灰色工装的老人坐在暗室中央,面前摆着一排排精密的检测仪器。他的手指上沾满了油污,眼睛布满血丝——显然已经在这里调试了很久。

"我是调试者。"老人抬起头,"驱动开发中最痛苦的不是写代码——是找 bug。用户空间的程序崩溃了,最多重启进程。驱动崩溃了,整个系统就挂了。"

他拿起一个检测仪器——上面刻着 "printk" 的字样。"这是最基本的调试工具。你往内核日志里打印消息,然后用 dmesg 查看。简单、可靠、不会让系统崩溃。"

林小源接过 printk 仪器,感受到它沉甸甸的分量。仪器上刻着不同的日志级别——KERN_EMERG、KERN_ERR、KERN_WARNING、KERN_INFO、KERN_DEBUG。

"日志级别很重要,"老人说,"KERN_ERR 会在控制台上显示,KERN_DEBUG 默认不显示——你得手动开启。选错级别,要么日志太多看不到关键信息,要么日志太少漏掉重要线索。"

林小源握紧了手中的 printk 仪器——调试这活儿,工具只是起步,真正的功夫在"看见"别人看不见的问题。


c
/*
 * 驱动调试工具:
 *
 * 1. printk
 *    内核日志
 *    不同级别: KERN_INFO, KERN_ERR
 *
 * 2. dev_dbg/dev_err
 *    设备相关的日志
 *    包含设备名
 *
 * 3. ftrace
 *    函数跟踪
 *    跟踪函数调用
 *
 * 4. kprobes
 *    动态探针
 *    在任意函数上设置断点
 *
 * 5. /sys/kernel/debug/
 *    调试文件系统
 *    驱动可以注册调试信息
 *
 * 常见驱动 bug:
 *   竞态条件
 *   内存泄漏
 *   中断处理不当
 *   DMA 一致性问题
 */

printf("=== 设备调试 — 找到隐藏的 bug ===\n\n");

printf("驱动调试工具:\n\n");

printf("1. printk:\n");
printf("   内核日志\n");
printf("   printk(KERN_INFO \"msg\\n\");\n\n");

printf("2. dev_dbg/dev_err:\n");
printf("   设备相关的日志\n");
printf("   dev_err(&pdev->dev, \"error\\n\");\n\n");

printf("3. ftrace:\n");
printf("   函数跟踪\n");
printf("   跟踪函数调用\n\n");

printf("4. kprobes:\n");
printf("   动态探针\n");
printf("   在任意函数上设置断点\n\n");

printf("5. debugfs:\n");
printf("   /sys/kernel/debug/\n");
printf("   驱动注册调试信息\n\n");

printf("--- 常见驱动 bug ---\n");
printf("竞态条件:\n");
printf("  多个线程同时访问\n");
printf("  需要加锁\n\n");
printf("内存泄漏:\n");
printf("  分配了没有释放\n\n");
printf("中断处理不当:\n");
printf("  处理时间太长\n");
printf("  没有清除中断标志\n\n");
printf("DMA 一致性:\n");
printf("  缓存没有刷新\n\n");

printf("--- 调试技巧 ---\n");
printf("1. 使用 printk 跟踪执行路径\n");
printf("2. 检查错误返回值\n");
printf("3. 使用 lockdep 检测死锁\n");
printf("4. 使用 KASAN 检测内存错误\n");

老人从暗室的角落里搬出一台巨大的仪器——仪器的面板上布满了旋钮和按钮,最中央是一个屏幕,屏幕上显示着密密麻麻的函数调用轨迹。

"这是 ftrace——函数跟踪器。"老人拍了拍仪器的外壳,"它可以记录内核中每一个函数的调用——谁调用了谁,调用了多少次,每次花了多少时间。"

林小源凑近屏幕,看到一条条函数调用链从左到右排列。某个驱动的 probe 函数在中间断了——它调用了一个返回错误的函数,然后整个初始化流程就中断了。

"找到了,"老人指着断裂点,"这个驱动在 probe 里调用 request_irq() 失败了,但它没有检查返回值——直接继续往下走,结果访问了未初始化的硬件,系统就崩了。"

林小源看到屏幕上还标注了每个函数的执行时间。有些函数只用了几微秒,有些用了几百毫秒——那些慢的函数就是性能瓶颈。

"ftrace 不仅能调试,还能做性能分析,"老人说,"你可以在 /sys/kernel/debug/tracing/ 下配置跟踪规则——跟踪哪些函数、过滤哪些进程、输出到哪里。它是最强大的分析工具。"

林小源注意到仪器的侧面有一个小小的探针插孔——那是 kprobes 的接口。

"kprobes 是动态探针,"老人解释道,"你可以在运行时给任意内核函数打上断点——不需要重新编译。当函数被调用时,探针触发,你可以记录参数、返回值、调用栈。"

他盯着屏幕上那条断裂的调用链,心想:ftrace 这东西,调试和性能分析两头都能用,确实好使。

老人从工装口袋里掏出一张皱巴巴的纸——上面列着他在多年调试中总结的常见 bug 类型。

"驱动 bug 和用户空间 bug 不一样,"他说,"用户空间的 bug 最多让你的程序崩溃。驱动的 bug 可能导致整个系统死锁、数据损坏、甚至硬件损坏。"

他指着纸上的第一条:"竞态条件——这是最常见的。两个 CPU 同时访问同一个数据结构,没有加锁,结果数据被破坏。你可以用 lockdep 工具检测锁的使用是否正确。"

第二条是内存泄漏:"驱动分配了内存但忘记释放——系统运行时间越长,可用内存越少。KASAN(Kernel Address Sanitizer)可以检测内存越界和释放后使用。"

第三条是中断处理不当:"中断处理函数不能睡眠、不能做太多工作。如果中断处理时间太长,系统就卡住了。正确做法是把工作放到下半部——tasklet 或 workqueue。"

第四条是 DMA 一致性:"DMA 操作绕过了 CPU 缓存。如果你没有在 DMA 传输前后刷新缓存,CPU 读到的可能是旧数据。使用 dma_map_single() 可以自动处理缓存一致性。"

林小源接过那张纸,仔细看了一遍。每一条 bug 后面都标注着对应的检测工具——printk、ftrace、kprobes、lockdep、KASAN、debugfs。这些工具就像修仙者的灵识一样,能探测到肉眼看不到的问题。

"调试是一门手艺,"老人最后说,"工具再好,也需要经验和直觉。有些 bug 只在特定条件下出现——高负载、多核并发、特定的硬件配置。你得学会制造复现条件,才能找到根因。"

林小源郑重地把纸折好收起。他知道,未来的修炼路上,这些调试工具将是他最可靠的伙伴。


道藏笔记

内核启示

驱动调试跟用户空间调试完全是两回事——用户空间程序崩了最多重启进程,驱动崩了整个系统都挂。最基本的工具是 printk,往内核日志里打印消息,用 dmesg 查看,简单可靠不会崩系统。dev_dbg/dev_err 带设备名,比 printk 更有针对性。ftrace 能记录内核中每个函数的调用链和执行时间,调试和性能分析都能用。kprobes 是动态探针,运行时给任意函数打断点,不需要重新编译。debugfs 让驱动注册调试信息。常见的驱动 bug 无非就那几类:竞态条件(多核同时访问)、内存泄漏(分配了没释放)、中断处理不当(处理时间太长)、DMA 一致性(缓存没刷新)。工具再好也需要经验和直觉,关键是学会制造复现条件。


破关试炼

设备调试之试

设备调试章中,运行时给任意内核函数打动态探针、不需重新编译的工具是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道