这很简单——人们可以使用门铃让内核知道它在外面等着。在我们的例子中,这个门铃通常是一个特殊的 CPU 异常、软件中断或允许用户空间应用程序使用并且内核可以处理的专用指令。
所以程序是这样的:
首先你需要知道系统调用号。每个系统调用都有其唯一的编号,并且内核内部有一个表格,将这些编号映射到特定的函数。每个体系结构对于相同的编号可以有不同的表条目。在两种不同的架构上,相同的编号可能会映射到不同的系统调用。
然后你设置你的论点。这也是特定于架构的,但与在通常的函数调用之间传递参数没有太大区别。通常,您会将参数放在特定的 CPU 寄存器中。这在此架构的 ABI 中进行了描述。
然后你输入系统调用。根据体系结构,这可能意味着导致一些异常或执行专用 CPU 指令。
内核具有特殊的处理函数,在调用系统调用时在内核模式下运行。它将暂停进程执行,存储特定于该进程的所有信息(这称为context switch
),读取系统调用号和参数并调用正确的系统调用例程。它还将确保将返回值放在适当的位置以供用户空间读取,并在系统调用例程完成时安排进程返回(恢复其上下文)。
例如,要让内核知道您想在 x86_64 上调用 syscall,您可以在寄存器中使用sysenter
带有 syscall 编号的指令。%rax
使用寄存器传递参数(如果我没记错的话)%rdi
、、、、和。%rsi
%rdx
%rcx
%r8
%r9
您还可以使用在 32 位 x86 CPU 上使用的旧方式 - 软件中断号 0x80(int 0x80
指令)。同样,系统调用号在%rax
寄存器中指定,参数转到(再次,如果我没记错的话)%ebx
, %ecx
, %edx
, %esi
, %edi
, %ebp
.
ARM 非常相似 - 您将使用“主管调用”指令 ( SVC #0
)。您的系统调用号将进入r7
寄存器,所有参数将进入寄存器r0-r6
,系统调用的返回值将存储在r0
.
其他架构和操作系统使用类似的技术。细节可能会有所不同 - 软件中断号可能不同,参数可能使用不同的寄存器甚至使用堆栈传递,但核心思想是相同的。