在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口。
这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其他资源的能力。
应用程序发出各种请求,而内核负责满足这些请求(或者无法满足时返回一个错误)。
实际上提供这些接口主要是为了保证系统稳定可靠,避免应用程序恣意妄行。
系统调用在用户空间进程和硬件设备之间添加了一个中间层。
该层主要作用有三个。
首先,它为用户空间提供了一种硬件的抽象接口。
举例来说,当需要读写文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型。
系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行的访问进行裁决。
举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他危害系统的事情。
在Linux中,系统调用就是用户空间访问内核的唯一手段;除异常和陷入外,它们是内核唯一的合法入口。
一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。
这点很重要,因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用对应。
一个API定义了一组应用程序使用的编程接口。
实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异。
在Unix世界中,最流行的应用编程接口是基于POSIX标准的。
从纯技术的角度看,POSIX是由IEEE的一组标准组成,其目标是提供一套大体上基于Unix的可移植操作系统标准。
要访问系统调用(在Linux中常称作syscall),通常通过C库中定义的函数调用来进行。
它们通需要定义零个、一个或几个参数,而且可能产生一些副作用。
通常,用一个负的返回值来表明错误。返回一个0值通常表明成功。
系统调用在出现错误的时候C库会把错误码写入errno全局变量。
通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。
在Linux中,每个系统调用被赋予一个系统调用号。
这样,通过这个独一无二的号就可以关联系统调用。
当用户空间的进程执行一个系统调用的时候,这个系统调用号就用来指明到底是要执行哪个系统调用。
系统调用号想当重要,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。
此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,否则,以前编译过的代码会调用这个系统调用,但事实上却调用的是另一个系统调用、
Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回-ENOSYS外不做任何其它工作,这个错误号就是专门针对无效的系统调用而设的。
虽然很罕见,但如果一个系统调用被删除,或者变得不可用,这个函数就要负责“填补空缺”。
内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在sys_call_table中。这个表为每一个有效的系统调用指定了唯一的系统调用号。
Linux系统调用比其它许多操作系统执行得要快。
Linux很短的上下文切换时间是一个重要原因,进出内核都被优化得简洁高效。
用户空间的程序无法直接执行内核代码。
它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。
如果进程可以直接在内核的地址空间上读写的话,系统的安全性和稳定性将不复存在。
所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。
通知内核的机制是靠软中断实现的。
通过引发一个异常来促使系统切换到内核态去执行异常处理程序。
此时的异常处理程序实际上就是系统调用处理程序。
system_call()