13.2.3 系统调用接口

如果一个人相信Corbató的机制最少化的格言,那么操作系统应该提供恰好够用的系统调用,并且每个系统调用都应该尽可能简单(但不能过于简单)。统一的数据范型在此处可以扮演重要的角色。例如,如果文件、进程、I/O设备以及更多的东西都可以看作是文件或者对象,那么它们就都能够用单一的read系统调用来读取。否则,可能就有必要具有read_file、read_proc以及read_tty等单独的系统调用。

在某些情况下,系统调用可能看起来需要若干变体,但是通常更好的实现是具有处理一般情况的一个系统调用,而由不同的库过程向程序员隐藏这一事实。例如,UNIX具有一个系统调用exec,用来覆盖一个进程的虚拟地址空间。最一般的调用是:


exec(name,argp,envp);

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元


该调用加载可执行文件name,并且给它提供由argp所指向的参数和envp所指向的环境变量。有时明确地列出参数是十分方便的,所以库中包含如下调用的过程:


execl(name,arg0,arg1,...,argn,0);

execle(name,arg0,arg1,...,argn,envp);


所有这些过程所做的事情是将参数粘连在一个数组中,然后调用exec来做工作。这一安排达到了双赢目的:单一的直接系统调用使操作系统保持简单,而程序员得到了以各种方法调用exec的便利。

当然,试图拥有一个调用来处理每一种可能的情况很可能难以控制。在UNIX中,创建一个进程需要两个调用:fork然后是exec,前者不需要参数,后者具有3个参数。相反,创建一个进程的Win32 API调用CreateProcess具有10个参数,其中一个参数是指向一个结构的指针,该结构具有另外18个参数。

很久以前,有人曾经问过这样的问题:“如果我们省略了这些东西会不会发生可怕的事情?”诚实的回答应该是:“在某些情况下程序员可能不得不做更多的工作以达到特定的效果,但是最终的结果将会是一个更简单、更小巧并且更可靠的操作系统。”当然,主张10+18个参数版本的人可能会说:“但是用户喜欢所有这些特性。”对此的反驳可能会是:“他们更加喜欢使用很少内存并且从来不会崩溃的系统。”在更多功能性和更多内存代价之间的权衡是显而易见的,并且可以从价格上来衡量(因为内存的价格是已知的)。然而,每年由于某些特性而增加的崩溃次数是难于估算的,并且如果用户知道了隐藏的代价是否还会做出同样的选择呢?这一影响可以在Tanenbaum软件第一定律中做出总结:

添加更多的代码就是添加更多的程序错误。

添加更多的功能特性就要添加更多的代码,因此就要添加更多的程序错误。相信添加新的功能特性而不会添加新的程序错误的程序员要么是计算机的生手,要么就是相信牙齿仙女(据说会在儿童掉落在枕边的幼齿旁放上钱财的仙女)正在那里监视着他们。

简单不是设计系统调用时出现的惟一问题。一个重要的考虑因素是Lampson(1984)的口号:

不要隐藏能力。

如果硬件具有极其高效的方法做某事,它就应该以简单的方法展露给程序员,而不应该掩埋在某些其他抽象的内部。抽象的目的是隐藏不合需要的特性,而不是隐藏值得需要的特性。例如,假设硬件具有一种特殊的方法以很高的速度在屏幕上(也就是视频RAM中)移动大型位图,正确的做法是要有一个新的系统调用能够得到这一机制,而不是只提供一种方法将视频RAM读到内存中并且再将其写回。新的系统调用应该只是移动位而不做其他事情。如果系统调用速度很快,用户总可以在其上建立起更加方便的接口。如果它的速度慢,没有人会使用它。

另一个设计问题是面向连接的调用与无连接的调用。读文件的标准UNIX系统调用和Win32系统调用是面向连接的。首先你要打开一个文件,然后读它,最后关闭它。某些远程文件访问协议也是面向连接的。例如,要使用FTP,用户首先要登录到远程计算机上,读文件,然后注销。

另一方面,某些远程文件访问协议是无连接的,例如Web协议(HTTP)。要读一个Web页面你只要请求它就可以了;不存在事先建立连接的需要(TCP连接是需要的,但是这处于协议的低层;访问Web本身的HTTP协议是无连接的)。

任何面向连接的机制与无连接的机制之间的权衡在于建立连接的机制(例如打开文件)要求的额外开销,以及在后续调用(可能很多)中避免进行连接所带来的好处。对于单机上的文件I/O而言,由于建立连接的代价很低,标准的方法(首先打开,然后使用)可能是最好的方法。对于远程文件系统而言,两种方法都可以采用。

与系统调用接口有关的另一个问题是接口的可见性。POSIX强制的系统调用列表很容易找到。所有UNIX系统都支持这些系统调用,以及少数其他系统调用,但是完全的列表总是公开的。相反,Microsoft从未将Windows Vista系统调用列表公开。作为替代,Win32 API和其他API被公开了,但是这些API包含大量的库调用(超过10 000个),只有很少数是真正的系统调用。将所有系统调用公开的论据是可以让程序员知道什么是代价低廉的(在用户空间执行的函数),什么是代价昂贵的(内核调用)。不将它们公开的论据是这样给实现提供了灵活性,无须破坏用户程序就可以修改实际的底层系统调用,以便使其工作得更好。