Linux内核代码优化

从理解Linux内核第三版在描述内核固定映射的章节中,有一个使用以下枚举器的函数 –

每个固定映射的线性地址由enum fixed_addresses数据结构中定义的一个小整数索引表示:

enum fixed_addresses { FIX_HOLE, FIX_VSYSCALL, FIX_APIC_BASE, FIX_IO_APIC_BASE_0, [...] _ _end_of_fixed_addresses }; 

并给这个枚举器,下面的函数将编译

固定映射的线性地址放在第四个千兆字节的线性地址的末尾。 fix_to_virt()函数计算从索引开始的常量线性地址:

 inline unsigned long fix_to_virt(const unsigned int idx) { if (idx >= _ _end_of_fixed_addresses) _ _this_fixmap_does_not_exist( ); return (0xfffff000UL - (idx << PAGE_SHIFT)); } 

现在,看看下面这个函数如何最终转化为只有0xfffff000-(3 << PAGE_SHIFT)

假设某个内核函数调用fix_to_virt(FIX_IOAPIC_BASE_0)。 由于该函数被声明为“inline”,因此C编译器不会生成对fix_to_virt()的调用,而是将其代码插入到调用函数中。 而且,索引值的检查从不在运行时执行。 实际上,FIX_IOAPIC_BASE_0是一个等于3的常量,所以编译器可以在编译的时候删除if语句,因为它的条件是false。 相反,如果条件为真,或者fix_to_virt()的参数不是常量,则编译器在链接阶段发出错误,因为符号_ _this_fixmap_does_not_exist没有在任何地方定义。 最终,编译器计算0xfffff000-(3 << PAGE_SHIFT),并用常量线性地址0xffffc000replacefix_to_virt()函数调用。

所以,代码编写者似乎依赖于一些假设,如果我们有一个if语句,在编译时间中定义两个数字之间的比较(可以说FIX_IO_APIC_BASE_0_ _end_of_fixed_addresses ),并且因此在编译时间中知道结果, if语句肯定会被优化并从代码中移除。

Linux内核代码如何假设?

另外,这样的代码的动机是什么? 如果编写者希望函数被评估为0xfffff000-(3 << PAGE_SHIFT)为什么不写一个0xfffff000-(3 << PAGE_SHIFT)而不是调用这个函数呢?

其实有两个问题:

  • Linux内核代码如何假定代码已从二进制文件中删除? 简单的答案是编译环境(编译器等)保证。 基本上,不断的传播是一个简单的,众所周知的编译器优化,在这种情况下,它很容易检测到代码是未使用的。
  • 为什么不把拼写出来? 简单的原因是便携性和可读性。 可移植性意味着这使您可以在一个新的平台或CPU的一个地方调整功能。 可读性意味着这个意图是随着这个函数的可能文档一起传递的,如果你只是把原始函数的内容抛在那里,情况就不会这样。 这与在代码中不使用“幻数”的基本原理类似。

__end_of_fixed_addresses在单元编译时已知,但不是idx:可能是由外部源代码调用idx无效的函数。 由于this_fixmap_does_not_exists符号不存在,就像解释说的那样,如果开发人员尝试使用带有无效idx的fix_to_virt将会导致编译错误。 但是如果他使用了正确的语句,编译器就会删除“if”语句。 所以,这实际上不是运行时保护,而是一种开发保护,一种将可能的运行时错误转换为编译错误的技术。 关于修复结果,请注意,仅仅是例子。 idx可能是3的不同,所以这个函数的结果。