系统调用API,系统调用指令和异常机制(中断)之间的关系

时间:2018-04-25 15:16:52

标签: c linux assembly operating-system x86-64

我正在尝试理解C语言系统调用API,syscall汇编程序指令与用于在进程之间切换上下文的异常机制(中断)之间的关系。我自己要研究很多,所以请耐心等待。

我的理解是否正确C语言系统调用由编译器实现为syscall,其中各自的代码在汇编中,而这些代码又由OS实现为异常机制(中断)?

所以在以下write代码中调用C函数:

#include <unistd.h>

int main(void)
{
    write(2, "There was an error writing to standard out\n", 44);
    return 0;
}

作为syscall指令编译为程序集:

mov eax,4       ; system call number (sys_write)
syscall 

反过来,指令由OS实现为异常机制(中断)?

4 个答案:

答案 0 :(得分:5)

TL; DR

syscall指令本身就像一个美化的跳跃,它是一种硬件支持的方式,可以有效,安全地从无特权的用户空间跳转到内核。
syscall指令跳转到调度调用的内核入口点。

在x86_64之前使用了另外两种机制:int指令和sysenter指令。
它们具有不同的入口点(目前仍然存在于32位内核中,以及可以运行32位用户空间程序的64位内核)。 前者使用x86中断机制,可以与异常调度混淆(也使用中断机制) 但是,异常是虚假事件,而int用于生成软件中断,同样是一个美化的跳跃。

C语言并不关心系统调用,它依赖于the C runtime来执行与未来程序环境的所有交互。

C运行时通过特定于环境的机制实现上述交互 可能存在各种层次的软件抽象,但最终会调用OS API。

术语API用于表示合同,严格来说,使用API​​并不需要调用一段内核代码(趋势是在用户空间中实现非关键功能以限制可利用的代码),这里我们只对需要特权切换的API子集感兴趣。

在Linux下,内核公开了一组可从用户空间访问的服务,这些入口点称为system calls。 在Windows下,内核服务(使用与Linux类似物相同的机制访问)被认为是私有的,因为它们不需要在不同版本之间保持稳定。
一组DLL / EXE导出函数用作入口点(例如ntoskrnl.exe,hal.dll,kernel32.dll,user32.dll),后者又通过(私有)系统调用使用内核服务。 /> 请注意,在Linux下,大多数系统调用都有一个POSIX包装器,因此可以使用这些包装器(即普通的C函数)来调用系统调用。
底层ABI是不同的,错误报告也是如此;包装器在两个世界之间进行转换。

C运行时调用OS API,在Linux的情况下,系统调用是直接使用的,因为它们是公共的(在某种意义上说在各个版本中是稳定的),而对于Windows,通常的DLL,如kernel32.dll,是标记为依赖项并使用。

我们被缩减到用户模式程序(作为C运行时(Linux)的一部分或API DLL(Windows)的一部分)需要调用内核中的代码的程度。

x86架构历来提供了不同的方法,例如,call gate 另一种方法是通过int指令,它有一些优点:

  • 这是BIOS和DOS在他们的时代所做的事情 在实模式下,使用int指令是合适的,因为向量编号(例如21h)比远地址(例如0f000h:0fff0h)更容易记住。
  • 它保存标志。
  • 设置简单(设置ISR相对容易)。

随着架构的现代化,这种机制变得有一个很大的缺点:它很慢。 在引入sysenter(注意,sysenter而非syscall)指令之前,没有更快的替代方案(呼叫门同样会很慢)。

