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

C 系统调用API、系统调用指令和异常机制(中断)之间的关系,c,linux,assembly,operating-system,x86-64,C,Linux,Assembly,Operating System,X86 64,我试图理解C语言系统调用API、syscall汇编指令和用于在进程之间切换上下文的异常机制(中断)之间的关系。我有很多东西要自己学习,所以请容忍我 我的理解正确吗?C语言系统调用由编译器以syscall的形式实现,其各自的代码在汇编中,然后由操作系统以异常机制(中断)的形式实现 因此,在下面的Ccode中调用write函数: #include <unistd.h> int main(void) { write(2, "There was an error writing t

我试图理解
C
语言系统调用API、
syscall
汇编指令和用于在进程之间切换上下文的异常机制(中断)之间的关系。我有很多东西要自己学习,所以请容忍我

我的理解正确吗?
C
语言系统调用由编译器以
syscall
的形式实现,其各自的代码在汇编中,然后由操作系统以异常机制(中断)的形式实现

因此,在下面的
C
code中调用
write
函数:

#include <unistd.h>

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

反过来,该指令是由操作系统作为异常机制(中断)来实现的?

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

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

那么uu libc_uwrite看起来像什么

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...
0000000000 40F9C0:
40f9c0:83 3d c5 bb 2a 00 cmpl$0x0,0x2ABC5(%rip)#6bb58c
40f9c7:75 14 jne 40f9dd
0000000000 40F9C9:
40f9c9:b8 01 00 mov$0x1,%eax
40f9ce:0f 05系统调用
……切。。。
Write只检查线程状态,假设一切正常, 将写入系统调用号(1)移入EAX并进入内核

一些注意事项:

  • x86-64 Linux写系统调用为1,旧x86为4
  • rdi指标准输出
  • rsi指向字符串
  • rdx是字符串大小计数
请注意,这是针对作者的x86-64 Linux系统的

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

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

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

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

编辑

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

库的实现方式由实现定义。编译器如何将您的代码连接到库运行时或链接时间有许多组合,关于如何实现,没有一种方法可以或需要实现,因此它也是实现定义的。只要它在功能上正确并且不干扰C标准,它就可以工作

我们的手机和桌面上安装了windows和linux等操作系统,人们强烈希望将应用程序与系统隔离开来,这样它们就不会以各种方式造成损害,因此需要保护,您需要有一种特定于体系结构的方法来对操作系统进行函数调用,这种调用在操作系统切换模式时不是正常的调用。如果体系结构有不止一种方法可以做到这一点,那么操作系统可以选择一种或多种方法作为其设计的一部分

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

TL;博士
syscall
指令本身就像一个美化的跳转,它是一种硬件支持的方式,可以高效、安全地从非特权用户空间跳转到内核中。
syscall
指令跳转到分派调用的内核入口点

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


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

C运行时通过特定于环境的机制实现上述交互。
可能有不同的软件抽象层,但最终会调用操作系统API

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

在Linux下,内核公开了一组可从用户空间访问的服务,这些入口点被称为。
在下面
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(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