本节围绕系统调用展开,主要讲解了系统调用的概念与实现、程序调用与系统调用的不同、系统调用的开销。
概念:
首先看一个标准C库的例子:当我们程序中使用了C库中的printf()函数,实际在底层是在内核态中调用了write()函数。图中右侧则是将程序代码与C库都算到应用程序中,内核提供了一个系统调用接口。从这个例子我们可以得到以下几点:1. 系统调用是操作系统服务的编程接口;2. 系统调用通常由高级语言编写(C或者C++);3. 程序访问通常是通过高层次的API接口(比如C库)而不是直接进行系统调用。其中第三条提到的编程接口库中最常见的三种:1. Windows下的Win32 API;2. POSIX-based系统(包括UNIX、LINUX、Mac OS等)下的 POSIX API;3. JAVA虚拟机中的Java API。
实现:
每个系统调用对应一个系统调用编号。当使用系统调用时,系统调用首先通过软中断的方式进入到内核的中断向量表产生中断,发现是系统调用软中断后转移到系统调用表,系统调用表中记录系统调用编号与具体实现之间的映射关系,根据系统调用编号选取不同的系统调用实现,得到结果之后返回。通过这种方式,用户不需要知道系统调用内部是如何实现的,而只需要设置调用参数和湖区返回结果即可,并且系统调用接口的细节大部分都隐藏函数库后面,通过调用库函数实现。
函数调用与系统调用的不同:
使用的指令不同。系统调用使用INT和IRET,函数调用使用CALL和RET,他们的指令级是完全不同的。具体还有哪些不同呢?我们知道在调用一个函数的时候需要把参数压到堆栈中去,然后转到相应函数去执行,执行的时候从堆栈获取我的参数信息执行,然后返回结果。而对于系统调用来讲,内核是受保护的,为了保护内核,内核与应用程序之间使用不同的堆栈,因此系统调用时会有一个堆栈和特权级的切换,首先切换到内核态,此时可以使用特权指令,并拥有自己的堆栈,执行完后再切换回用户态。而常规调用是没有堆栈切换的。
开销:
系统调用比函数调用更安全,但是开销更大。主要原因就是有一个用户态的切换。具体有以下操作导致开销更大:1. 引导机制,需要引导用户态到内核态的切换;2. 建立内核堆栈,第一次调用时需要创建新的内核堆栈