(五)系统调用
接口
在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口。这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其他资源的能力。应用程序发出各种请求,而内核负责满足这些请求,实际上提供这些接口主要是为了保证系统稳定可靠,避免应用程序恣意妄行。
与内核通信
系统调用可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他危害系统的事情。在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,它们是内核唯一的合法入口。
API、POSIX、C库
一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。
API:一个API定义了一组应用程序使用的编程接口,它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。
POSIX:在Unix世界中,最流行的应用编程接口是基于POSIX标准的。在大多数Unix系统上,根据POSIX定义的API函数和系统调用之间有着直接关系。
C库:Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供。C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。此外,C库提供了POSIX的绝大部分API。
系统调用
要访问系统调用,通常通过C库中定义的函数调用来进行。
在Linux中,每个系统调用被赋予一个系统调用号,这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称。
如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用。
系统调用处理程序
用户空间的程序无法直接执行内核代码,它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。
应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。
通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序,此时的异常处理程序实际上就是系统调用处理程序。
用户空间引起异常或陷入内核就是一个重要的概念。
仅仅陷入内核空间是不够的,还必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。参数传递也一样,以存放到寄存器的方式从用户空间传给内核。
系统调用的实现
提供机制而不是策略
通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用。但如果你仅仅写出系统调用,glibc库不提供支持,Linux本身会提供一组宏,用于直接对系统调用进行访问。这些宏是_syscalln(),其中n的范围从0到6,代表需要传递给系统调用的参数个数。它会设置好寄存器并调用陷入指令。