知识点

系统调用

定义

应用通常通过系统调用请求内核提供文件、网络等服务。具体接口和进入内核的机制取决于操作系统与体系结构。这种“用户态程序主动请求内核执行特权操作”的机制就是系统调用。内核给每项服务编一个系统调用号,调用方把号和参数放进约定的寄存器,内核据此分发到对应的处理函数。

系统调用是同步事件:它在执行到陷入指令的那一刻发生,与外部设备是否就绪无关。32位x86 Linux用int 0x80陷入,x86-64用syscall陷入。

性质

以32位x86 Linux的int 0x80为例,寄存器的分工是固定的:

寄存器用途
EAX系统调用号,例如exit为1,write为4
EBX、ECX、EDX、ESI、EDI、EBP依次存放最多6个参数
EAX(返回时)返回值;失败时是负的错误码
  1. 准备参数

    应用程序或C库把系统调用号写入EAX,把参数写入EBX、ECX等寄存器。以write为例,三个参数分别是文件描述符、缓冲区地址和要写入的字节数。

  2. 触发陷入

    执行int 0x80,产生第128号中断(十六进制0x80等于十进制128)。这个中断在IDT(中断描述符表)中的门允许Ring 3的代码进入,所以用户态可以合法发起。

  3. 切换特权级并跳转

    CPU按IDT找到0x80对应的处理程序,保存用户态的CS、EIP、EFLAGS和栈指针,把CPL从3改为0,跳转到内核预定义的system_call入口。

  4. 内核分发并执行

    内核按EAX中的调用号查系统调用表,进入sys_write等具体实现。执行期间内核拥有完整的硬件控制权,可以访问设备寄存器和内核数据结构。

  5. 返回用户态

    内核把返回值或错误码写回EAX,执行iret恢复之前保存的CS、EIP、EFLAGS和栈,CPL回到3。程序从int 0x80的下一条指令继续执行。

现代指令

int 0x80要走完整的中断门流程:查IDT、检查门权限、保存现场,通常需要更多通用中断门处理步骤。AMD64的syscall和Intel的sysenter通过专门配置的寄存器直接进入内核入口,采用不同的专用入口机制。它们做的仍然是同一件事:用户态按约定放好调用号和参数,一条指令陷入,内核执行,再返回。

x86-64的约定与32位不同:调用号放在RAX,参数依次放在RDI、RSI、RDX、R10、R8、R9,返回值放在RAX,可使用sysret等返回路径,实际返回方式取决于内核实现。

例子

用int 0x80调用write

下面的片段把字符串写到标准输出,缓冲区地址用msg表示:

write(1, msg, 5) via int 0x80
mov eax, 4 ; 系统调用号:sys_write
mov ebx, 1 ; 参数1:文件描述符1是标准输出
mov ecx, msg ; 参数2:缓冲区地址
mov edx, 5 ; 参数3:写入5个字节
int 0x80 ; 陷入内核,返回后EAX中是实际写入的字节数

sys_write执行完后,EAX中存放实际写入的字节数;出错时是一个负的错误码。用户态代码不需要知道页表怎么改、网卡寄存器在哪,它只负责按约定放好号和参数。

printf与系统调用是什么关系

printf是C标准库函数,不是系统调用。它先在用户态的缓冲区里把格式串和参数拼成文本,缓冲区满了或程序主动刷新时才调用write系统调用,把数据交给内核。因此一次printf可能对应零次、一次或多次系统调用,取决于缓冲区当时的状态。

交互探索

以写文件请求为例,逐步查看进入内核、处理请求与返回的过程。

系统调用怎样进入内核并返回?

逐步查看应用请求、内核处理和返回值。

正在准备演示…

题集

练习

  1. int 0x80触发的第128号中断中,0x80对应的十进制中断号是 。

    参考解答

    十六进制0x80等于 8×16=1288 \times 16 = 128。

  2. 在32位x86 Linux的int 0x80约定中,系统调用号和前三个参数分别放在哪里?

    选择答案
    参考解答

    调用号放EAX,参数依次用EBX、ECX、EDX、ESI、EDI、EBP;返回值也写回EAX。

  3. 执行int 0x80后,CPU如何找到内核中的处理程序?

    选择答案
    参考解答

    中断号是IDT的下标,CPU用0x80找到登记的处理程序入口并切换特权级;用户态不能自己跳到内核地址。