跳到主要内容

8.1 锁调试概览

这里有一类 Bug,它们最擅长的把戏就是「隐身」。

当你只是盯着代码看时,一切看起来都完美无缺;当你试图用 printk 去捕捉它们时,它们因为输出的延时而神奇地消失了;甚至当你鼓起勇气用 GDB 单步调试时,它们会因为时序的改变而不再触发。这就是并发 Bug——内核开发领域最令人头疼的「海森堡 Bug」。

在这一章,我们要处理的就是这些幽灵。但在此之前,我们需要达成一个共识:并发编程不是靠「运气」的,它是靠严密规则的。我们要做的,是把那些藏在时序缝隙里的东西,放到台面上来。

这比想象中要难。难就难在,你需要在一个看不见、摸不着的多维时空里,建立一套绝对的秩序。


8.1 Technical requirements

环境准备与前置知识

在开始这场侦探游戏之前,我们先确认一下手里的工具箱。本章的环境要求和第 1 章完全一致——你需要一台配置好的 Linux 开发机,以及必要的交叉编译工具链。书中的代码示例依然会出现在 GitHub 仓库中(如果你还没克隆,现在是时候了):

https://github.com/PacktPublishing/Linux-Kernel-Debugging

此外,我们还会频繁引用我之前那本免费的电子书 Linux Kernel Programming – Part 2 的最后两章。为什么?因为那本书花了整整两章的篇幅,把锁的原理和调试工具掰碎了讲了一遍。如果你还没读过,或者对「临界区」、「自旋锁」、「互斥锁」这些词感到陌生,那么接下来的路可能会走得有点艰难。

为了确保我们都在同一个频道上,强烈建议你去拿一份 Linux Kernel Programming – Part 2 的副本。别担心,它是免费的——真的免费。

仓库地址在这里:

https://github.com/PacktPublishing/Linux-Kernel-Programming-Part-2

关于锁:我们假设你已经知道了什么

这本书的任务是「调试」,而不是「入门教程」。这意味着我们不会花费篇幅去解释什么是互斥锁,什么是自旋锁,或者原子操作是如何工作的。如果你现在正在纠结「为什么要加锁?」这个问题,那么请先暂停,去读一下 LKP - Part 2 的以下章节,那里有你需要的所有答案:

  • Chapter 6, Kernel Synchronization – Part 1:这是基础中的基础。它详细解释了什么是临界区,为什么内核空间需要关注并发,以及如何正确使用 mutexspinlock API。特别是关于锁副作用的那部分——比如 spin_lock_irq() 会关中断,如果你不知道这一点,你的系统可能会莫名其妙地死锁。
  • Chapter 7, Kernel Synchronization – Part 2:这里讲的是高级技巧,包括无锁技术和 Lockdep 的使用。Lockdep 是内核里最强大的锁验证工具,如果你还没听说过它,这一章会打开你的新世界大门。

这听起来像是在打广告,但并不是。这是因为内核的并发调试太依赖对底层机制的深刻理解了。如果你不清楚 spin_lockmutex 的本质区别,你就看不懂后续的 KCSAN 报告;如果你不知道 lockdep 的工作原理,你就不知道为什么它能提前发现死锁。

闲话少说,书在哪?

再次强调,这本 LKP-2 电子书是完全免费的。你可以直接从 GitHub 下载 PDF 版本,或者在 Amazon 上搞一本 Kindle 版(也是免费)。

链接在这里,别说我没告诉你:

https://github.com/PacktPublishing/Linux-Kernel-Programming/blob/master/Linux-Kernel-Programming-(Part-2)/Linux%20Kernel%20Programming%20Part%202%20-%20Char%20Device%20Drivers%20and%20Kernel%20Synchronization_eBook.pdf

好了,工具领到了,前置知识补齐了。现在,让我们正式进入正题——在这张看不见的网里,抓出那些会咬人的 Bug。