Linux 系统调用是否在异常处理程序中执行?
Are Linux system calls executed inside an exception handler?
我知道在输入系统调用后,例如syscall、int 0x80 (x86/x86-64) 或 svc (ARM) 指令,从 Linux 内核的角度来看,我们停留在调用进程上下文中(但从用户模式切换到内核模式)。但是,从硬件的角度来看,我们跳转到了 syscall/svc/... 异常处理程序。整个系统调用代码是否在 Linux 中的异常处理程序中执行?
使用 80x86 常用的术语(来自英特尔手册等); CPU 有一个“当前特权级别”(CPL),用于确定代码是否受到限制(例如,是否允许特权指令),这是“user-space vs 的基础.内核space”。触发从 CPL=3 ("user space") 到 CPL=0 ("kernel space") 的切换的是:
异常,通常表示 CPU
检测到问题(例如被零除)
IRQ,指示设备需要注意
软件中断、调用门以及syscall
和sysenter
指令。这些都是软件明确向 OS/kernel 请求某些东西(内核系统调用)的不同方式,其中不同的操作 systems/kernels 可能只支持其中的一些或其中之一(64 位代码只需要 syscall
和所有其他替代方案可能不会被 OS/kernel 支持,除非它试图为过时的 32 位内容提供向后兼容性)。
任务门(已过时,不支持 64 位且未被任何知名的 32 位 OS 使用)。
使用这个术语;说 Linux 系统调用在异常处理程序中执行是错误的(因为异常是不涉及的特定事物)。
然而...
不同的人对术语的定义不同;有些人 (ARM) 将“异常”定义为“任何导致切换到内核 space”的同义词。这对于 CPU 设计师来说是有道理的,他们主要关注任何切换到主管模式对 CPU 的影响,并且没有理由关心差异(因为差异主要是软件开发人员的问题)。对于其他人(软件开发人员),通过使用该术语,您可以说内核中的所有内容都在异常处理程序中使用;这主要使“异常”一词变得毫无意义(因为“可能是任何东西”不提供任何附加信息)。换句话说,使用该术语,“Linux 系统调用在异常处理程序中执行”在技术上是正确的,但可以缩短为“Linux 系统调用被执行”而不改变语句的含义。
注意:最近英特尔发布了一份关于未来可能扩展的提案草案,该提案将(如果被 CPU 采纳和支持并被 OS 启用)用新的“事件”方案;其中许多 different/separate(异常、IRQ、系统调用等)处理程序被单个“事件处理程序”(必须获取 CPU 提供的“事件原因”然后分支到“事件原因特定”代码)。如果发生这种情况,我会期待第三组术语(例如“异常事件”、“IRQ 事件”和“系统调用事件”,其中所有内核代码都在某种事件的上下文中执行;以及“Linux 系统调用在事件处理程序中执行”在技术上是正确的,但可以缩短为“Linux 系统调用被执行”。
在32位x86中Linux,使用了sysenter指令。 sysenter 指令跳转到 MSR 中指定的地址。 sysenter 指令不是中断。它跳转到 MSR 中指定的地址(由 Linux 在引导时放置在那里)。
在 x64 Linux 中,改为使用 syscall 指令。它的工作方式与 sysenter 相同。
查看 Whosebug 上的以下问答:。我提供了一个非常完整的答案。
此外,我没有提到的是,当您 link 静态程序时,所有 glibc 代码都会添加到您的可执行文件中,直到 syscall 指令。因此,您的代码依赖于 OS 到 运行 的存在(因为否则没有任何东西可以跳转到)。
答案是:不,系统调用不是在中断处理程序中执行的。
没有。最重要的是,syscall
/ sysenter
根本不是异常或中断;见下文。
而且,“中断”(包括像 int 0x80
这样的软件中断)不同于英特尔术语中的“异常”(由错误条件引起的事件)。
对于“异常”,保存的 RIP 是错误指令(就像您想要的 #PF
页面错误一样,因此使用 iret
返回到 user-space 将重试那条指令。在为 有效 页面错误调整页表后,这就是你想要的,而不是会导致内核传送 SIGSEGV)。此外,一些异常将与 RFLAGS 和 CS:RIP.
一起推送错误代码
像 int 0x80
这样的软件中断会在 之后生成已保存的 EIP/RIP 指令,因此 iret
将继续而不是重新 运行 相同的指令,无需内核手动修改保存的上下文。所以它与异常非常相似,因为它将 RFLAGS 和 CS:RIP 压入堆栈并跳转到从 IDT 加载的 CS:RIP 地址,但它的不同之处在于压入的 saved-RIP 值.无论哪种方式,代码都在特权级别(ring)0
执行,但是在捕获指令之后的 saved-RIP = 指令可以方便地用作远程过程调用(从 user-space 进入内核).
(半相关 显示了 64 位 Linux 内核中系统调用和 int 0x80 处理程序的一些内核端。从 Meltdown / Spectre 缓解措施的更改之前开始事情更复杂。)
当然 syscall
根本不使用中断/异常机制(没有 IDT,内核堆栈上没有任何内容)。相反,它使用 RCX 和 R11 来保存 user-space RIP 和 RFLAGS,并设置 RIP = IA32_LSTAR_MSR
(内核将其设置为指向其系统调用入口点)。而且它不使用 TSS 东西将 RSP 设置为内核堆栈指针;内核必须自己做。 (通常使用 swapgs
来访问每个内核或每个任务的存储,它可以保存用户 space RSP 并加载内核堆栈指针。在 Linux 中,kernegs 点到内核堆栈的底部,最低地址/最后使用,IIRC。)
sysenter
使用不同的机制,但我认为类似的想法来自 MSR 的内核入口地址,而不是每次都必须使用解析 IDT 入口类型的所有机制从 IDT 加载.
syscall 和 sysenter 入口点有点像中断处理程序,但是 iret
不会让您返回到用户 space。 (相反,给定寄存器/堆栈的状态,sysret
或 sysexit
会。)
我知道在输入系统调用后,例如syscall、int 0x80 (x86/x86-64) 或 svc (ARM) 指令,从 Linux 内核的角度来看,我们停留在调用进程上下文中(但从用户模式切换到内核模式)。但是,从硬件的角度来看,我们跳转到了 syscall/svc/... 异常处理程序。整个系统调用代码是否在 Linux 中的异常处理程序中执行?
使用 80x86 常用的术语(来自英特尔手册等); CPU 有一个“当前特权级别”(CPL),用于确定代码是否受到限制(例如,是否允许特权指令),这是“user-space vs 的基础.内核space”。触发从 CPL=3 ("user space") 到 CPL=0 ("kernel space") 的切换的是:
异常,通常表示 CPU
检测到问题(例如被零除)IRQ,指示设备需要注意
软件中断、调用门以及
syscall
和sysenter
指令。这些都是软件明确向 OS/kernel 请求某些东西(内核系统调用)的不同方式,其中不同的操作 systems/kernels 可能只支持其中的一些或其中之一(64 位代码只需要syscall
和所有其他替代方案可能不会被 OS/kernel 支持,除非它试图为过时的 32 位内容提供向后兼容性)。任务门(已过时,不支持 64 位且未被任何知名的 32 位 OS 使用)。
使用这个术语;说 Linux 系统调用在异常处理程序中执行是错误的(因为异常是不涉及的特定事物)。
然而...
不同的人对术语的定义不同;有些人 (ARM) 将“异常”定义为“任何导致切换到内核 space”的同义词。这对于 CPU 设计师来说是有道理的,他们主要关注任何切换到主管模式对 CPU 的影响,并且没有理由关心差异(因为差异主要是软件开发人员的问题)。对于其他人(软件开发人员),通过使用该术语,您可以说内核中的所有内容都在异常处理程序中使用;这主要使“异常”一词变得毫无意义(因为“可能是任何东西”不提供任何附加信息)。换句话说,使用该术语,“Linux 系统调用在异常处理程序中执行”在技术上是正确的,但可以缩短为“Linux 系统调用被执行”而不改变语句的含义。
注意:最近英特尔发布了一份关于未来可能扩展的提案草案,该提案将(如果被 CPU 采纳和支持并被 OS 启用)用新的“事件”方案;其中许多 different/separate(异常、IRQ、系统调用等)处理程序被单个“事件处理程序”(必须获取 CPU 提供的“事件原因”然后分支到“事件原因特定”代码)。如果发生这种情况,我会期待第三组术语(例如“异常事件”、“IRQ 事件”和“系统调用事件”,其中所有内核代码都在某种事件的上下文中执行;以及“Linux 系统调用在事件处理程序中执行”在技术上是正确的,但可以缩短为“Linux 系统调用被执行”。
在32位x86中Linux,使用了sysenter指令。 sysenter 指令跳转到 MSR 中指定的地址。 sysenter 指令不是中断。它跳转到 MSR 中指定的地址(由 Linux 在引导时放置在那里)。
在 x64 Linux 中,改为使用 syscall 指令。它的工作方式与 sysenter 相同。
查看 Whosebug 上的以下问答:
此外,我没有提到的是,当您 link 静态程序时,所有 glibc 代码都会添加到您的可执行文件中,直到 syscall 指令。因此,您的代码依赖于 OS 到 运行 的存在(因为否则没有任何东西可以跳转到)。
答案是:不,系统调用不是在中断处理程序中执行的。
没有。最重要的是,syscall
/ sysenter
根本不是异常或中断;见下文。
而且,“中断”(包括像 int 0x80
这样的软件中断)不同于英特尔术语中的“异常”(由错误条件引起的事件)。
对于“异常”,保存的 RIP 是错误指令(就像您想要的 #PF
页面错误一样,因此使用 iret
返回到 user-space 将重试那条指令。在为 有效 页面错误调整页表后,这就是你想要的,而不是会导致内核传送 SIGSEGV)。此外,一些异常将与 RFLAGS 和 CS:RIP.
像 int 0x80
这样的软件中断会在 之后生成已保存的 EIP/RIP 指令,因此 iret
将继续而不是重新 运行 相同的指令,无需内核手动修改保存的上下文。所以它与异常非常相似,因为它将 RFLAGS 和 CS:RIP 压入堆栈并跳转到从 IDT 加载的 CS:RIP 地址,但它的不同之处在于压入的 saved-RIP 值.无论哪种方式,代码都在特权级别(ring)0
执行,但是在捕获指令之后的 saved-RIP = 指令可以方便地用作远程过程调用(从 user-space 进入内核).
(半相关
当然 syscall
根本不使用中断/异常机制(没有 IDT,内核堆栈上没有任何内容)。相反,它使用 RCX 和 R11 来保存 user-space RIP 和 RFLAGS,并设置 RIP = IA32_LSTAR_MSR
(内核将其设置为指向其系统调用入口点)。而且它不使用 TSS 东西将 RSP 设置为内核堆栈指针;内核必须自己做。 (通常使用 swapgs
来访问每个内核或每个任务的存储,它可以保存用户 space RSP 并加载内核堆栈指针。在 Linux 中,kernegs 点到内核堆栈的底部,最低地址/最后使用,IIRC。)
sysenter
使用不同的机制,但我认为类似的想法来自 MSR 的内核入口地址,而不是每次都必须使用解析 IDT 入口类型的所有机制从 IDT 加载.
syscall 和 sysenter 入口点有点像中断处理程序,但是 iret
不会让您返回到用户 space。 (相反,给定寄存器/堆栈的状态,sysret
或 sysexit
会。)