系统调用

X86系统调用

Posted by 邹盛富 on June 6, 2018

系统调用、库函数、API、内核函数

系统调用、库函数、API、内核函数的关系图如下:

应用程序可以直接调系统调用,但是通常情况下,应用程序都是通过C函数库或者是API的接口来间接地调用系统调用,在操作系统内核当中,提供了很多的内核函数,这些内核函数通过封装实际上把它提供到了C函数库或者是 API接口,所以系统调用对于内核而言,内核函数就是这个系统调用的处理程序,这些处理程序通过封装在C函数库或者API接口呢提供给用户来使用。但是,C函数库里或者是API接口里还有一些函数不是系统调用,就是一些普通的函数在完成一些功能,而且一些函数通过系统调用多个内核函数,当然也可能是某一个函数通过系统调用呢对应内核的一个函数。

参数传递

用户程序在执行时是用户栈,一般的函数调用是通过这个栈来传递参数的, 但是现在面临的问题是用户态和系统态,用户程序不能够把它的这些参数推到系统栈里头去,这是不允许的,调用函数的参数传递给内核通常有三种方法:

  • 由陷入指令自带参数:陷入指令的长度有限,且还要携带系统调用功能号,只能自带有限参数
  • 通过通用寄存器传递参数:通用寄存器是操作系统和用户都能访问的,但是寄存器的个数会限制参数的数量
  • 在内存中开辟专门的堆栈存储参数

执行过程

当CPU执行到了特殊的陷入指令的时:

  • 硬件的中断/异常机制工作:保存现场,查找中断向量表,并且把CPU的控制权转交给中断处理程序,这个中断处理程序叫做系统调用的一个总入口程序。因为所有的系统调用,都是通过这个中断向量进来,都是执行这个总的入口程序
  • 总入口程序:也要保存现场,把参数保存在内核的堆栈当中,查找系统调用表,把控制权交给对应的内核函数,或者是系统调用的处理程序
  • 执行系统调用的过程
  • 再恢复现场,返回用户程序

基于X86系统调用Linux系统调用

陷入指令

int $0x80  /*系统调用的一种指令 int*/

门描述符初始化

对中断描述符表中的128号门进行初始化,过程如下:

  • 门描述符的从右边数的2、3两个字节设置成段选择符
  • 把0、1、6、7这4个字节设置成了一个偏移量
  • 设置门的类型为陷阱门,因为在系统调用的执行过程中允许接受中断
  • 特权级设置为与用户级别相同,因为要允许用户进程调用系统调用,则用户进程的特权必须高于或者等于系统调用的特权

int $0x80过程

  • 特权级发生了改变,进行切换栈,CPU从任务状态段TSS表当中装入新的栈指针,指向内核栈
  • 硬件自动依次地把用户栈的信息SS:ESP标志状态字,标志寄存器的信息EFLAGS还有返回的地址用户态的CS和EIP寄存器的内容依次压栈
  • 把EFLAGS(包含一组状态标志、系统标志以及一个控制标志)压完栈之后,就复位TF位(单步标志位),IF位(中断允许位)保持不变,保持不变
  • 硬件用128在中断描述符表中找到初始化好的门描述符,从中取到段选择符,装到了代码段寄存器CS当中
  • 代码段描述符当中的基地址和陷阱门描述符当中的偏移,由这两部分就能够定位我们系统调用的一个总的入口地址