系统调用

    在现代操作系统中,内核提供了用户进程与内核进程进行交互的一组接口,这些接口让应用程序受限的访问硬件设备,提供了创建新进程并与已有进程进行通信的机制也提供了申请操作系统资源的能力。这些接口在应用程序和内核之间扮演了使者的角色,应用程序发出各种请求,而内核负责满足这些请求。

与内核通信
    系统调用在用户空间进程和硬件设备之间添加了一个中间层,该层主要有三个目的:首先为用户空间提供了一种硬件的抽象接口。其次系统调用保证了系统的稳定和安全,作为硬件设备和应用程序的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行的访问进行裁决。最后每个进程都运行虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口。在linux中,系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核的唯一合法入口。

    一般情况下应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用对应,一个API定义了一组应用程序使用的编程接口,它们可以用一个系统调用实现,也可以用多个系统调用实现。在uninx世界中,最流行的应用编程接口是基于POSIX标准的,linux的系统调用同大多数unix一样,作为C库的一部分提供。

系统调用
    要访问系统调用,通常通过C库中定义的函数调用来进行,函数通常都需要0个或多个参数。系统调用最终具有一种明确的操作,定义中并没有规定它要如何实现,内核必须提供系统调用所希望完成的功能,但他完全可以按照自己的方式去实现,只要最后结构正确就行。在linux中,每个系统调用都被赋予一个系统调用号,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用时这个系统调用号就用来指明到底是要执行那个系统调用,进程不会提及系统调用的名称。系统调用号一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃,如果一个系统调用被删除,它所占用的系统调用号也不能被删除,否则以前编译过的程序在调用它时会调用到另一个系统调用。linux有一个未实现的系统调用sys_ni_syscall(),它除了返回-ENOSYS外不做任何其他工作,这个错误号就是针对无效的系统调用设计的,内核记录了系统调用表中所有已经注册过的系统调用表,存储在sys_call_table中。linux的系统调用比其他许多操作系统执行的要快,因为linux的上下文切换很迅速是一个重要原因。

系统调用处理程序
    应用程序以某种方式通知系统,告诉内核自己需要执行一个系统调用,系统切换到内核态,这样就可以代表应用程序在内核空间执行系统调用,这样的通知内核的机制是通过软中断实现的,通过引发一个异常来促使系统切换到内核态去执行异常处理程序,此时的异常处理程序就是系统调用处理程序。在x86系统上,预定义的软中断中断号是128,通过int $0x80指令触发该中断。这条指令会触发一个异常导致系统切换到内核态并且执行第128号异常处理程序,这个处理程序名称叫system_call()。

    因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的,因此必须把系统调用号一并传给内核,在x86上,系统调用号是通过eax寄存器传递给内核的,在陷入内核前,用户空间就把相应的系统调用号存入eax中,这样系统调用处理程序一旦运行就可以从eax中得到数据。system_call()函数通过将给定的系统调用号与NR syscalls做比较来检查其有效性,如果它大于等于NR syscalls,该函数就返回-ENOSYS,否则,就执行相应的系统调用程序。由于系统调用表中的表项是以64位类型存放,所以内核需要将给定的系统调用号乘以4,然后在表中查询其位置。除了系统调用号,大部分系统调用还需要传入参数,在发生陷入的时候,也应该把这些参数也传递给内核。



你可能感兴趣的:(linux,系统调用)