1.前言
现代计算机系统由一个或多个处理器、主存、磁盘、打印机、键盘、鼠标、显示器、网络接口以及各种其他输入/输出设备组成。一般而言,现代计算机系统是一个复杂的系统。如果每位应用程序员都不得不掌握系统的所有细节,那就不可能再编写代码了。而且,管理这些部件并加以优化使用,是一件挑战性极强的工作。
所以,计算机安装了一层软件,称为操作系统,它的任务是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理刚才提到的所有设备。
多数读者都会对诸如Windows、Linux、FreeBSD或OS X等某个操作系统有些体验,但表面现象是会骗人的。用户与之交互的程序,基于文本的通常称为shell,而基于图标的则称为图形用户界面(Graphical User Interface,GUI),它们实际上并不是操作系统的一部分,尽管这些程序使用操作系统来完成工作。
图1-1给出了这里所讨论的主要部件的一个简化视图。
图的底部是硬件。硬件包括芯片、电路板.磁盘、键盘、显示器以及类似的设备。在硬件的顶部是软件。
多数计算机有两种运行模式:内核态和用户态。
- 软件中最基础的部分是操作系统,它运行在内核态(也称为管态、核心态)。在这个模式中,操作系统具有对所有硬件的完全访问权,可以执行机器能够运行的任何指令。
- 软件的其余部分运行在用户态下。在用户态下,只使用了机器指令中的一个子集。特别地,那些会影响机器的控制或可进行10(输入/输出)操作的指令,在用户态中的程序里是禁止的。
在后续的文章中,我们会不断地讨论内核态和用户态之间的差别,这些差别在操作系统的运作中扮
演着极其重要的角色。
用户接口程序(shell或者GUI)处于用户态程序中的最低层次,允许用户运行其他程序,诸如Web浏览器、电子邮件阅读器或音乐播放器等。这些程序也大量使用操作系统。
操作系统所在的位置如图1-1所示。它运行在裸机之上,为所有其他软件提供基础的运行环境。
操作系统和普通软件(用户态)之间的主要区别是,如果用户不喜欢某个特定的电子邮件阅读器,他可以自由选择另一个,或者自己写一个,但是不能自行写一个属于操作系统一部分的时钟中断处理程序。这个程序由硬件保护,防止用户试图对其进行修改。
然而,有时在嵌入式系统(该系统没有内核态)或解释系统(如基于Java的操作系统,它采用解释方式而非硬件方式区分组件)中,上述区别是模糊的。
另外,在许多系统中,一些在用户态下运行的程序协助操作系统完成特权功能。例如,经常有一个程序供用户修改其口令之用。但是这个程序不是操作系统的一部分,也不在内核态下运行,不过它明显地带有敏感的功能,并且必须以某种方式给予保护。在某些系统中,这种想法被推向了极致,一些传统上被认为是操作系统的部分(诸如文件系统)在用户空间中运行。在这类系统中,很难划分出一条明显的界限。在内核态中运行的当然是操作系统的一部分,但是一些在内核外运行的程序也有争议地被认为是操作系统的一部分,或者至少与操作系统密切相关。
操作系统与用户(即应用)程序的差异并不仅仅在于它们所处的地位。特别地,操作系统更加大型、复杂和长寿。Linux或Windows操作系统的源代码有500万行甚至更高的数量级。要理解这个数量的含义,请考虑具有500万行的一套书,每页50行,每卷1000页(比本书厚)。为了以书的大小列出一个操作系统,需要有100卷书——基本上需要一整个书架来摆放。请设想一下有个维护操作系统的工作,第一天老板带你到装有代码的书架旁,说:“去读吧。”而这仅仅是运行在内核中的部分代码。当包括重要的共享库时,Windows将有超过7000万行代码,或者说要用10~20个书架,而且这还不包括一些基础的应用(如Windows ExplorerWindows Media Player等)。
至于为什么操作系统的寿命较长,读者现在应该清楚了——操作系统是很难编写的。一旦编写完成,操作系统的所有者当然不愿意把它扔掉,再写一个。相反,操作系统会在长时间内进行演化。基本上可以把Windows 95/98/Me看成是一个操作系统,而Windows NT/2000/XP/Vista/Windows 7则是另外一个操作系统。
对于用户而言,它们看上去很像,因为微软公司努力使Windows 2000/XP/Vista/Windows7与被替代系统(如Windows 98)的用户界面看起来十分相似。无论如何,微软公司要舍弃Windows98是有非常正当的原因的,大家感兴趣的话可以自己去了解。
除了Windows以外,UNIX当然也演化了多年,如System V版、Solaris以及FreeBSD等都是来源于UNIX的原始版;不过尽管Linux非常像依照UNIX模式仿制而成,并且与UNIX高度兼容,但是Linux具有全新的代码基础。本书将采用来自UNIX中的示例,并在第10章中具体讨论Linux。
2.什么是操作系统
很难给出操作系统的准确定义。
操作系统是一种运行在内核态的软件——尽管这个说法并不总是符合事实。部分原因是操作系统有两个基本上独立的任务,即为应用程序员(实际上是应用程序)提供一个资源集的清晰抽象,并管理这些硬件资源,而不仅仅是一堆硬件。
另外,还取决于从什么角度看待操作系统。读者多半听说过其中一个或另一个的功能。下面我们逐项进行讨论。
2.1 作为扩展机器的操作系统
在机器语言一级上,多数计算机的体系结构(指令集、存储组织、I/O和总线结构)是很原始的,而且编程是很困难的,尤其是对输入/输出操作而言。
为了更细致地考察这一点,我们以大多数电脑使用的更现代的SATA(Serial ATA)硬盘为例。曾有一本描述早期版本硬盘接口(程序员为了使用硬盘而需要了解的东西)的书(Anderson,2007),它的页数超过450页。自2007年起,接口又被修改过很多次,因而比当时更加复杂。显然,没有任何理智的程序员想要在硬件层面上和硬盘打交道。
相反,他们使用一些叫作硬盘驱动(disk driver)的软件来和硬件交互。这类软件提供了读写硬盘块的接口,而不用深入细节。操作系统包含很多用于控制输入/输出设备的驱动。 但就算是在这个层面,对于大多数应用来说还是太底层了。因此,所有的操作系统都提供使用硬盘的又一层抽象:文件。使用该抽象,程序能创建、读写文件,而不用处理硬件实际工作中那些恼人的细节。
抽象是管理复杂性的一个关键。好的抽象可以把一个几乎不可能管理的任务划分为两个可管理的部分。其第一部分是有关抽象的定义和实现,第二部分是随时用这些抽象解决问题。几乎每个计算机用户都理解的一个抽象是文件,正如上文所提到的。文件是一种有效的信息片段,诸如数码照片、保存的电子邮件、歌曲或Web页面等。处理数码照片、电子邮件、歌曲以及Web页面等,要比处理SATA(或者其他)硬盘的细节容易,这些磁盘的具体细节与前面叙述过的软盘一样。操作系统的任务是创建好的抽象,并实现和管理它所创建的抽象对象。本书中,我们将研究许多关于抽象的内容,因为这是理解操作系统的关键。
上述观点是非常重要的,所以值得用不同的表达方式来再次叙述。即使怀着如此小心翼翼对设计Macintosh机器的工业设计师的尊重,还是不得不说,硬件是丑陋的。真实的处理器、内存条、磁盘和其他装置都是非常复杂的,对于那些为使用某个硬件而不得不编写软件的人们而言,他们使用的是困难、可怕、特殊和不一致的接口。有时这是由于需要兼容旧的硬件,有时是为了节省成本,但是,有时硬件设计师们并没有意识到(或在意)他们给软件设计带来了多大的麻烦。操作系统的一个主要任务是隐藏硬件,呈现给程序(以及程序员)良好、清晰、优雅、一致的抽象。
如图1-2所示,操作系统将丑陋的硬件转变为美丽的抽象。
需要指出的是,操作系统的实际客户是应用程序(当然是通过应用程序员)。它们直接与操作系统及其抽象打交道。相反,最终用户与用户接口所提供的抽象打交道,或者是命令行shell或者是图形接口。
而用户接口的抽象可以与操作系统提供的抽象类似,但也不总是这样。
为了更清晰地说明这一点,请读者考虑普通的Windows桌面以及面向行的命令提示符。两者都是运行在Windows操作系统上的程序,并使用了Windows提供的抽象,但是它们提供了非常不同的用户接口。
类似地,运行Gnome或者KDE的Linux用户与直接在X Window系统(面向文本)顶部工作的Linux用户看到的是非常不同的界面,但是在这两种情形中,操作系统下面的抽象是相同的。
在本专栏中,我们将具体讨论提供给应用程序的抽象,不过很少涉及用户界面。尽管用户界面是一个巨大和重要的课题,但是它们毕竟只和操作系统的外围相关。
2.2.作为资源管理者的操作系统
把操作系统看作向应用程序提供基本抽象的概念,是一种自顶向下的观点。按照另一种自底向上的观点,操作系统则用来管理一个复杂系统的各个部分。现代计算机包含处理器、存储器、时钟、磁盘、鼠标、网络接口、打印机以及许多其他设备。从这个角度看,操作系统的任务是在相互竞争的程序之间有序地控制对处理器、存储器以及其他I/O接口设备的分配。
现代操作系统允许同时在内存中运行多道程序。假设在一台计算机上运行的三个程序试图同时在同一台打印机上输出计算结果,那么开始的几行可能是程序1的输出,接着几行是程序2的输出,然后又是程序3的输出等,最终结果将是一团糟。采用将打印结果送到磁盘上缓冲区的方法,操作系统可以把潜在的混乱有序化。在一个程序结束后,操作系统可以将暂存在磁盘上的文件送到打印机输出,同时其他程序可以继续产生更多的输出结果,很明显,这些程序的输出还没有真正送至打印机。
当一个计算机(或网络)有多个用户时,管理和保护存储器、I/O设备以及其他资源的需求变得强烈起来,因为用户间可能会互相干扰。另外,用户通常不仅共享硬件,还要共享信息(文件、数据库等)。简而言之,操作系统的这种观点认为,操作系统的主要任务是记录哪个程序在使用什么资源,对资源请求进行分配,评估使用代价,并且为不同的程序和用户调解互相冲突的资源请求。
资源管理包括用以下两种不同方式实现多路复用(共享)资源:在时间上复用和在空间上复用。
当一种资源在时间上复用时,不同的程序或用户轮流使用它。
先是第一个获得资源的使用,然后下一个,以此类推。
例如,若在系统中只有一个CPU,而多个程序需要在该CPU上运行,操作系统则首先把该CPU分配给某个程序,在它运行了足够长的时间之后,另一个程序得到CPU,然后是下一个,如此进行下去,最终,轮到第一个程序再次运行。
至于资源是如何实现时间复用的——谁应该是下一个以及运行多长时间等——则是操作系统的任务。还有一个有关时间复用的例子是打印机的共享。当多个打印作业在一台打印机上排队等待打印时,必须决定将轮到打印的是哪个作业。
另一类复用是空间复用。
每个客户都得到资源的一部分,从而取代了客户排队。
例如,通常在若干运行程序之间分割内存,这样每一个运行程序都可同时入驻内存(例如,为了轮流使用CPU)。假设有足够的内存可以存放多个程序,那么在内存中同时存放若干个程序的效率,比把所有内存都分给一个程序的效率要高得多,特别是,如果一个程序只需要整个内存的一小部分,结果更是这样。
当然,如此的做法会引起公平、保护等问题,这有赖于操作系统解决它们。有关空间复用的其他资源还有磁盘。在许多系统中,一个磁盘同时为许多用户保存文件。分配磁盘空间并记录谁正在使用哪个磁盘块,是操作系统的典型任务。