Intel x86 vs x64系统调用

时间:2013-03-01 23:46:59

标签: linux assembly x86 x86-64 interrupt

我正在阅读x86和x64之间的汇编差异。

在x86上,系统调用号码放在eax中,然后执行int 80h以生成软件中断。

但是在x64上,系统调用号码放在rax中,然后执行syscall

我被告知syscall比生成软件中断更轻,更快。

为什么x64上的速度比x86快,我可以使用int 80h在x64上进行系统调用吗?

1 个答案:

答案 0 :(得分:15)

当你调用内核(进行系统调用)时,有三件事需要发生:

  1. 系统从“用户模式”变为“内核模式”(响铃0)。
  2. 堆栈从“用户模式”切换到“内核模式”。
  3. 跳转到内核的适当部分。
  4. 显然,一旦进入内核,内核代码将需要知道你真正想要内核做什么,因此在EAX中添加一些东西,而在其他寄存器中往往会有更多东西,因为有些东西比如“你的文件名”想要打开“或”缓冲区以将文件中的数据读入“等等

    不同的处理器有不同的方法来实现上述三个步骤。在x86中,有几种选择,但手写asm最常用的两种选择是int 0xnn(32位模式)或syscall(64位模式)。 (英特尔也引入了32位模式sysenter,原因与AMD推出32位模式版本的syscall相同:作为慢速int 0x80的更快替代方案。 bit glibc使用任何有效的系统调用机制,只有在没有更好的可用时才使用慢int 0x80。)

    64位版本的the syscall instruction是随x86-64架构引入的,是进入系统调用的更快捷方式。它有一组寄存器(使用x86 MSR机制),包含我们希望跳转到的地址RIP,加载到CS和SS的选择器值,以及用于执行Ring3到Ring0转换的选择器值。它还将返回地址存储在ECX / RCX中。 [请阅读说明书手册以了解本说明书的所有细节 - 这并非完全无关紧要!]。由于处理器知道这将切换到Ring0,它可以直接做正确的事情。

    关键点之一是syscall仅操纵寄存器;它不会进行任何加载或存储。(这就是为什么它用保存的RIP覆盖RCX,用保存的RFLAGS覆盖R11)。内存访问取决于页表,页表条目有一点可以使它们仅对内核有效,而不是用户空间,因此在更改权限级别时进行内存访问可能需要等待vs只是写寄存器。一旦进入内核模式,内核通常会使用swapgs或其他一些查找内核堆栈的方式。 (syscall 修改RSP;它仍然指向进入内核的用户堆栈。)

    使用SYSRET指令返回时,值会从寄存器中的预定值恢复,因此处理速度很快,因为处理器只需要设置几个寄存器。处理器知道它将从Ring0变为Ring3,因此可以快速做正确的事情。

    (AMD CPU支持来自32位用户空间的syscall指令; Intel CPU不支持.x86-64最初是AMD64;这就是我们在64位模式下syscall的原因。 AMD为64位模式重新设计了syscall的内核端,因此64位syscall内核入口点与64位内核中的32位syscall入口点明显不同。)

    32位模式中使用的int 0x80变体将根据中断描述符表中的值决定做什么,这意味着从内存中读取。在那里它找到新的CS和EIP / RIP值。新的CS寄存器确定新的“振铃”电平 - 在这种情况下为Ring0。然后,它将使用新的CS值查看任务状态段(基于TR寄存器)以找出哪个堆栈指针(ESP / RSP和SS),然后最终跳转到新地址。由于这是一种不太直接且更通用的解决方案,因此速度也较慢。旧的EIP / RIP和CS与旧的SS和ESP / RSP值一起存储在新堆栈中。

    当使用IRET指令返回时,处理器从堆栈中读取返回地址和堆栈指针值,同时从堆栈中加载新的堆栈段和代码段值。同样,该过程是通用的,并且需要相当多的内存读取。由于它是通用的,处理器还必须检查“我们是否正在从Ring0更改模式到Ring3,如果这样改变这些东西”。

    因此,总而言之,它更快,因为它意味着以这种方式工作。

    对于32位代码,是的,如果需要,你绝对可以使用慢速和兼容的int 0x80

    对于64位代码,int 0x80syscall慢,并且会将指针截断为32位,因此不要使用它。请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?另外,在所有内核上,int 0x80在64位模式下不可用,因此即使对于不带任何指针args的sys_exit,它也不安全:{{1可以禁用,尤其是在Windows的Linux子系统上