程序员的自我修养 - 系统调用及原理

什么是系统调用

系统调用是应用程序(包含运行库)与操作系统内核的接口,它决定了应用程序如何与内核打交道。在现在的操作系统系统里,程序运行的时候,本身是没有权利访问系统的资源,由于系统有限的资源有可能被不同的应用程序同时访问,因此,如果不加以保护,各个应用程序的冲突在所难免。所以现代操作系统都尽可能的把冲突的资源保护起来,阻止程序直接访问。这些资源,包括文件、网络、IO、各种设备等。

为了让应用程序有能力访问系统资源,每个操作系统都会提供一套接口,以供应用程序使用。这些接口往往通过终端的形式实现,比如Linux使用0x80号中断作为系统调用的入口,Windows采用0x2E号中断作为系统调用的入口。

Linux系统调用

在x86下,Linux的系统调用由0x80完成,各个通用寄存器用于传递参数,EAX寄存器用于表示系统调用的接口号,比如EAX=2表示创建进程(fork),每个系统调用都对应于内核源码中的一个函数,他们都以sys_开头。

系统调用原理

现代的CPU常常可以在多种不同的特权级下执行命令,在现在的操作系统中,通常有两种特权级,分为用户模式和内核模式,也称用户态和内核态。由于多种模式的存在,操作系统可以让不同的代码运行在不同的模式上,提高稳定性和安全性。

系统调用时运行在内核态的,而应用程序一般是运行在用户态。用户态的程序如何运行内核态的代码呢?操作系统一般是通过中断来从用户态切换到内核态。中断是一个硬件或者软件发出请求,请求CPU暂停当前的工作去处理更加重要的事情。

中断一般具有两个属性,一个称为中断号,一个称为中断处理程序(Interrupt Service Routine,ISR)。不同的中断具有不同的中断号,而同时一个中断处理程序一一对应一个中断号。在内核中,有一个数组称为中断向量表(interrupt vector table),这个数组的第n项包含了指向第n号中断处理程序的指针。当中断到来时,CPU回暂停执行当前的行的代码,根据中断的中断号,在中断向量表中找到对应的中断处理程序,并调用它。

通常意义上,中断有两种类型,一种称为硬件中断,这种中断来自硬件的异常或者事件的发生,入电源掉电、键盘被按下等。另外一种称为软件中断,软件中断通常是一条指令(i386下是int),带有一个参数记录中断号。和中断一样,系统调用都有一个系统调用号,表明是哪一个系统调用,这个系统调用号通常就是系统调用在系统调用表中的位置,这个系统调用号在执行int之前会被放置到某个固定的寄存器里面。比如Linux的int 0x80,系统调用号是放在eax,然后使用int 0x80调用中断,从eax取得系统调用号。

image

切换堆栈

在实际执行中断向量表中的第0x80号元素所对应的函数之前,CPU首先还要进行栈的切换。在Linux中,用户态和内核态使用不同的栈,两者各自负责各自的函数调用,互不干扰。但是在程序调用 0x80号中断时,程序的执行流程从用户态切换到内核态,这时候程序的当前栈必须也从用户栈切换到内核栈。返回时同理。

所谓的当前栈,指的是ESP的值所在的栈空间,如果ESP的值位于用户栈的范围内,那么当前程序的栈就是用户栈,反之亦然,此外,寄存器SS的值还应该指向当前栈所在的页。所以,将当前栈由用户栈切换到内核栈的实际行为就是:

  • 保存当前ESP,SS的值
  • 将ESP,SS的值设置成内核栈的值

当0x80寄存器发生的时候,CPU除了进入内核态以外,还会自动完成以下几件事:

  • 找到当前进程的内核栈(每个进程都有自己的内核栈)
  • 在内核栈中依次压入用户态的寄存器SS,ESP,EFLAGS,CS,EIP

返回时用这些地址和数据返回。

Linux新型系统调用机制

由于基于int指令的系统调用在奔腾4处理器上性能不佳,Linux2.5版本开始支持一种新的系统调用机制。这种机制使用奔腾2带开始提供的一组专门针对系统调用的指令-syscenter/sysexit。

调用syscenter之后,系统会直接跳转到由某个寄存器制定的函数执行,并自动完成特定特权级的转换,堆栈切换等功能。

在参数传递方面,新型的系统调用和int 的系统调用完全相同,仍然使用EBX..EDX,ESI,EDI,EBP这六个寄存器。

分析过程:
ldd获取共享库,找到linux-gate.so.1,也就是地址0xffffe0000被映射到vdso,可以把文件的这个段单独导出到一个文件里分析,vdso导出一系列函数,其中 _kernel_vsyscall_ 中使用了 syscenter系统调用,从而完成系统调用。

-->