为什么传统模式下的 syscall/sysret 被认为是 "sufficiently poorly designed"?
Why syscall/sysret in legacy mode is considered "sufficiently poorly designed"?
查看 https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64_compat.S
中的评论
我知道因为 32 位 syscall/sysret 没有 save/restore ESP,所以有必要在任务门中处理 NMI 以确保良好的堆栈指针。除此之外,OS采用它的其他障碍是什么?是否有支持它的操作系统或所有操作系统都使用 sysenter/sysexit 在 32 位传统模式下进行快速系统调用?
注意:我从来没有处理过遗留 syscall
是 AMD 唯一的指令。
遗留 syscall
的主要问题是它需要某种形式的每个 cpu space 保存当前寄存器的位置。
如您所知,OS 无法将寄存器保存在堆栈中(因为 ESP
未被指令更改),也无法在保存当前堆栈之前设置不同的堆栈。
在单个 CPU 系统(即单处理器系统,即没有带或不带超线程的 SMP)中,OS 可以将当前寄存器保存在内存中已知的固定位置。
mov DWORD [0badf00dh], esp
等指令将地址编码为立即数,因此无需预先设置架构寄存器。
但是,这在 SMP 系统上不起作用,在 SMP 系统中,所有 CPU 共享相同的代码,除非 OS 为所有这些使用相同的内存区域(序列化对它的访问).
请注意,您不能加载 per-cpu 指针,因为这必然会覆盖某些寄存器。
另一个重要的一点是遗留 syscall
不会保存 eflags
,这使得编写其处理程序就像在蛋壳上行走一样。
此外,该指令还任意将 VM
和 IF
设置为零,使得编写可重入代码变得更加困难。
一种解决方法是使用调用约定:OS 可以在整个调用过程中将一个(或几个)寄存器标记为易变的(就像 ecx
已经是)。
问题是您最终可能会保存比您想象的更多的寄存器,从而使性能增益变小。
另一个难以置信的解决方法是在运行时 assemble 每个 CPU 的 syscall
的入口点(基本上,只是修补 moffset
s 字段),但这非常 hacky .
在 64 位模式下,OS 可以依赖 swapgs
来拥有一个 per-cpu 指针(或者更准确地说,一个 per-cpu 基地址)存储当前寄存器的位置。
由于 swapgs
从 MSR 加载,这可以在 OS 初始化期间提前设置。
请注意,在 64 位系统上,OS 也可以像 Linux 一样使用上层 GPR,将 esp
保存到例如 r8d
.
这在处理 32 位兼容模式程序时有效。
长话短说:遗留 syscall
使得 OS 很难将当前上下文保存在每个 cpu 内存区域中。
查看 https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64_compat.S
中的评论我知道因为 32 位 syscall/sysret 没有 save/restore ESP,所以有必要在任务门中处理 NMI 以确保良好的堆栈指针。除此之外,OS采用它的其他障碍是什么?是否有支持它的操作系统或所有操作系统都使用 sysenter/sysexit 在 32 位传统模式下进行快速系统调用?
注意:我从来没有处理过遗留 syscall
是 AMD 唯一的指令。
遗留 syscall
的主要问题是它需要某种形式的每个 cpu space 保存当前寄存器的位置。
如您所知,OS 无法将寄存器保存在堆栈中(因为 ESP
未被指令更改),也无法在保存当前堆栈之前设置不同的堆栈。
在单个 CPU 系统(即单处理器系统,即没有带或不带超线程的 SMP)中,OS 可以将当前寄存器保存在内存中已知的固定位置。
mov DWORD [0badf00dh], esp
等指令将地址编码为立即数,因此无需预先设置架构寄存器。
但是,这在 SMP 系统上不起作用,在 SMP 系统中,所有 CPU 共享相同的代码,除非 OS 为所有这些使用相同的内存区域(序列化对它的访问).
请注意,您不能加载 per-cpu 指针,因为这必然会覆盖某些寄存器。
另一个重要的一点是遗留 syscall
不会保存 eflags
,这使得编写其处理程序就像在蛋壳上行走一样。
此外,该指令还任意将 VM
和 IF
设置为零,使得编写可重入代码变得更加困难。
一种解决方法是使用调用约定:OS 可以在整个调用过程中将一个(或几个)寄存器标记为易变的(就像 ecx
已经是)。
问题是您最终可能会保存比您想象的更多的寄存器,从而使性能增益变小。
另一个难以置信的解决方法是在运行时 assemble 每个 CPU 的 syscall
的入口点(基本上,只是修补 moffset
s 字段),但这非常 hacky .
在 64 位模式下,OS 可以依赖 swapgs
来拥有一个 per-cpu 指针(或者更准确地说,一个 per-cpu 基地址)存储当前寄存器的位置。
由于 swapgs
从 MSR 加载,这可以在 OS 初始化期间提前设置。
请注意,在 64 位系统上,OS 也可以像 Linux 一样使用上层 GPR,将 esp
保存到例如 r8d
.
这在处理 32 位兼容模式程序时有效。
长话短说:遗留 syscall
使得 OS 很难将当前上下文保存在每个 cpu 内存区域中。