在Linux系统中,系统调用基本上是应用程序与内核交互的唯一方式。而所有的外设驱动都由内核管理,因此系统调用基本上也是应用程序与外设交互的唯一方式。
Linux的系统调用有两个视角:应用程序和内核。从应用程序的角度,系统调用表现为库函数的形式,遵循POSIX标准,存放在多个库文件中,包括C标准库。从内核的角度,则表现为从中断处理程序开始的一系列处理。
应用程序和内核的切换
为了更好地保护系统,避免在多用户环一般境中由于应用程序的问题导致整个系统的崩溃,Linux采用CPU的两个级别来分别运行内核和应用程序:特权级用于运行内核;用户级用于运行应用程序。在x86系列的芯片中,RING0用于内核,RING3用于应用程序(还有两个级别RING1、RING2,Linux系统没有使用)。因此应用程序和内核的切换本质上是特权级和用户级的切换,这种切换必须通过硬件实现。在早期的x86系统中,采用软件终端int 0x80实现,而现在则有专用的指令:syscall/sysret(x64系统)、sysenter/sysexit(x86系统)。
内核内的系统调用过程
在内核中,系统调用首先被中断处理程序俘获,这部分实现与硬件架构相关,在arch/目录下。x86相关的代码在arch/x86下。由于这一部分与架构密切相关,主要以x64系统为例介绍。另外,内核版本不同,实现上也有差别,下面的介绍以4.8.1内核为参考。
在arch/x86/entry中,有三个汇编文件:entry_32.S,entry_64.S,entry_64_compat.S。分别用于32系统、64系统和兼容模式(64位系统运行32位程序)的中断入口处理,其中也包含系统调用的入口entry_SYSCALL_64。在用户模式和内核模式之间传递参数,都必须使用寄存器,在x64系统中使用以下几个寄存器:
- rax:系