随着Pentium Pro / II [1]的出现,引入了一对新的说明sysentersysexit,以加快系统调用。
Linux开始使用它们since the version 2.5,并且至今仍在32位系统上使用。我相信 我不会解释sysenter指令和使用它所需的伴随VDSO的整个机制,只需要说它比int机制更快(我找不到Andy Glew的一篇文章,他说sysenter在奔腾III上变慢了,我现在还不知道它是如何表现的。

随着x86-64的出现,AMD对sysenter的响应,即syscall / sysret对,开始了从用户模式切换到内核的事实上的方式 - 模式。
这是因为sysenter实际上很快且非常简单(它分别将riprflags复制到rcxr11,屏蔽{{1跳转到rflags中设置的地址。

Linux和Windows的64位版本都使用IA32_LSTAR

总结一下,可以通过三种机制对内核进行控制:

  • 软件中断。
    对于32位Linux(2.5之前的版本),这是syscall,对于32位Windows,这是int 80h
  • 通过int 2eh
    自2.5以来由32位版本的Linux使用。
  • 通过sysenter
    由64位版本的Linux和Windows使用。

Here is a nice page to put it in a better shape

C运行时通常是一个静态库,因此是预编译的,它使用上述三种方法之一。

syscall instruction直接将控制权转移到内核入口点(见entry_64.s) 它是一个只是这样做的指令,它不是由OS实现的,它是由操作系统使用

术语异常在CS中重载,C ++有例外,Java和C#也是例外。
操作系统可以有一个与语言无关的异常捕获机制(在windows下它曾被称为 SEH ,现在已被重写)。
CPU也有例外 我相信我们正在谈论最后的意义。

例外是通过中断发送的,它们是一种中断 它没有说明,虽然异常是同步的(它们发生在特定的,可重放的点上),但它们是“不受欢迎的”,它们是例外的,因为程序员倾向于避免它们,并且当它们发生时是由于错误,未处理的角落案件或不良情况 因此,它们不用于将控制转移到内核(它们可以)。

使用软件中断(也是同步的);机制几乎完全相同(异常可以在内核堆栈上推送状态代码)但语义不同 我们从不引用空指针,访问未映射的页面或类似于调用系统调用,我们使用syscall指令代替。

答案 1 :(得分:3)

修改

是的,C应用程序调用C库函数,它隐藏在C库解决方案中是一个特定于系统的调用或一组调用,它使用体系结构特定的方式到达操作系统,该操作系统具有异常/中断处理程序设置处理这些系统调用。实际上并不需要在架构上具体,可以简单地跳转/调用一个众所周知的地址,但是对于安全和保护模式的现代需求,一个简单的调用不会有这些增加的功能,但仍然在功能上正确。

如何实现库是实现定义的。以及编译器如何将代码连接到库运行时或链接时有多种组合,如何实现,没有一种方法可以或者需要发生,所以它也是实现定义的。只要它在功能上是正确的并且不会干扰C标准,那么它就可以工作。

在我们的手机和桌面上使用Windows和Linux等操作系统时,强烈希望将应用程序与系统隔离,以便它们不会以各种方式造成损害,因此需要保护,并且您需要具有架构性在切换模式时,将函数调用到非正常调用的操作系统的特定方法。如果架构有多种方法可以实现,那么操作系统可以选择一种或多种方式作为其设计的一部分。

“软件中断”是一种常见的方式,因为硬件中断大多数解决方案包括一个处理程序地址表,通过扩展该表并将一些向量绑定到一个软件创建的“中断”(打一个特殊的指令而不是比输入上的信号改变状态)但经过相同的停止,保存一些状态,调用矢量等。

答案 2 :(得分:3)

  

我的理解是正确的,C语言系统调用是由编译器实现的,系统调用是汇编[...]中的相应代码吗?

C编译器处理系统调用的方式与处理对任何其他函数的调用相同:

; write(2, "There was an error writing to standard out\n", 44);
mov    $44, %edx
lea    .LC0(%rip), %rsi  ; address of the string
mov    $2, %edi
call   write

libc(系统的C库)中这些函数的实现可能包含syscall指令,或者系统架构中的等效指令。

答案 3 :(得分:2)

不是问题的直接答案,但这可能会引起你的兴趣(我没有足够的业力来评论) - 它详细解释了所有用户空间执行(包括glibc以及它如何进行系统调用):

http://www.maizure.org/projects/printf/index.html

您可能会对“第8步 - 写入标准输出的最终字符串”感兴趣:

  

__libc_write看起来像什么......?

000000000040f9c0 <__libc_write>:
  40f9c0:  83 3d c5 bb 2a 00 00   cmpl   $0x0,0x2abbc5(%rip)  # 6bb58c <__libc_multiple_threads>
  40f9c7:  75 14                  jne    40f9dd <__write_nocancel+0x14>

000000000040f9c9 <__write_nocancel>:
  40f9c9: b8 01 00 00 00          mov    $0x1,%eax
  40f9ce: 0f 05                   syscall 
  ...cut...
     

写简单地检查线程状态,假设一切正常,   将write syscall number(1)移入EAX并进入内核。

     

一些注意事项:

     
      
  • x86-64 Linux写的系统调用是1,旧的x86是4
  •   
  • rdi指的是stdout
  •   
  • rsi指向字符串
  •   
  • rdx是字符串大小计数
  •   

请注意,这是针对作者的x86-64 Linux系统。

对于x86,这提供了一些帮助:

http://www.tldp.org/LDP/khg/HyperNews/get/syscall/syscall86.html

  

在Linux下,由指令int 0x80引起的可屏蔽中断或异常类传输调用系统调用的执行。我们使用向量0x80将控制转移到内核。该中断向量在系统启动期间与其他重要向量(如系统时钟向量)一起初始化。

但作为Linux内核的一般答案:

  

我的理解是正确的,C语言系统调用是由编译器实现的,系统调用是汇编中的相应代码,而这些代码又由OS实现为异常机制(中断)?