1.系统调用的概念
为了和用户空间上运行的进程进行交互,内核提供了一组借口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组借口在应用程序和内核之间扮演着使者的角色。同时,这组接口也保证了系统稳定可靠,避免应用程序肆意妄行,惹出麻烦。Linux系统的系统调用作为C库的一部分提供,其调用过程中的实例如下图所示:
从程序员的角度看,系统调用无关紧要,他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道,库函数以及应用程序是怎么使用系统调用不是内核所关心的。
2.系统调用的处理程序
用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统就会变得不安全。
所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态。这是通过软中断实现的:通过引发一个异常来促使系统切换到内核态去指向异常处理程序,而此时的异常处理程序就是系统调用的处理程序。x86系统上的软中断是由int $0x80指令产生。这条指令会触发一个异常导致系统切换到内核太并执行第128号异常处理程序,而该程序正是系统调用处理程序。这个处理程序名字起的很贴切,叫system_call()。
因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的,必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应的系统调用号存在eax中。这样系统调用处理程序一旦运行,就可以从eax中得到数据。执行read()系统调用的实例如下:
除了系统调用号以外,大部分系统调用还需要一些外部参数输入。在x86系统上,ebx、ecx、edx、esi和edi按照顺序存放前五个参数(需要六个或六个以上参数的情况不多)。给用户空间的返回值也寄存器来实现。
3.系统调用上下文
内核在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。在进程上下文中,内核可以休眠并且可以被抢占。这表明即使是在内核空间中,当前进程也可以被其他进程抢占。因为新的进程可以执行相同的系统调用,所以必须保证系统调用是可重入的。当系统调用返回时,控制权仍然在system_call()中,它最终会负责切换到用户空间并让用户继续执行下去。