我正在尝试理解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实现为异常机制(中断)?
答案 0 :(得分:5)
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指令,它有一些优点:
int
指令是合适的,因为向量编号(例如21h
)比远地址(例如0f000h:0fff0h
)更容易记住。随着架构的现代化,这种机制变得有一个很大的缺点:它很慢。
在引入sysenter
(注意,sysenter
而非syscall
)指令之前,没有更快的替代方案(呼叫门同样会很慢)。
随着Pentium Pro / II [1]的出现,引入了一对新的说明sysenter
和sysexit
,以加快系统调用。
Linux开始使用它们since the version 2.5,并且至今仍在32位系统上使用。我相信
我不会解释sysenter
指令和使用它所需的伴随VDSO的整个机制,只需要说它比int
机制更快(我找不到Andy Glew的一篇文章,他说sysenter
在奔腾III上变慢了,我现在还不知道它是如何表现的。
随着x86-64的出现,AMD对sysenter
的响应,即syscall
/ sysret
对,开始了从用户模式切换到内核的事实上的方式 - 模式。
这是因为sysenter
实际上很快且非常简单(它分别将rip
和rflags
复制到rcx
和r11
,屏蔽{{1跳转到rflags
中设置的地址。
Linux和Windows的64位版本都使用IA32_LSTAR
。
总结一下,可以通过三种机制对内核进行控制:
syscall
,对于32位Windows,这是int 80h
。 int 2eh
。sysenter
。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实现为异常机制(中断)?
是