请考虑订阅LWN订阅是LWN.net的命脉。如果您欣赏此内容并希望看到更多内容,您的订阅将有助于确保LWN继续蓬勃发展。请访问本页加入并保持LWN在网上。
当LWN上一次查看e1000e硬件损坏错误时,问题的来源充其量也是不清楚的。司机本身的问题似乎是一个可能的罪魁祸首,但那些追查这个问题的人很快就意识到他们需要看得更远。有一段时间,X服务器和其他一些系统组件都受到了仔细的检查,然而,当发现真正的问题时,却出乎所有人的意料。追踪间歇性问题是很困难的。当这些问题导致硬件损坏时,找到它们就更难了。即使是最敬业的测试人员,在面对将系统运回制造商进行维修的前景时,也往往会犹豫不决。因此,发现这个问题的任务落在了英特尔身上;那里的工程师们把自己锁在一个实验室里,里面装满了e1000e适配器,并着手将内核历史一分为二,以确定导致问题的补丁。过了一段时间(和许多友好的适配器)之后,这个一分为二的过程出现了一个不太可能的疑点:ftrace跟踪框架。从事跟踪工作的开发人员通常会投入大量精力来最小化代码对系统性能的影响。如果可能的话,会仔细检查并消除运行时开销的每一点。一般来说,搭建硬件是远远超出可接受参数的开销级别。因此,ftrace开发人员在被告知二等分结果后,就自己做了一些重要的工作来弄清楚发生了什么。Ftrace提供的特性之一是一个简单的函数调用跟踪操作;每次进行函数调用时,ftrace都会输出一行包含被调用函数(及其调用者)的内容。这种跟踪是通过使用GCC(和大多数其他基于UNIX的编译器)中内置的令人敬畏的剖析机制来完成的。当使用-pg选项编译代码时,编译器将在每个函数的开头调用mcount()。然后,ftrace提供的mcount()版本会记录每次调用的相关信息。但是如上所述,跟踪开发人员关心的是开销。在大多数系统上,几乎可以肯定的是,在任何给定的时间,都不会有人进行函数调用跟踪。不管怎样,使用所有这些mcount()调用调整都会对系统造成明显的拖累。因此,ftrace黑客寻找了一种在不需要的时候消除开销的方法。这个问题的天真解决方案可能如下所示:与其无条件调用mcount(),不如让GCC添加这样的代码:但是内核进行了大量的函数调用,所以即使是这个版本也会有明显的开销;它还会因为所有这些测试而增大内核的大小。因此,最受欢迎的方法往往是不同的:运行时打补丁。当没有使用函数跟踪时,内核会用无操作指令覆盖所有mcount()调用。在当代处理器中,什么都不做是一种高度优化的操作,因此几次无操作的开销几乎为零。如果有人决定打开函数跟踪,内核可以遍历并修补所有mcount()调用。运行时打补丁可以解决性能问题,但它本身也会带来新的问题。更改正在运行的内核下面的代码是一件危险的事情,需要格外小心。必须小心确保内核当时没有在受影响的代码中运行,处理器缓存必须无效,等等。为安全起见,有必要让系统上的所有其他处理器在打补丁时停止并等待。最终的结果是,修补代码是一件代价高昂的事情。Ftrace的编码方式是修补每个mcount()调用点,因为它是通过实际调用mcount()发现的。但是,如上所述,运行时修补非常昂贵,特别是如果一次只执行一个函数。因此,ftrace将列出mcount()调用点,然后稍后修复其中的一大堆。这样,拨打电话的成本就大大降低了。现在的问题是,从注意到mcount()调用到内核开始执行调用之间,情况可能已经发生了变化。如果内核完成了一个不再存在于预期位置的mcount()调用,那将是非常不幸的。为了绝对确保不相关的数据没有损坏,ftrace代码使用cmpxchg操作修补No-ops。Cmpxchg根据调用者对应该在那里的内容的想法自动测试targetmemory的内容;如果两者不匹配,则目标位置将在o的末尾保留其旧值。