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