系统调用、库函数、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当中
- 代码段描述符当中的基地址和陷阱门描述符当中的偏移,由这两部分就能够定位我们系统调用的一个总的入口地址