开通VIP,畅享免费电子书等14项超值服
首页
好书
留言交流
下载APP
联系客服
2023.09.09
现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入/输出设备构成。
然而,程序员不会直接和这些硬件打交道,而且每位程序员不可能会掌握所有计算机系统的细节,这样我们就不用再编写代码了,所以在硬件的基础之上,计算机安装了一层软件,这层软件能够通过响应用户输入的指令达到控制硬件的效果,从而满足用户需求,这种软件称之为操作系统,它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型。
我们一般常见的操作系统主要有Windows、Linux、FreeBSD或OSX,这种带有图形界面的操作系统被称为图形用户界面(GraphicalUserInterface,GUI),而基于文本、命令行的通常称为Shell。下面是我们所要探讨的操作系统的部件
这是一个操作系统的简化图,最下面的是硬件,硬件包括芯片、电路板、磁盘、键盘、显示器等我们上面提到的设备,在硬件之上是软件。大部分计算机有两种运行模式:内核态和用户态,软件中最基础的部分是操作系统,它运行在内核态中,内核态也称为管态和核心态,它们都是操作系统的运行状态,只不过是不同的叫法而已。操作系统具有硬件的访问权,可以执行机器能够运行的任何指令。软件的其余部分运行在用户态下。
用户接口程序(shell或者GUI)处于用户态中,并且它们位于用户态的最低层,允许用户运行其他程序,例如Web浏览器、电子邮件阅读器、音乐播放器等。而且,越靠近用户态的应用程序越容易编写,如果你不喜欢某个电子邮件阅读器你可以重新写一个或者换一个,但你不能自行写一个操作系统或者是中断处理程序。这个程序由硬件保护,防止外部对其进行修改。
操作系统与运行操作系统的内核硬件关系密切。操作系统扩展了计算机指令集并管理计算机的资源。因此,操作系统因此必须足够了解硬件的运行,这里我们先简要介绍一下现代计算机中的计算机硬件。
从概念上来看,一台简单的个人电脑可以被抽象为上面这种相似的模型,CPU、内存、I/O设备都和总线串联起来并通过总线与其他设备进行通信。现代操作系统有着更为复杂的结构,会设计很多条总线,我们稍后会看到。暂时来讲,这个模型能够满足我们的讨论。
CPU是计算机的大脑,它主要和内存进行交互,从内存中提取指令并执行它。一个CPU的执行周期是从内存中提取第一条指令、解码并决定它的类型和操作数,执行,然后再提取、解码执行后续的指令。重复该循环直到程序运行完毕。
除了用于保存变量和临时结果的通用寄存器外,大多数计算机还具有几个特殊的寄存器,这些寄存器对于程序员是可见的。其中之一就是程序计数器(programcounter),程序计数器会指示下一条需要从内存提取指令的地址。提取指令后,程序计数器将更新为下一条需要提取的地址。
另一个寄存器是堆栈指针(stackpointer),它指向内存中当前栈的顶端。堆栈指针会包含输入过程中的有关参数、局部变量以及没有保存在寄存器中的临时变量。
还有一个寄存器是PSW(ProgramStatusWord)程序状态字寄存器,这个寄存器是由操作系统维护的8个字节(64位)long类型的数据集合。它会跟踪当前系统的状态。除非发生系统结束,否则我们可以忽略PSW。用户程序通常可以读取整个PSW,但通常只能写入其某些字段。PSW在系统调用和I/O中起着重要作用。
为了提升性能,CPU设计人员早就放弃了同时去读取、解码和执行一条简单的指令。许多现代的CPU都具有同时读取多条指令的机制。例如,一个CPU可能会有单独访问、解码和执行单元,所以,当CPU执行第N条指令时,还可以对N+1条指令解码,还可以读取N+2条指令。像这样的组织形式被称为流水线(pipeline)。
比流水线更先进的设计是超标量(superscalar)CPU,下面是超标量CPU的设计
在上面这个设计中,存在多个执行单元,例如,一个用来进行整数运算、一个用来浮点数运算、一个用来布尔运算。两个或者更多的指令被一次性取出、解码并放入缓冲区中,直至它们执行完毕。只要一个执行单元空闲,就会去检查缓冲区是否有可以执行的指令。如果有,就把指令从缓冲区中取出并执行。这种设计的含义是应用程序通常是无序执行的。在大多数情况下,硬件负责保证这种运算的结果与顺序执行指令时的结果相同。
除了用在嵌入式系统中非常简单的CPU之外,多数CPU都有两种模式,即前面已经提到的内核态和用户态。通常情况下,PSW寄存器中的一个二进制位会控制当前状态是内核态还是用户态。当运行在内核态时,CPU能够执行任何指令集中的指令并且能够使用硬件的功能。在台式机和服务器上,操作系统通常以内核模式运行,从而可以访问完整的硬件。在大多数嵌入式系统中,一部分运行在内核态下,剩下的一部分运行在用户态下。
用户应用程序通常运行在用户态下,在用户态下,CPU只能执行指令集中的一部分并且只能访问硬件的一部分功能。一般情况下,在用户态下,有关I/O和内存保护的所有指令是禁止执行的。当然,设置PSW模式的二进制位为内核态也是禁止的。
为了获取操作系统的服务,用户程序必须使用系统调用(systemcall),系统调用会转换为内核态并且调用操作系统。TRAP指令用于把用户态切换为内核态并启用操作系统。当有关工作完成之后,在系统调用后面的指令会把控制权交给用户程序。我们会在后面探讨操作系统的调用细节。
需要注意的是操作系统在进行系统调用时会存在陷阱。大部分的陷阱会导致硬件发出警告,比如说试图被零除或浮点下溢等你。在所有的情况下,操作系统都能得到控制权并决定如何处理异常情况。有时,由于出错的原因,程序不得不停止。
对于操作系统来讲,多线程是有意义的,因为每个线程对操作系统来说都像是一个单个的CPU。比如一个有两个CPU的操作系统,并且每个CPU运行两个线程,那么这对于操作系统来说就可能是4个CPU。
除了多线程之外,现在许多CPU芯片上都具有四个、八个或更多完整的处理器或内核。多核芯片在其上有效地承载了四个微型芯片,每个微型芯片都有自己的独立CPU。
如果要说在绝对核心数量方面,没有什么能赢过现代GPU(GraphicsProcessingUnit),GPU是指由成千上万个微核组成的处理器。它们擅长处理大量并行的简单计算。
计算机中第二个主要的组件就是内存。理想情况下,内存应该非常快速(比执行一条指令要快,从而不会拖慢CPU执行效率),而且足够大且便宜,但是目前的技术手段无法满足三者的需求。于是采用了不同的处理方式,存储器系统采用一种分层次的结构
顶层的存储器速度最高,但是容量最小,成本非常高,层级结构越向下,其访问效率越慢,容量越大,但是造价也就越便宜。
存储器的顶层是CPU中的寄存器,它们用和CPU一样的材料制成,所以和CPU一样快。程序必须在软件中自行管理这些寄存器(即决定如何使用它们)
缓存在计算机很多领域都扮演了非常重要的角色,不仅仅是RAM缓存行。
随机存储器(RAM):内存中最重要的一种,表示既可以从中读取数据,也可以写入数据。当机器关闭时,内存中的信息会丢失。
大量的可用资源被划分为小的部分,这些可用资源的一部分会获得比其他资源更频繁的使用权,缓存经常用来提升性能。操作系统无时无刻的不在使用缓存。例如,大多数操作系统在主机内存中保留(部分)频繁使用的文件,以避免重复从磁盘重复获取。举个例子,类似于/home/ast/projects/minix3/src/kernel/clock.c这样的场路径名转换成的文件所在磁盘地址的结果也可以保存缓存中,以避免重复寻址。另外,当一个Web页面(URL)的地址转换为网络地址(IP地址)后,这个转换结果也可以缓存起来供将来使用。
在任何缓存系统中,都会有下面这几个噬需解决的问题
并不是每个问题都与每种缓存情况有关。对于CPU缓存中的主存缓存行,当有缓存未命中时,就会调入新的内容。通常通过所引用内存地址的高位计算应该使用的缓存行。
缓存是解决问题的一种好的方式,所以现代CPU设计了两种缓存。第一级缓存或者说是L1cache总是位于CPU内部,用来将已解码的指令调入CPU的执行引擎。对于那些频繁使用的关键字,多数芯片有第二个L1cache。典型的L1cache的大小为16KB。另外,往往还设有二级缓存,也就是L2cache,用来存放最近使用过的关键字,一般是兆字节为单位。L1cache和L2cache最大的不同在于是否存在延迟。访问L1cache没有任何的延迟,然而访问L2cache会有1-2个时钟周期的延后。
计算机处理器可以在每个时钟周期执行一条或多条指令,这具体取决于处理器的类型。早期的计算机处理器和较慢的CPU在每个时钟周期只能执行一条指令,而现代处理器在每个时钟周期可以执行多条指令。
在上面的层次结构中再下一层是主存,这是内存系统的主力军,主存通常叫做RAM(RandomAccessMemory),由于1950年代和1960年代的计算机使用微小的可磁化铁氧体磁芯作为主存储器,因此旧时有时将其称为核心存储器。所有不能再高速缓存中得到满足的内存访问请求都会转往主存中。
除了主存之外,许多计算机还具有少量的非易失性随机存取存储器。它们与RAM不同,在电源断电后,非易失性随机访问存储器并不会丢失内容。ROM(ReadOnlyMemory)中的内容一旦存储后就不会再被修改。它非常快而且便宜。(如果有人问你,有没有什么又快又便宜的内存设备,那就是ROM了)在计算机中,用于启动计算机的引导加载模块(也就是bootstrap)就存放在ROM中。另外,一些I/O卡也采用ROM处理底层设备控制。
闪存也通常用来作为便携性的存储媒介。闪存是数码相机中的胶卷,是便携式音乐播放器的磁盘。闪存的速度介于RAM和磁盘之间。另外,与磁盘存储器不同的是,如果闪存擦除的次数太多,会出现磨损。
磁盘是一种机械装置,在一个磁盘中有一个或多个金属盘片,它们以5400rpm、7200rpm、10800rpm或更高的速度旋转。从边缘开始有一个机械臂悬横在盘面上,这类似于老式播放塑料唱片33转唱机上的拾音臂。信息会写在磁盘一系列的同心圆上。在任意一个给定臂的位置,每个磁头可以读取一段环形区域,称为磁道(track)。把一个给定臂的位置上的所有磁道合并起来,组成了一个柱面(cylinder)。
需要注意,固态硬盘(SolidStateDisk,SSD)不是磁盘,固态硬盘并没有可以移动的部分,外形也不像唱片,并且数据是存储在存储器(闪存)中,与磁盘唯一的相似之处就是它也存储了大量即使在电源关闭也不会丢失的数据。
许多计算机支持一种著名的虚拟内存机制,这种机制使得期望运行的存储空间大于实际的物理存储空间。其方法是将程序放在磁盘上,而将主存作为一部分缓存,用来保存最频繁使用的部分程序,这种机制需要快速映像内存地址,用来把程序生成的地址转换为有关字节在RAM中的物理地址。这种映像由CPU中的一个称为存储器管理单元(MemoryManagementUnit,MMU)的部件来完成。
缓存和MMU的出现是对系统的性能有很重要的影响,在多道程序系统中,从一个程序切换到另一个程序的机制称为上下文切换(contextswitch),对来自缓存中的资源进行修改并把其写回磁盘是很有必要的。
CPU和存储器不是操作系统需要管理的全部,I/O设备也与操作系统关系密切。可以参考上面这个图片,I/O设备一般包括两个部分:设备控制器和设备本身。控制器本身是一块芯片或者一组芯片,它能够控制物理设备。它能够接收操作系统的指令,例如,从设备中读取数据并完成数据的处理。
在许多情况下,实际控制设备的过程是非常复杂而且存在诸多细节。因此控制器的工作就是为操作系统提供一个更简单(但仍然非常复杂)的接口。也就是屏蔽物理细节。任何复杂的东西都可以加一层代理来解决,这是计算机或者人类社会很普世的一个解决方案
I/O设备另一部分是设备本身,设备本身有一个相对简单的接口,这是因为接口既不能做很多工作,而且也已经被标准化了。例如,标准化后任何一个SATA磁盘控制器就可以适配任意一种SATA磁盘,所以标准化是必要的。ATA代表高级技术附件(ATAttachment),而SATA表示串行高级技术附件(SerialATA)。
AT是啥?它是IBM公司的第二代个人计算机的高级技术成果,使用1984年推出的6MHz80286处理器,这个处理器是当时最强大的。
像是高级这种词汇应该慎用,否则20年后再回首很可能会被无情打脸。
现在SATA是很多计算机的标准硬盘接口。由于实际的设备接口隐藏在控制器中,所以操作系统看到的是对控制器的接口,这个接口和设备接口有很大区别。
每种类型的设备控制器都是不同的,所以需要不同的软件进行控制。专门与控制器进行信息交流,发出命令处理指令接收响应的软件,称为设备驱动程序(devicedriver)。每个控制器厂家都应该针对不同的操作系统提供不同的设备驱动程序。
为了使设备驱动程序能够工作,必须把它安装在操作系统中,这样能够使它在内核态中运行。要将设备驱动程序装入操作系统,一般有三个途径
每个设备控制器都有少量用于通信的寄存器,例如,一个最小的磁盘控制器也会有用于指定磁盘地址、内存地址、扇区计数的寄存器。要激活控制器,设备驱动程序回从操作系统获取一条指令,然后翻译成对应的值,并写入设备寄存器中,所有设备寄存器的结合构成了I/O端口空间。
在一些计算机中,设备寄存器会被映射到操作系统的可用地址空间,使他们能够向内存一样完成读写操作。在这种计算机中,不需要专门的I/O指令,用户程序可以被硬件阻挡在外,防止其接触这些存储器地址(例如,采用基址寄存器和变址寄存器)。在另一些计算机中,设备寄存器被放入一个专门的I/O端口空间,每个寄存器都有一个端口地址。在这些计算机中,特殊的IN和OUT指令会在内核态下启用,它能够允许设备驱动程序和寄存器进行读写。前面第一种方式会限制特殊的I/O指令但是允许一些地址空间;后者不需要地址空间但是需要特殊的指令,这两种应用都很广泛。
实现输入和输出的方式有三种。
在操作系统中,中断是非常重要的,所以这需要更加细致的讨论一下。
在第四步中,中断控制器把该设备的编号放在总线上,这样CPU可以读取总线,并且知道哪个设备完成了操作(可能同时有多个设备同时运行)。
一旦CPU决定去实施中断后,程序计数器和PSW就会被压入到当前堆栈中并且CPU会切换到内核态。设备编号可以作为内存的一个引用,用来寻找该设备中断处理程序的地址。这部分内存称作中断向量(interruptvector)。一旦中断处理程序(中断设备的设备驱动程序的一部分)开始后,它会移除栈中的程序计数器和PSW寄存器,并把它们进行保存,然后查询设备的状态。在中断处理程序全部完成后,它会返回到先前用户程序尚未执行的第一条指令,这个过程如下
上面的结构(简单个人计算机的组件图)在小型计算机已经使用了多年,并用在早期的IBMPC中。然而,随着处理器核内存变得越来越快,单个总线处理所有请求的能力也达到了上线,其中也包括IBMPC总线。必须放弃使用这种模式。其结果导致了其他总线的出现,它们处理I/O设备以及CPU到存储器的速度都更快。这种演变的结果导致了下面这种结构的出现。
上图中的x86系统包含很多总线,高速缓存、内存、PCIe、PCI、USB、SATA和DMI,每条总线都有不同的传输速率和功能。操作系统必须了解所有的总线配置和管理。其中最主要的总线是PCIe(PeripheralComponentInterconnectExpress)总线。
Intel发明的PCIe总线也是作为之前古老的PCI总线的继承者,而古老的PCI总线也是为了取代古董级别的ISA(IndustryStandardArchitecture)总线而设立的。数十Gb/s的传输能力使得PCIe比它的前身快很多,而且它们本质上也十分不同。直到发明PCIe的2004年,大多数总线都是并行且共享的。共享总线架构(sharedbusarchiteture)表示多个设备使用一些相同的电线传输数据。因此,当多个设备同时发送数据时,此时你需要一个决策者来决定谁能够使用总线。而PCIe则不一样,它使用专门的端到端链路。传统PCI中使用的并行总线架构(parallelbusarchitecture)表示通过多条电线发送相同的数据字。例如,在传统的PCI总线上,一个32位数据通过32条并行的电线发送。而PCIe则不同,它选用了串行总线架构(serialbusarchitecture),并通过单个连接(称为通道)发送消息中的所有比特数据,就像网络数据包一样。这样做会简化很多,因为不再确保所有32位数据在同一时刻准确到达相同的目的地。通过将多个数据通路并行起来,并行性仍可以有效利用。例如,可以使用32条数据通道并行传输32条消息。
在上图结构中,CPU通过DDR3总线与内存对话,通过PCIe总线与外围图形设备(GPU)对话,通过DMI(DirectMediaInterface)总线经集成中心与所有其他设备对话。而集成控制中心通过串行总线与USB设备对话,通过SATA总线与硬盘和DVD驱动器对话,通过PCIe传输以太网络帧。
不仅如此,每一个核
USB(UnivversalSerialBus)是用来将所有慢速I/O设备(比如键盘和鼠标)与计算机相连的设备。USB1.0可以处理总计12Mb/s的负载,而USB2.0将总线速度提高到480Mb/s,而USB3.0能达到不小于5Gb/s的速率。所有的USB设备都可以直接连接到计算机并能够立刻开始工作,而不像之前那样要求重启计算机。
SCSI(SmallComputerSystemInterface)总线是一种高速总线,用在高速硬盘,扫描仪和其他需要较大带宽的设备上。现在,它们主要用在服务器和工作站中,速度可以达到640MB/s。
那么有了上面一些硬件再加上操作系统的支持,我们的计算机就可以开始工作了,那么计算机的启动过程是怎样的呢?下面只是一个简要版的启动过程
在每台计算机上有一块双亲板,也就是母板,母板也就是主板,它是计算机最基本也就是最重要的部件之一。主板一般为矩形电路板,上面安装了组成计算机的主要电路系统,一般有BIOS芯片、I/O控制芯片、键盘和面板控制开关接口、指示灯插接件、扩充插槽、主板及插卡的直流电源供电接插件等元件。
在母板上有一个称为基本输入输出系统(BasicInputOutputSystem,BIOS)的程序。在BIOS内有底层I/O软件,包括读键盘、写屏幕、磁盘I/O以及其他过程。如今,它被保存在闪存中,它是非易失性的,但是当BIOS中发现错误时,可以由操作系统进行更新。
在计算机启动(booted)时,BIOS开启,它会首先检查所安装的RAM的数量,键盘和其他基础设备是否已安装并且正常响应。接着,它开始扫描PCIe和PCI总线并找出连在上面的所有设备。即插即用的设备也会被记录下来。如果现有的设备和系统上一次启动时的设备不同,则新的设备将被重新配置。
蓝后,BIOS通过尝试存储在CMOS存储器中的设备清单尝试启动设备
CMOS是ComplementaryMetalOxideSemiconductor(互补金属氧化物半导体)的缩写。它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的RAM芯片。因为可读写的特性,所以在电脑主板上用来保存BIOS设置完电脑硬件参数后的数据,这个芯片仅仅是用来存放数据的。
而对BIOS中各项参数的设定要通过专门的程序。BIOS设置程序一般都被厂商整合在芯片中,在开机时通过特定的按键就可进入BIOS设置程序,方便地对系统进行设置。因此BIOS设置有时也被叫做CMOS设置。
用户可以在系统启动后进入一个BIOS配置程序,对设备清单进行修改。然后,判断是否能够从外部CD-ROM和USB驱动程序启动,如果启动失败的话(也就是没有),系统将从硬盘启动,boots设备中的第一个扇区被读入内存并执行。该扇区包含一个程序,该程序通常在引导扇区末尾检查分区表以确定哪个分区处于活动状态。然后从该分区读入第二个启动加载程序,该加载器从活动分区中读取操作系统并启动它。
操作系统已经存在了大半个世纪,在这段时期内,出现了各种类型的操作系统,但并不是所有的操作系统都很出名,下面就罗列一些比较出名的操作系统
高端一些的操作系统是大型机操作系统,这些大型操作系统可在大型公司的数据中心找到。这些计算机的I/O容量与个人计算机不同。一个大型计算机有1000个磁盘和数百万G字节的容量是很正常,如果有这样一台个人计算机朋友会很羡慕。大型机也在高端Web服务器、大型电子商务服务站点上。
下一个层次是服务器操作系统。它们运行在服务器上,服务器可以是大型个人计算机、工作站甚至是大型机。它们通过网络为若干用户服务,并且允许用户共享硬件和软件资源。服务器可提供打印服务、文件服务或Web服务。Internet服务商运行着许多台服务器机器,为用户提供支持,使Web站点保存Web页面并处理进来的请求。典型的服务器操作系统有Solaris、FreeBSD、Linux和WindowsServer201x
获得大型计算能力的一种越来越普遍的方式是将多个CPU连接到一个系统中。依据它们连接方式和共享方式的不同,这些系统称为并行计算机,多计算机或多处理器。他们需要专门的操作系统,不过通常采用的操作系统是配有通信、连接和一致性等专门功能的服务器操作系统的变体。
个人计算机中近来出现了多核芯片,所以常规的台式机和笔记本电脑操作系统也开始与小规模多处理器打交道,而核的数量正在与时俱进。许多主流操作系统比如Windows和Linux都可以运行在多核处理器上。
接下来一类是个人计算机操作系统。现代个人计算机操作系统支持多道处理程序。在启动时,通常有几十个程序开始运行,它们的功能是为单个用户提供良好的支持。这类系统广泛用于字处理、电子表格、游戏和Internet访问。常见的例子是Linux、FreeBSD、Windows7、Windows8和苹果公司的OSX。
随着硬件越来越小化,我们看到了平板电脑、智能手机和其他掌上计算机系统。掌上计算机或者PDA(PersonalDigitalAssistant),个人数字助理是一种可以握在手中操作的小型计算机。这部分市场已经被谷歌的Android系统和苹果的IOS主导。
有许多用途需要配置微小传感器节点网络。这些节点是一种可以彼此通信并且使用无线通信基站的微型计算机。这类传感器网络可以用于建筑物周边保护、国土边界保卫、森林火灾探测、气象预测用的温度和降水测量等。
每个传感器节点是一个配有CPU、RAM、ROM以及一个或多个环境传感器的实实在在的计算机。节点上运行一个小型但是真是的操作系统,通常这个操作系统是事件驱动的,可以响应外部事件。
最小的操作系统运行在智能卡上。智能卡是一种包含一块CPU芯片的信用卡。它有非常严格的运行能耗和存储空间的限制。有些卡具有单项功能,如电子支付;有些智能卡是面向Java的。这意味着在智能卡的ROM中有一个Java虚拟机(JavaVirtualMachine,JVM)解释器。
大部分操作系统提供了特定的基础概念和抽象,例如进程、地址空间、文件等,它们是需要理解的核心内容。下面我们会简要介绍一些基本概念,为了说明这些概念,我们会不时的从UNIX中提出示例,相同的示例也会存在于其他系统中,我们后面会进行介绍。
所以,一个挂起的进程包括:进程的地址空间(往往称作磁芯映像,coreimage,纪念过去的磁芯存储器),以及对应的进程表项(其中包括寄存器以及稍后启动该进程所需要的许多其他信息)。
与进程管理有关的最关键的系统调用往往是决定着进程的创建和终止的系统调用。考虑一个典型的例子,有一个称为命令解释器(commandinterpreter)或shell的进程从终端上读取命令。此时,用户刚键入一条命令要求编译一个程序。shell必须先创建一个新进程来执行编译程序,当编译程序结束时,它执行一个系统调用来终止自己的进程。
如果一个进程能够创建一个或多个进程(称为子进程),而且这些进程又可以创建子进程,则很容易找到进程数,如下所示
上图表示一个进程树的示意图,进程A创建了两个子进程B和进程C,子进程B又创建了三个子进程D、E、F。
其他可用的进程系统调用包括:申请更多的内存(或释放不再需要的内存),等待一个子进程结束,用另一个程序覆盖该程序。
有时,需要向一个正在运行的进程传递信息,而该进程并没有等待接收信息。例如,一个进程通过网络向另一台机器上的进程发送消息进行通信。为了保证一条消息或消息的应答不丢失。发送者要求它所在的操作系统在指定的若干秒后发送一个通知,这样如果对方尚未收到确认消息就可以进行重新发送。在设定该定时器后,程序可以继续做其他工作。
在UNIX操作系统中,有一个UID是超级用户(superuser),或者Windows中的管理员(administrator),它具有特殊的权利,可以违背一些保护规则。在大型系统中,只有系统管理员掌握着那些用户可以称为超级用户。
每台计算机都有一些主存用来保存正在执行的程序。在一个非常简单的操作系统中,仅仅有一个应用程序运行在内存中。为了运行第二个应用程序,需要把第一个应用程序移除才能把第二个程序装入内存。
复杂一些的操作系统会允许多个应用程序同时装入内存中运行。为了防止应用程序之间相互干扰(包括操作系统),需要有某种保护机制。虽然此机制是在硬件中实现,但却是由操作系统控制的。
上述观点涉及对计算机主存的管理和保护。另一种同等重要并与存储器有关的内容是管理进程的地址空间。通常,每个进程有一些可以使用的地址集合,典型值从0开始直到某个最大值。一个进程可拥有的最大地址空间小于主存。在这种情况下,即使进程用完其地址空间,内存也会有足够的内存运行该进程。
但是,在许多32位或64位地址的计算机中,分别有2^32或2^64字节的地址空间。如果一个进程有比计算机拥有的主存还大的地址空间,而且该进程希望使用全部的内存,那该怎么处理?在早期的计算机中是无法处理的。但是现在有了一种虚拟内存的技术,正如前面讲到过的,操作系统可以把部分地址空间装入主存,部分留在磁盘上,并且在需要时来回交换它们。
几乎所有操作系统都支持的另一个关键概念就是文件系统。如前所述,操作系统的一项主要功能是屏蔽磁盘和其他I/O设备的细节特性,给程序员提供一个良好、清晰的独立于设备的抽象文件模型。创建文件、删除文件、读文件和写文件都需要系统调用。在文件可以读取之前,必须先在磁盘上定位和打开文件,在文件读过之后应该关闭该文件,有关的系统调用则用于完成这类操作。
为了提供保存文件的地方,大多数个人计算机操作系统都有目录(directory)的概念,从而可以把文件分组。比如,学生可以给每个课程都创建一个目录,用于保存该学科的资源,另一个目录可以存放电子邮件,再有一个目录可以存放万维网主页。这就需要系统调用创建和删除目录、将已有文件放入目录中,从目录中删除文件等。目录项可以是文件或者目录,目录和目录之间也可以嵌套,这样就产生了文件系统
目录层结构中的每一个文件都可以通过从目录的顶部即根目录(Rootdirectory)开始的路径名(pathname)来确定。绝对路径名包含了从根目录到该文件的所有目录清单,它们之间用斜杠分隔符分开,在上面的大学院系文件系统中,文件CS101的路径名是/Faculty/Prof.Brown/Courses/CS101。最开始的斜杠分隔符代表的是根目录/,也就是文件系统的绝对路径。
出于历史原因,Windows下面的文件系统以\来作为分隔符,但是Linux会以/作为分隔符。
在上面的系统中,每个进程会有一个工作目录(workingdirectory),对于没有以斜线开头给出绝对地址的路径,将在这个工作目录下寻找。如果/Faculty/Prof.Brown是工作目录,那么/Courses/CS101与上面给定的绝对路径名表示的是同一个文件。进程可以通过使用系统调用指定新的工作目录,从而变更其工作目录。
在读写文件之前,首先需要打开文件,检查其访问权限。若权限许可,系统将返回一个小整数,称作文件描述符(filedescriptor),供后续操作使用。若禁止访问,系统则返回一个错误码。
在UNIX中,另一个重要的概念是特殊文件(specialfile)。提供特殊文件是为了使I/O设备看起来像文件一般。这样,就像使用系统调用读写文件一样,I/O设备也可以通过同样的系统调用进行读写。特殊文件有两种,一种是块儿特殊文件(blockspecialfile)和字符特殊文件(characterspecialfile)。块特殊文件指那些由可随机存取的块组成的设备,如磁盘等。比如打开一个块特殊文件,然后读取第4块,程序可以直接访问设备的第4块而不必考虑存放在该文件的文件系统结构。类似的,字符特殊文件用于打印机、调制解调起和其他接受或输出字符流的设备。按照惯例,特殊文件保存在/dev目录中。例如,/devv/lp是打印机。
如果A和B希望通过管道对话,他们必须提前设置管道。当进程A相对进程B发送数据时,它把数据写到管道上,相当于管道就是输出文件。这样,在UNIX中两个进程之间的通信就非常类似于普通文件的读写了。
比如UNIX操作系统,UNIX操作系统通过对每个文件赋予一个9位二进制保护代码,对UNIX中的文件实现保护。该保护代码有三个位子段,一个用于所有者,一个用于与所有者同组(用户被系统管理员划分成组)的其他成员,一个用于其他人。每个字段中有一位用于读访问,一位用于写访问,一位用于执行访问。这些位就是著名的rwx位。例如,保护代码rwxr-x--x的含义是所有者可以读、写或执行该文件,其他的组成员可以读或执行(但不能写)此文件、而其他人可以执行(但不能读和写)该文件。
操作系统是执行系统调用的代码。编辑器、编译器、汇编程序、链接程序、使用程序以及命令解释符等,尽管非常重要,非常有用,但是它们确实不是操作系统的组成部分。下面我们着重介绍一下UNIX下的命令提示符,也就是shell,shell虽然有用,但它也不是操作系统的一部分,然而它却能很好的说明操作系统很多特性,下面我们就来探讨一下。
shell有许多种,例如sh、csh、ksh以及bash等,它们都支持下面这些功能,最早起的shell可以追溯到sh
shell会创建一个子进程,并运行date做为子进程。在该子进程运行期间,shell将等待它结束。在子进程完成时,shell会显示提示符并等待下一行输入。
用户可以将标准输出重定向到一个文件中,例如
date>file同样的,也可以将标准输入作为重定向
这会调用sort程序来接收file1的内容并把结果输出到file2。
可以将一个应用程序的输出通过管道作为另一个程序的输入,因此有
catfile1file2file3|sort>/dev/lp这会调用cat应用程序来合并三个文件,将其结果输送到sort程序中并按照字典进行排序。sort应用程序又被重定向到/dev/lp,显然这是一个打印操作。
我们已经可以看到操作系统提供了两种功能:为用户提供应用程序抽象和管理计算机资源。对于大部分在应用程序和操作系统之间的交互主要是应用程序的抽象,例如创建、写入、读取和删除文件。计算机的资源管理对用户来说基本上是透明的。因此,用户程序和操作系统之间的接口主要是处理抽象。为了真正理解操作系统的行为,我们必须仔细的分析这个接口。
多数现代操作系统都有功能相同但是细节不同的系统调用,引发操作系统的调用依赖于计算机自身的机制,而且必须用汇编代码表达。任何单CPU计算机一次执行执行一条指令。如果一个进程在用户态下运行用户程序,例如从文件中读取数据。那么如果想要把控制权交给操作系统控制,那么必须执行一个异常指令或者系统调用指令。操作系统紧接着需要参数检查找出所需要的调用进程。操作系统紧接着进行参数检查找出所需要的调用进程。然后执行系统调用,把控制权移交给系统调用下面的指令。大致来说,系统调用就像是执行了一个特殊的过程调用,但是只有系统调用能够进入内核态而过程调用则不能进入内核态。
为了能够了解具体的调用过程,下面我们以read方法为例来看一下调用过程。像上面提到的那样,会有三个参数,第一个参数是指定文件、第二个是指向缓冲区、第三个参数是给定需要读取的字节数。就像几乎所有系统调用一样,它通过使用与系统调用相同的名称来调用一个函数库,从而从C程序中调用:read。
系统调用在count中返回实际读出的字节数。这个值通常与nbytes相同,但也可能更小。比如在读过程中遇到了文件尾的情况。
如果系统调用不能执行,不管是因为无效的参数还是磁盘错误,count的值都会被置成-1,然后在全局变量errno中放入错误信号。程序应该进场检查系统调用的结果以了解是否出错。
系统调用是通过一系列的步骤实现的,为了更清楚的说明这个概念,我们还以read调用为例,在准备系统调用前,首先会把参数压入堆栈,如下所示
C和C++编译器使用逆序(必须把第一个参数赋值给printf(格式字符串),放在堆栈的顶部)。第一个参数和第三个参数都是值调用,但是第二个参数通过引用传递,即传递的是缓冲区的地址(由&指示),而不是缓冲的内容。然后是C调用系统库的read函数,这也是第四步。
在由汇编语言写成的库过程中,一般把系统调用的编号放在操作系统所期望的地方,如寄存器(第五步)。然后执行一个TRAP指令,将用户态切换到内核态,并在内核中的一个固定地址开始执行第六步。TRAP指令实际上与过程调用指令非常相似,它们后面都跟随一个来自远处位置的指令,以及供以后使用的一个保存在栈中的返回地址。
TRAP指令与过程调用指令存在两个方面的不同
跟随在TRAP指令后的内核代码开始检查系统调用编号,然后dispatch给正确的系统调用处理器,这通常是通过一张由系统调用编号所引用的、指向系统调用处理器的指针表来完成第七步。此时,系统调用处理器运行第八步,一旦系统调用处理器完成工作,控制权会根据TRAP指令后面的指令中返回给函数调用库第九步。这个过程接着以通常的过程调用返回的方式,返回到客户应用程序,这是第十步。然后调用完成后,操作系统还必须清除用户堆栈,然后增加堆栈指针(incrementstackpointer),用来清除调用read之前压入的参数。从而完成整个read调用过程。
在上面的第九步中我们说道,控制可能返回TRAP指令后面的指令,把控制权再移交给调用者这个过程中,系统调用会发生阻塞,从而避免应用程序继续执行。这么做是有原因的。例如,如果试图读键盘,此时并没有任何输入,那么调用者就必须被阻塞。在这种情形下,操作系统会检查是否有其他可以运行的进程。这样,当有用户输入时候,进程会提醒操作系统,然后返回第9步继续运行。
下面,我们会列出一些常用的POSIX系统调用,POSIX系统调用大概有100多个,它们之中最重要的一些调用见下表
进程管理
文件管理
目录和文件系统管理
其他
从宏观角度上看,这些系统调所提供的服务确定了多数操作系统应该具有的功能,下面分别来对不同的系统调用进行解释
在UNIX中,fork是唯一可以在POSIX中创建进程的途径,它创建一个原有进程的副本,包括所有的文件描述符、寄存器等内容。在fork之后,原有进程以及副本(父与子)就分开了。在fork过程中,所有的变量都有相同的值,虽然父进程的数据通过复制给子进程,但是后续对其中任何一个进程的修改不会影响到另外一个。fork调用会返回一个值,在子进程中该值为0,并且在父进程中等于子进程的进程标识符(ProcessIDentified,PID)。使用返回的PID,就可以看出来哪个是父进程和子进程。
在多数情况下,在fork之后,子进程需要执行和父进程不一样的代码。从终端读取命令,创建一个子进程,等待子进程执行命令,当子进程结束后再读取下一个输入的指令。为了等待子进程完成,父进程需要执行waitpid系统调用,父进程会等待直至子进程终止(若有多个子进程的话,则直至任何一个子进程终止)。waitpid可以等待一个特定的子进程,或者通过将第一个参数设为-1的方式,等待任何一个比较老的子进程。当waitpid完成后,会将第二个参数statloc所指向的地址设置为子进程的退出状态(正常或异常终止以及退出值)。有各种可使用的选项,它们由第三个参数确定。例如,如果没有已经退出的子进程则立刻返回。
那么shell该如何使用fork呢?在键入一条命令后,shell会调用fork命令创建一个新的进程。这个子进程会执行用户的指令。通过使用execve系统调用可以实现系统执行,这个系统调用会引起整个核心映像被一个文件所替代,该文件由第一个参数给定。下面是一个简化版的例子说明fork、waitpid和execve的使用
#defineTRUE1/*一直循环下去*/while(TRUE){/*在屏幕上显示提示符*/type_prompt();/*从终端读取输入*/read_command(command,parameters)/*fork子进程*/if(fork()!=0){/*父代码*//*等待子进程执行完毕*/waitpid(-1,&status,0);}else{/*执行命令*//*子代码*/execve(command,parameters,0)}}一般情况下,execve有三个参数:将要执行的文件名称,一个指向变量数组的指针,以及一个指向环境数组的指针。这里对这些参数做一个简要的说明。
先看一个shell指令
此命令把file1复制到file2文件中,在shell执行fork之后,子进程定位并执行文件拷贝,并将源文件和目标文件的名称传递给它。
main(argc,argv,envp)其中argc是命令行中参数数目的计数,包括程序名称。对于上面的例子,argc是3。第二个参数argv是数组的指针。该数组的元素i是指向该命令行第i个字符串的指针。在上面的例子中,argv[0]指向字符串cp,argv[1]指向字符串file1,argv[2]指向字符串file2。main的第三个参数是指向环境的指针,该环境是一个数组,含有name=value的赋值形式,用以将诸如终端类型以及根目录等信息传送给程序。这些变量通常用来确定用户希望如何完成特定的任务(例如,使用默认打印机)。在上面的例子中,没有环境参数传递给execve,所以环境变量是0,所以execve的第三个参数为0。
可能你觉得execve过于复杂,这时候我要鼓励一下你,execve可能是POSIX的全部系统调用中最复杂的一个了,其他都比较简单。作为一个简单的例子,我们再来看一下exit,这是进程在执行完成后应执行的系统调用。这个系统调用有一个参数,它的退出状态是0-255之间,它通过waitpid系统调用中的statloc返回给父级。
UNIX中的进程将内存划分成三个部分:textsegment,文本区,例如程序代码,datasegment,数据区,例如变量,stacksegment,栈区域。数据向上增长而堆栈向下增长,如下图所示
上图能说明三个部分的内存分配情况,夹在中间的是空闲区,也就是未分配的区域,堆栈在需要时自动的挤压空闲区域,不过数据段的扩展是显示地通过系统调用brk进行的,在数据段扩充后,该系统调用指向一个新地址。但是,这个调用不是POSIX标准中定义的,对于存储器的动态分配,鼓励程序员使用malloc函数,而malloc的内部实现则不是一个适合标准化的主题,因为几乎没有程序员直接使用它。
许多系统调用都与文件系统有关,要读写一个文件,必须先将其打开。这个系统调用通过绝对路径名或指向工作目录的相对路径名指定要打开文件的名称,而代码O_RDONLY、O_WRONLY或O_RDWR的含义分别是只读、只写或者两者都可以,为了创建一个新文件,使用O_CREATE参数。然后可使用返回的文件描述符进行读写操作。接着,可以使用close关闭文件,这个调用使得文件描述符在后续的open中被再次使用。
最常用的调用还是read和write,我们再前面探讨过read调用,write具有与read相同的参数。
Iseek有三个参数,position=iseek(fd,offset,whence),第一个是文件描述符,第二个是文件位置,第三个是说明该文件位置是相对于文件起始位置,当前位置还是文件的结尾。在修改了指针之后,Iseek所返回的值是文件中的绝对位置。
下面我们探讨目录和整个文件系统的系统调用,上面探讨的是和某个文件有关的系统调用。mkdir和rmdir分别用于创建s=mkdir(nname,mode)和删除s=rmdir(name)空目录,下一个调用是s=link(name1,name2)它的作用是允许同一个文件以两个或者多个名称出现,多数情况下是在不同的目录中使用link,下面我们探讨一下link是如何工作的
图中有两个用户ast和jim,每个用户都有他自己的一个目录和一些文件,如果ast要执行一个包含下面系统调用的应用程序
jim中的memo文件现在会进入到ast的目录中,在note名称下。此后,/usr/jim/memo和/usr/ast/note会有相同的名称。
用户目录是保存在/usr,/user,/home还是其他位置,都是由本地系统管理员决定的。
要理解link是如何工作的需要清楚link做了什么操作。UNIX中的每个文件都有一个独一无二的版本,也称作i-number,i-编号,它标示着不同文件的版本。这个i-编号是i-nodes,i-节点表的索引。每个文件都会表明谁拥有这个文件,这个磁盘块的位置在哪,等等。目录只是一个包含一组(i编号,ASCII名称)对应的文件。UNIX中的第一个版本中,每个目录项都会有16个字节,2个字节对应i-编号和14个字节对应其名称。现在需要一个更复杂的结构需要支持长文件名,但是从概念上讲一个目录仍是一系列(i-编号,ASCII名称)的集合。在上图中,mail的i-编号为16,依此类推。link只是利用某个已有文件的i-编号,创建一个新目录项(也许用一个新名称)。在上图b中,你会发现有两个相同的70i-编号的文件,因此它们需要有相同的文件。如果其中一个使用了unlink系统调用的话,其中一个会被移除,另一个将保留。如果两个文件都移除了,则UNIX会发现该文件不存在任何没有目录项(i-节点中的一个域记录着指向该文件的目录项),就会把该文件从磁盘中移除。
就像我们上面提到过的那样,mount系统s=mount(special,name,flag)调用会将两个文件系统合并为一个。通常的情况是将根文件系统分布在硬盘(子)分区上,并将用户文件分布在另一个(子)分区上,该根文件系统包含常用命令的二进制(可执行)版本和其他使用频繁的文件。然后,用户就会插入可读取的USB硬盘。
通过执行mount系统调用,USB文件系统可以被添加到根文件系统中,
如果用C语言来执行那就是
mount('/dev/sdb0','/mnt',0)这里,第一个参数是USB驱动器0的块特殊文件名称,第二个参数是被安装在树中的位置,第三个参数说明将要安装的文件系统是可读写的还是只读的。
当不再需要一个文件系统时,可以使用umount移除之。
除了进程、文件、目录系统调用,也存在其他系统调用的情况,下面我们来探讨一下。我们可以看到上面其他系统调用只有四种,首先来看第一个chdir,chdir调用更改当前工作目录,在调用
后,打开xyz文件,会打开/usr/ast/test/xyz文件,工作目录的概念消除了总是需要输入长文件名的需要。
在UNIX系统中,每个文件都会有保护模式,这个模式会有一个读-写-执行位,它用来区分所有者、组和其他成员。chmod系统调用提供改变文件模式的操作。例如,要使一个文件除了对所有者之外的用户可读,你可以执行
chmod('file',0644);kill系统调用是用户和用户进程发送信号的方式,如果一个进程准备好捕捉一个特定的信号,那么在信号捕捉之前,会运行一个信号处理程序。如果进程没有准备好捕捉特定的信号,那么信号的到来会杀掉该进程(此名字的由来)。
上面我们提到的都是UNIX系统调用,现在我们来聊聊Win32中的系统调用。Windows和UNIX在各自的编程方式上有着根本的不同。UNIX程序由执行某些操作或执行其他操作的代码组成,进行系统调用以执行某些服务。Windows系统则不同,Windows应用程序通常是由事件驱动的。主程序会等待一些事件发生,然后调用程序去处理。最简单的事件处理是键盘敲击和鼠标滑过,或者是鼠标点击,或者是插入USB驱动,然后操作系统调用处理器去处理事件,更新屏幕和更新程序内部状态。这是与UNIX不同的设计风格。
当然,Windows也有系统调用。在UNIX中,系统调用(比如read)和系统调用所使用的调用库(例如read)几乎是一对一的关系。而在Windows中,情况则大不相同。首先,函数库的调用和实际的系统调用几乎是不对应的。微软定义了一系列过程,称为Win32应用编程接口(ApplicationProgrammingInterface),程序员通过这套标准的接口来实现系统调用。这个接口支持从Windows95版本以来所有的Windows版本。
Win32API调用的数量是非常巨大的,有数千个多。但这些调用并不都是在内核态的模式下运行时,有一些是在用户态的模型下运行。Win32API有大量的调用,用来管理视窗、几何图形、文本、字体、滚动条、对话框、菜单以及GUI的其他功能。为了使图形子系统在内核态下运行,需要系统调用,否则就只有函数库调用。
上表中是UNIX调用大致对应的Win32API系统调用,简述一下上表。CreateProcess用于创建一个新进程,它把UNIX中的fork和execve两个指令合成一个,一起执行。它有许多参数用来指定新创建进程的性质。Windows中没有类似UNIX中的进程层次,所以不存在父进程和子进程的概念。在进程创建之后,创建者和被创建者是平等的。WaitForSingleObject用于等待一个事件,等待的事件可以是多种可能的事件。如果有参数指定了某个进程,那么调用者将等待指定的进程退出,这通过ExitProcess来完成。
然后是6个文件操作,在功能上和UNIX的调用类似,然而在参数和细节上是不同的。和UNIX中一样,文件可以打开,读取,写入,关闭。SetFilePointer和GetFileAttributesEx设置文件的位置并取得文件的属性。
Win32接口中没有文件的链接、文件系统的mount、umount和stat,当然,Win32中也有大量UNIX中没有的系统调用,特别是对GUI的管理和调用。
下面我们会探讨操作系统的几种结构,主要包括单体结构、分层系统、微内核、客户-服务端系统、虚拟机和外核等。下面以此来探讨一下
到目前为止,在大多数系统中,整个系统在内核态以单一程序的方式运行。整个操作系统是以程序集合来编写的,链接在一块形成一个大的二进制可执行程序。使用此技术时,如果系统中的每个过程都提供了前者所需的一些有用的计算,则它可以自由调用任何其他过程。在单体系统中,调用任何一个所需要的程序都非常高效,但是上千个不受限制的彼此调用往往非常臃肿和笨拙,而且单体系统必然存在单体问题,那就是只要系统发生故障,那么任何系统和应用程序将不可用,这往往是灾难性的。
在单体系统中构造实际目标程序时,会首先编译所有单个过程(或包含这些过程的文件),然后使用系统链接器将它们全部绑定到一个可执行文件中
对于单体系统,往往有下面几种建议
在单体系统中,对于每个系统调用都会有一个服务程序来保障和运行。需要一组实用程序来弥补服务程序需要的功能,例如从用户程序中获取数据。可将各种过程划分为一个三层模型
除了在计算机初启动时所装载的核心操作系统外,许多操作系统还支持额外的扩展。比如I/O设备驱动和文件系统。这些部件可以按需装载。在UNIX中把它们叫做共享库(sharedlibrary),在Windows中则被称为动态链接库(DynamicLinkLibrary,DLL)。他们的扩展名为.dll,在C:\Windows\system32目录下存在1000多个DLL文件,所以不要轻易删除C盘文件,否则可能就炸了哦。
分层系统使用层来分隔不同的功能单元。每一层只与该层的上层和下层通信。每一层都使用下面的层来执行其功能。层之间的通信通过预定义的固定接口通信。
分层系统是由E.W.Dijkstar和他的学生在荷兰技术学院所开发的THE系统。
把上面单体系统进一步通用化,就变为了一个层次式结构的操作系统,它的上层软件都是在下层软件的基础之上构建的。该系统分为六层,如下所示
处理器在0层运行,当中断发生或定时器到期时,由该层完成进程切换;在第0层之上,系统由一些连续的进程组成,编写这些进程时不用再考虑在单处理器上多进程运行的细节。内存管理在第1层,它分配进程的主存空间。第1层软件保证一旦需要访问某一页面,该页面必定已经在内存中,并且在页面不需要的时候将其移出。
在分层方式中,设计者要确定在哪里划分内核-用户的边界。传统上,所有的层都在内核中,但是这样做没有必要。事实上,尽可能减少内核态中功能可能是更好的做法。因为内核中的错误很难处理,一旦内核态中出错误会拖累整个系统。
所以,为了实现高可靠性,将操作系统划分成小的、层级之间能够更好定义的模块是很有必要的,只有一个模块---微内核---运行在内核态,其余模块可以作为普通用户进程运行。由于把每个设备驱动和文件系统分别作为普通用户进程,这些模块中的错误虽然会使这些模块崩溃,但是不会使整个系统死机。
MINIX3是微内核的代表作,它的具体结构如下
在内核的外部,系统的构造有三层,它们都在用户态下运行,最底层是设备驱动器。由于它们都在用户态下运行,所以不能物理的访问I/O端口空间,也不能直接发出I/O命令。相反,为了能够对I/O设备编程,驱动器构建一个结构,指明哪个参数值写到哪个I/O端口,并声称一个内核调用,这样就完成了一次调用过程。
位于用户态的驱动程序上面是服务器层,包含有服务器,它们完成操作系统的多数工作。由一个或多个文件服务器管理着文件系统,进程管理器创建、销毁和管理进程。服务器中有一个特殊的服务器称为再生服务器(reincarnationserver),它的任务就是检查服务器和驱动程序的功能是否正确,一旦检查出来错误,它就会补上去,无需用户干预。这种方式使得系统具有可恢复性,并具有较高的可靠性。
微内核中的内核还具有一种机制与策略分离的思想。比如系统调度,一个比较简单的调度算法是,对每个进程赋予一个优先级,并让内核执行具有最高优先级的进程。这里,内核机制就是寻找最高的优先级进程并运行。而策略(赋予进程优先级)可以在用户态中的进程完成。在这种模式中,策略和机制是分离的,从而使内核变得更小。
微内核思想的策略是把进程划分为两类:服务器,每个服务器用来提供服务;客户端,使用这些服务。这个模式就是所谓的客户-服务器模式。
客户-服务器模式会有两种载体,一种情况是一台计算机既是客户又是服务器,在这种方式下,操作系统会有某种优化;但是普遍情况下是客户端和服务器在不同的机器上,它们通过局域网或广域网连接。
客户通过发送消息与服务器通信,客户端并不需要知道这些消息是在本地机器上处理,还是通过网络被送到远程机器上处理。对于客户端而言,这两种情形是一样的:都是发送请求并得到回应。
越来越多的系统,包括家里的PC,都成为客户端,而在某地运行的大型机器则成为服务器。许多web就是以这种方式运行的。一台PC向某个服务器请求一个Web页面,服务器把Web页面返回给客户端,这就是典型的客服-服务器模式