操作系统通过系统调用为运行于其上的进程提供服务。
当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。
举一个最简单的例子,应用进程需要输出一行文字,需要调用 write 这个系统调用:
hello_world.c
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *msg = "Hello, world!n";
write(1, msg, strlen(msg));
return 0;
}
注解
读者可能会有些疑问——输出文本不是用 printf 等函数吗?
确实是。 printf 是更高层次的库函数,建立在系统调用之上,实现数据格式化等功能。 因此,本质上还是系统调用起决定性作用。
调用流程
那么,在应用程序内,调用一个系统调用的流程是怎样的呢?
我们以一个假设的系统调用 xyz 为例,介绍一次系统调用的所有环节。

如上图,系统调用执行的流程如下:
应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ; 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核; CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call ); 系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;执行态切换
应用程序 ( application program )与 库函数 ( libc )之间, 系统调用处理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用,应该不难理解。 而 库函数 与 系统调用处理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。
Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独立的执行流,因此在切换时,需要准备 执行栈 并保存 寄存器 。
内核实现了很多不同的系统调用(提供不同功能),而 系统调用处理函数 只有一个。 因此,用户进程必须传递一个参数用于区分,这便是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般通过 eax 寄存器 来传递。
总结起来, 执行态切换 过程如下:
应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ; CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ; 系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现); 系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ; 系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ; 系统调用处理函数 执行 ret 指令切换回 用户态 ;







