assembly - 为什么传统模式下的 syscall/sysret 被认为是 "sufficiently poorly designed"?

标签 assembly x86 operating-system system-calls amd-processor

查看 https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64_compat.S 中的评论

我知道因为 32 位 syscall/sysret 不保存/恢复 ESP,所以有必要在任务门中处理 NMI 以确保良好的堆栈指针。除此之外,操作系统采用它的其他障碍是什么?是否有操作系统支持它或所有操作系统都使用 sysenter/sysexit 在 32 位传统模式下进行快速系统调用?

最佳答案

备注 : 我从来没有处理过遗留问题 syscall作为 AMD 唯一的指令。

遗留系统的主要问题 syscall是它需要某种形式的 per-cpu 空间来保存当前寄存器。
如您所知,操作系统无法将寄存器保存在堆栈上(因为 ESP 不会被指令更改),也无法在保存当前堆栈之前设置不同的堆栈。

在单 CPU 系统(即单处理器系统,即没有 SMP 和超线程的情况下)中,操作系统可以将当前寄存器保存在内存中一个已知的、固定的位置。
指令如 mov DWORD [0badf00dh], esp将地址编码为立即数,因此无需预先设置架构寄存器。
但是,这不适用于所有 CPU 共享相同代码的 SMP 系统,除非操作系统为所有 CPU 使用相同的内存区域(序列化对它的访问)。
请注意,您不能加载 per-cpu 指针,因为这必然会覆盖某些寄存器。

另一个重点是遗留syscall不保存 eflags ,这使得编写它的处理程序就像在蛋 shell 上行走一样。
另外这条指令还任意设置了VMIF为零,使编写可重入代码变得更加困难。

一种解决方法是使用调用约定:操作系统可以在整个调用过程中将寄存器(或几个)标记为 volatile (例如 ecx 已经是)。
问题是您最终可能会节省比您想象的更多的寄存器,从而使性能增益变薄。
另一个难以置信的解决方法可能是组装 syscall 的入口点。对于运行时的每个 CPU(基本上,只是修补 moffset s 字段),但这非常hacky。

在 64 位模式下,操作系统可以依赖 swapgs有一个 per-cpu 指针(或更准确地说,一个 per-cpu 基地址)来存储当前寄存器。
swapgs从 MSR 加载,这可以在 OS 初始化期间预先设置。

请注意,在 64 位系统上,操作系统也可以像 Linux 一样使用上层 GPR 来保存 esp例如,进入 r8d .
这在处理 32 位兼容模式程序时有效。

长话短说:遗产syscall使操作系统很难将当前上下文保存在每个 CPU 的内存区域中。

关于assembly - 为什么传统模式下的 syscall/sysret 被认为是 "sufficiently poorly designed"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61706185/

相关文章:

assembly - 为什么汇编中字符串变量末尾包含 0?

assembly - 我什么时候应该单独使用 AESIMC,而不是使用 AESDEC

assembly - 上面的跳转在 AT&T 汇编格式中如何工作

c - 为什么在中断处理程序中使用自旋锁

operating-system - 在具有 1GB 物理内存和 8KB 页面大小的 32 位系统上,帧和偏移量需要多少位?

loops - MIPS 中的 While 循环(MIPS 初学者)

c - GCC 寄存器优化

assembly - 从 Assembly x86 NASM 内存中读取 16 位

c++ - 转换内联 C 汇编程序(Intel 语法到 AT&T)

c++ - 堆栈在哪里实现?