7. 内核入口¶
该文件记录了 arch/x86/entry/entry_64.S 中的一些内核入口。 大部分解释改编自 Ingo Molnar 的一封邮件
https://lore.kernel.org/r/20110529191055.GC9835%40elte.hu
x86 架构有很多种跳转到内核代码的方式。 大部分入口点在 arch/x86/kernel/traps.c 中注册,并在 arch/x86/entry/entry_64.S 中为 64 位实现,在 arch/x86/entry/entry_32.S 中为 32 位实现,最后在 arch/x86/entry/entry_64_compat.S 中实现,该文件实现了 32 位兼容 syscall 入口点,从而为 32 位进程提供在 64 位内核上执行 syscall 的能力。
IDT 向量分配在 arch/x86/include/asm/irq_vectors.h 中列出。
其中一些入口是
system_call:来自 64 位代码的 syscall 指令。
entry_INT80_compat:来自 32 位或 64 位代码的 int 0x80;兼容 syscall。
entry_INT80_compat,ia32_sysenter:来自 32 位代码的 syscall 和 sysenter
interrupt:入口数组。 每个没有明确指向其他位置的 IDT 向量都被设置为中断中相应的值。 这些指向一系列神奇生成的函数,这些函数通过中断号作为参数进入 common_interrupt()。
APIC 中断:各种特殊用途中断,例如 TLB shootdown。
架构定义的异常,例如 divide_error。
这里有一些复杂性。 不同的 x86-64 入口有不同的调用约定。 syscall 和 sysenter 指令有它们自己特殊的调用约定。 一些 IDT 入口将错误代码压入栈;另一些则不这样做。 使用 IST 替代栈机制的 IDT 入口需要它们自己的魔法来使栈帧正确。 (您可以在 AMD APM 第 2 卷第 8 章和 Intel SDM 第 3 卷第 6 章中找到一些文档。)
处理 swapgs 指令尤其棘手。 Swapgs 切换 gs 是内核 gs 还是用户 gs。 swapgs 指令相当脆弱:它必须完美嵌套并且仅在单个深度中嵌套,它应该仅在从用户模式进入内核模式时使用,然后在返回到用户空间时才使用,并且必须精确地这样使用。 如果我们哪怕稍微搞砸了,我们就会崩溃。
因此,当我们有辅助入口时,已经在内核模式下,我们绝不能盲目地使用 SWAPGS - 我们也不能忘记在尚未切换/交换时执行 SWAPGS。
现在,有一个辅助的复杂性:有一种廉价的方法来测试 CPU 处于哪种模式,还有一种昂贵的方法。
廉价的方法是从内核栈上的入口帧中提取此信息,从内核栈的 ptregs 区域的 CS 中提取
xorl %ebx,%ebx
testl $3,CS+8(%rsp)
je error_kernelspace
SWAPGS
昂贵的(偏执的)方法是读回 MSR_GS_BASE 值(这是 SWAPGS 修改的值)
movl $1,%ebx
movl $MSR_GS_BASE,%ecx
rdmsr
testl %edx,%edx
js 1f /* negative -> in kernel */
SWAPGS
xorl %ebx,%ebx
1: ret
如果我们处于中断或类似于用户陷阱/门的边界,那么我们可以使用更快的检查:栈将可靠地指示是否已经执行了 SWAPGS:如果我们看到我们是中断内核模式执行的辅助入口,那么我们知道 GS 基地址已经被切换。 如果它说我们中断了用户空间执行,那么我们必须执行 SWAPGS。
但是,如果我们处于 NMI/MCE/DEBUG/任何超级原子入口上下文中,这可能在正常入口将 CS 写入栈之后,但在我们执行 SWAPGS 之前触发,那么唯一安全的 GS 检查方法是较慢的方法:RDMSR。
因此,超级原子入口(NMI 除外,它被单独处理)必须使用 paranoid=1 的 idtentry 来正确处理 gsbase。 这会触发三个主要的行为更改
中断入口将使用较慢的 gsbase 检查。
来自用户模式的中断入口将关闭 IST 栈。
退出内核模式的中断入口不会尝试重新调度。
我们尝试仅对绝对需要更昂贵的 GS 基地址检查的向量使用 IST 入口和偏执入口代码 - 并且我们使用常规的(更快的)paranoid=0 变体生成所有“正常”入口点。