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 位兼容系统调用入口点,从而为 32 位进程提供了在 64 位内核上运行时执行系统调用的能力。
IDT 向量分配在 arch/x86/include/asm/irq_vectors.h 中列出。
其中一些入口点是
system_call:来自 64 位代码的 syscall 指令。
entry_INT80_compat:来自 32 位或 64 位代码的 int 0x80;兼容系统调用(无论哪种方式)。
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 除外,NMI 会单独处理)必须使用 paranoid=1 的 idtentry 来正确处理 gsbase。这会触发三个主要行为更改
中断入口将使用较慢的 gsbase 检查。
从用户模式中断入口将关闭 IST 堆栈。
中断退出到内核模式将不会尝试重新调度。
我们尝试仅将 IST 入口和偏执入口代码用于绝对需要对 GS 基址进行更昂贵检查的向量 - 我们使用常规(更快)的 paranoid=0 变体生成所有“正常”入口点。