0%

Linux系统调用的实现

Linux系统结构

Linux系统结构如图所示,位于最内层的kernel管理计算机的硬件资源,提供硬件访问、内存管理、进程调度、文件管理、用户管理和网络等系统服务。为了保护系统,进程不能直接访问内核的内存空间和内核函数,当进程需要内核提供的服务时,通过内核与外界的接口(也就是系统调用)来实现。

Linux系统结构图

用户态和内核态

进程运行的状态可以分为内核态和用户态,当进程处于内核态时,它可以访问内核的内存空间和硬件资源;当进程处于用户态时,进程只能访问用户空间的内存空间,不能访问内核内存空间和硬件资源,不能关闭中断。用户态到内核态的切换有三种方式:

  1. 系统调用,当用户态通过系统调用申请使用系统服务时,进程由用户态切换到内核态
  2. 异常,当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  3. 外围设备中断,当外围设备完成用户请求的操作时,会向CPU发送相应的中断信号,如果此时进程处于用户态,将切换到内核态。

系统调用的实现

系统调用通过软件中断的方式实现,每个系统调用都有相应的系统调用号作为唯一的标识,内核维护一张系统调用表,表中的元素是系统调用函数的起始地址,而系统调用号就是系统调用在调用表的偏移量。在进行系统调用是只要指定对应的系统调用号,就可以明确的要调用哪个系统调用。对于参数传递,Linux是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,分别依次由%ebx,%ecx,%edx,%esi,%edi和%ebp这个6个寄存器完成。

在用户态和内核态运行的进程使用的栈是不同的,分别叫做用户栈和内核栈,两者各自负责相应特权级别状态下的函数调用。当进行系统调用时,进程不仅要从用户态切换到内核态,同时也要完成栈切换,这样处于内核态的系统调用才能在内核栈上完成调用。系统调用返回时,还要切换回用户栈,继续完成用户态下的函数调用。

寄存器%esp(栈指针,指向栈顶)所在的内存空间叫做当前栈,比如%esp在用户空间则当前栈就是用户栈,否则是内核栈。栈切换主要就是%esp在用户空间和内核空间间的来回赋值。在Linux中,每个进程都有一个私有的内核栈,当从用户栈切换到内核栈时,需完成保存%esp以及相关寄存器的值(%ebx,%ecx…)并将%esp设置成内核栈的相应值。而从内核栈切换会用户栈时,需要恢复用户栈的%esp及相关寄存器的值以及保存内核栈的信息。一个问题就是用户栈的%esp和寄存器的值保存到什么地方,以便于恢复呢?答案就是内核栈,在调用int指令机型系统调用后会把用户栈的%esp的值及相关寄存器压入内核栈中,系统调用通过iret指令返回,在返回之前会从内核栈弹出用户栈的%esp和寄存器的状态,然后进行恢复。

相信大家一定听过说,系统调用很耗时,要尽量少用。第一,系统调用通过中断实现,需要完成栈切换。第二,使用寄存器传参,这需要额外的保存和恢复的过程。

参考资料

http://blog.csdn.net/chosen0ne/article/details/7721550