操作系统之哲学原理连载之四

 

2009年最值得关注的操作系统图书

操作系统之哲学原理连载之四

操作系统之哲学原理连载之一

操作系统之哲学原理连载之二

操作系统之哲学原理连载之三

 

3 操作系统基本概念之哲学原理

http://www.china-pub.com/static/zt_mb/zt_huodong_07.asp?filename=jsj_czxt_090417

 

引子

有一个幽默也许读者听过,说是三个数学家和三个软件专家在一列火车上相遇。攀谈中,六个人发现大家都是参加一个共同的会议。然而让软件专家们吃惊的是,三个数学家只买了一张车票,而他们自己却各买了一张票。

 

三个软件专家于是问数学家:“你们三个人只买一张票,等会列车员查票你们怎么应付呢?”(这里需要指出的是,这里是西方的火车,车站不查票,而是在车上查票。)

 

三个数学家回答说“这你们就不懂了吧。你们看好了,学习学习。”

 

过了不久,果然开始查票。只见三个数学家急急忙忙跑到厕所里面,将门从里面反锁上。查票员过来后敲打厕所的门,问里面有人吗。里面传来一个声音“有人,但正在上厕所,无法开门,能否将车票从门底下递出来给查票员检查?”这个时候厕所门下面递出来一张票。查票员看了没有任何问题,于是就离开了。

 

三个软件专家看完了这一幕,十分惊叹。心想,数学家就是比搞软件的人聪明,我们怎么从来没有想到这一招呢?于是,他们相约回程再同乘一次车。

 

过了几天,开完会后,六个人在回程中又聚到一起。见面后,三个软件专家就迫不及待的告诉数学家们:“我们这次学精了,也只买了一张票。你们是否也是只买了一张票?”

 

只见数学家们微笑着,不懂声色地回答说:“我们这次没有买票。”

 

软件专家一听,不敢相信自己的耳朵。接着问道:“那你们这次怎么应付查票呢?”

 

数学家们丢下一句话“你们自己想吧。”

 

计算机专家想了半天,实在是想不出来有什么办法可以应付查票员。没有办法,只好硬着头皮来问数学家:“我们想不出来,你们告诉我们吧。”

 

数学家们仍然微笑着:“你们的脑袋不够用了吧。看我们再教你们一招,好好学着点,免得将来说话这么没水平。”

 

过了不久,查票开始了。三个软件专家急急忙忙跑到厕所里面,将门从里面反锁上。这时有人敲厕所的门,说查票了,里面有人没有。里面传来一个声音“有人,但正在上厕所,无法开门,能否将车票从门底下递出来检查?”这个时候厕所门下面递出来一张票。

 

门外的人接了车票,却再也没有递回来...

 

差不多精神

这个幽默对于学软件的人来说,也许不会感到很幽默。可对于学数学的人来说,听了后感觉很爽。搞软件的人认为是数学家挣钱挣不过搞软件的人,于是编造这么个故事来寒碜搞软件的人,乘机阿Q一把。

 

不过沉下心来想一想,发现数学家寒碜搞软件的人还真有点道理。这个道理不是别的,而是因为数学是严谨的学科,一切都以精确为追求(这里指纯数学,不包括那些不被认为是数学的应用数学)。而软件呢,却没有任何精确可言,是十足的差不多学科。因为,软件是一门人造学科,它没有对与错(这里指的是同一功能的不同实现,而不是说程序不可能出现错误),只有好或坏。我们设计软件的时候,也是觉得差不多就可以了。而没有什么精确的追求。

 

如果不信,就看看我们是如何分析算法的吧。我们使用所谓的渐近分析,将系数和非决定项都抛去了。所谓的只要数量级对就差不多。至于软件可靠性、健壮性、和成本估算,那就更不用说了,连差不多都不如,而是差很多。

 

操作系统作为一种软件,自然也是差不多就可以了,其中的许多设计都是各种折中的结果,到处体现着差不多精神。读者在后面学习操作系统时只要留心观察,就会发现这些差不多的存在。而要理解这种差不多系统,自然需要抱着一种差不多的态度。如果一切吹毛求疵,学习操作系统会非常痛苦,也很难精确(不好意思,用了一次“精确”)把握操作系统的精髓。

 

令人欣慰的是,中国人自古就有差不多的传统。因此采纳差不多的态度对于中国人是很容易的事情。从这种角度看,学好操作系统是水到渠成的事情。(这里需要强调的是,作者并不是说计算机学科里面没有任何需要精确的时候,而是说在计算机学科里面体现差不多的地方非常多,以至于整个学科都带有某种差不多的精神)。

 

我们前面说过,操作系统是一个软件,它运行在硬件上,又为更加上层的应用软件提供服务。因此,对底层硬件的了解将帮助我们更好的把握操作系统。下面我们就从计算机硬件开始介绍,探讨一下操作系统的主要概念。

 

3.1计算机硬件基本知识

 

从概念上讲,计算机的结构非常简单:首先布置一根总线,然后将各种硬件设备挂在总线上。所有的这些设备都有一个控制设备,外部设备都由这些控制器与cpu通信。而所有设备之间的通信均需通过总线。如图3-1所示。图中的蓝色线条为总线。

OSPrinciple2-1

3-1 计算机结构概览图

 

为了提高计算机的效率,人们又设计出了流水线结构,即仿照工业流水装配线,将计算机的功能部件分为多个梯级,并将计算机的每条指令分拆为同样多个步骤,使每条指令在流水线上流动,到流水线最后一个梯级时指令执行完毕。而流水线上的每个梯级都可以容纳一条指令同时执行,如图3-2所示。

OSPrinciple3-2

3-2 一个5个梯级的流水线结构

 

而为了进一步提高计算机的效率,在流水线的基础上,人们又发明了多流水线、超标量计算和超长指令字等多指令发射机制。这些机制的发明在提升计算机效率(主要是吞吐量)的同时,也极大地增加计算机结构的复杂性,并对操作系统和编译器都提出了更高的要求。

复件 OSPrinciple3-2

3-3 一个4个梯级的超标量发射结构

 

3-3描述的是一个超标量发射的体系结构。这个结构有两队指令读取和译码单元,三个执行单元。通过一个指令保持缓冲区,就可以实现多路复用(Multiplex)和反多路复用(De-multiplex),从而提高系统每个功能单元的利用率和整个系统的吞吐量。

 

除了指令执行单元外,计算机里面的另一个重要部件是指令的存放单元,即所谓的存储架构。存储架构包括了缓存、主存、磁盘、磁带。有的情况下还存在多级缓存和外部光盘。图3-4描述的是一个包括寄存器的5级存储介质构成的存储架构。

OSPrinciple3-4.jpg

3-4 典型的5级存储结构

 

从寄存器到磁带,每一级的存储媒介的访问延迟和容量均依次增大,而价格却依次降低。即寄存器的访问速度最快,容量最小,但成本最高;而磁带的访问速度最慢,容量最大,成本却最低。通过合理的搭配,可以形成一个性能价格比颇佳的存储架构。

 

磁盘是计算机的主要存储媒介。可以说,没有磁盘,计算机就不成其为计算机,或者说计算机的用处就要大打折扣。虽然确实存在无磁盘的计算机(所谓的Diskless计算机),但这些计算机都有特别之用,并不是给一般用户用的。磁盘从概念上看非常简单非常简单,每个磁盘有多块盘片,盘片两面都可以存储。图3-5描述的是典型的磁盘结构。

OSPrinciple3-5

3-5 典型的磁盘内部结构

 

中断是计算机里面的一个最为重要的机制,它也是操作系统获得计算机控制权的根本保证。没有中断,很难想象操作系统能够完成人们所赋给的任务。中断的基本原理是:设备在完成自己的任务后向CPU发出中断,CPU判断优先级,然后确定是否响应。如果响应,则执行中断服务程序,并在中断服务程序执行完后继续原来的程序。图3-6简单地描述了中断机制。

OSPrinciple3-6

3-6磁盘中断的响应流程

 

中断是很复杂的过程,中断处理过程中又可以发生中断,且还可以有所谓的软中断,即软件发出的中断。透彻理解中断对了解计算机操作系统的运行具有重要意义。因此,对中断机制不甚了解的读者请复习复习在计算机组成与体系结构中的中断内容。

 

3.2 抽象

我们已经多次提到过,操作系统提供的是一个抽象。所谓的抽象,就是在根本上存在但现实中不存在的东西,即所谓exist in essence but not in reality。那么到底怎样理解抽象呢?

 

抽象来源于具体,但又超越具体。例如,人是具体的动物。但如果将人的具体属性,如肉体和骨架全部剥离,剩下的就是抽象,即人的灵魂。绘画史上有所谓的抽象派,而抽象画画的就是现实中不存在的东西,但这些东西确实又来源于现实,如瓦西里康定斯基的“海战”。

复件 kandinsky1000

3-7康定斯基的“海战”:来源于现实,又高于现实

 

操作系统提供的抽象自然也来源于现实,就是具体的计算机硬件,CPU、内存、IO设备等。但又超出这些现实而给人提供了强于现实的东西,使人和应用软件感觉到更多、更好的硬件存在。而且只有在操作系统层面上,一般的人才会觉得计算机是可以使用的。

 

另外,抽象不光是操作系统提供给用户的一个存在,而且它也存在于操作系统内部。操作系统内部分为不同的功能块,而不同的功能块之间互相提供的也是抽象。

 

3.2内核态和用户态

 

就像世界上的人并不平等一样,并不是所有的程序都平等的。世界上有的人占有资源多,有的人占有资源少,有的人来了,别人得让出资源,有的人则专门为别人让出资源。程序也是这样,有的程序可以访问计算机的任何资源,有的程序则只能访问非常受限的少量资源。而操作系统作为计算机的管理者,自然不能和被管理者享受一样的待遇。它应该享有更多的方便或特权。为了区分不同的程序这种权利的不同,人们发明了内核态和用户态的概念。

 

那么什么是内核态,什么是用户态呢?只要想一想现实生活中,处于社会核心的人与处于社会边缘的人有什么区别?处于核心的人拥有的资源多!因此,内核态就是拥有资源多的状态,或者说访问资源多的状态,我们称之为特权态。相对来说,用户态就是非特权态,在此种状态下访问的资源将受到限制。如果一个程序运行在特权态,则该程序就可以访问计算机的任何资源,即它的资源访问不受限制。如果一个程序运行在用户态,则其资源需求将受到各种限制。

 

例如,如果要访问操作系统的内核数据结构,如进程表,则需要到特权态下才能办到。如果要访问用户程序里的数据,则在用户态就可以了。

 

由于内核态的程序可以访问计算机的所有资源,这种程序的可靠性和安全性就十分重要。试想如果一个不可靠的程序在内核态下修改了操作系统的各种内核数据结构,结果会怎样呢?整个系统有可能崩溃。而运行于用户态的程序就比较简单了,如果其可靠性和安全性出了问题,其造成的损失只不过是让用户程序崩溃,而操作系统将继续运行。

 

很显然,内核态和用户态各有优势:运行在内核态的程序可以访问的资源多,但可靠性、安全性要求高,维护管理都较复杂;用户态程序程序访问的资源受限,但可靠性、安全性要求低,自然编写维护起来都就较简单。一个程序到底应该运行在内核态还是用户态则取决于其对资源和效率的需求

 

一般来说,一个程序能够运行于用户态,就应该让它运行在用户态。只在迫不得已的情况下,才让程序运行于内核态。只要看看一个国家的治理就清楚了。我们拿什么标准来判断什么事情应该归国家领导管理,什么事情应该大众来做?凡是牵扯到计算机本体根本运行的事情都应该在内核态下执行,而只与用户数据和应用相关的东西则放在用户态执行。另外,对时序要求特别高的事情,也应该在内核态做。你有没有想过,国家领导出门怎么不塞车呢?

 

那么什么样的功能应该在内核态下实现呢? 首先,CPU 的管理,内存管理都应该在内核态实现。这些功能可不可以在用户态下实现呢?当然能,但是不太安全。就像一个国家的军队(CPU和内存在计算机里的地位就相当于一个国家的军队的地位)交给老百姓来管一样,是非常危险的。所以从保障计算机安全的角度来说,CPU和内存的管理必须在内核态实现。

 

诊断与测试程序也需要在内核态下实现。因为诊断和测试需要访问计算机的所有资源,否则怎么判断计算机是否正常呢?就像中医治病,必须把脉触摸病人。你不让中医触摸,他怎么能看病呢(当然,很多人认为中医是伪科学,根本治不了病,本书对此问题不做讨论)?输入输出管理也一样,因为要访问各种设备和底层数据结构,也必须在内核态实现。

 

对于文件系统来说,则可以一部分放在用户态,一部分用在内核态。文件系统本身的管理,即文件系统的宏数据部分的管理,必须放在内核态;不然任何人都可能破坏文件系统的结构;而用户数据的管理,则可以放在用户态。编译器、网络管理的部分功能、编辑器、用户程序,自然都可以放在用户态下执行。3-6描述的是Windows操作系统的内核态与用户态的界线。

OSPrinciple1-9

3-6 Windows操作系统的内核态与用户态的界线

 

态势的识别

那么计算机是如何知道现在正在运转的程序是内核态程序呢?而正确作出内核态或用户态的判断对系统的正确运行至关重要。显然作出这种判断需要某种标志。这个标志就是处理器的一个状态位。这个状态位是CPU状态字里面的一个字位。这就是说,所谓的用户态、内核态实际上是处理器的一种状态,而不是程序的状态。我们通过设置该状态字,可以将CPU设置为内核态、或者用户态、或者其它的子态(有的CPU有更多种子态)。一个程序运行时,CPU是什么态,这个程序就运行在什么态。

 

内核态与用户态的实现

前面说过,内核态是特权态,而用户态是普通态。特权态下运行的程序可以访问任何资源,而用户态下的访问则受到限制。那么这种限制是如何实现的呢?

 

显然,要限制一个程序对资源的访问,需要对程序执行的每一条指令进行检查才能完成。而这种检查就是地址翻译。程序发出的每一条指令都要经过这个地址翻译过程。而通过对翻译的控制,就可以限制程序对资源的访问。关于地址翻译本书将在内存管理一章详细阐述。

 

而为了给内核态程序访问所有资源的特权,系统处于内核态时,内核程序可以绕过内存地址翻译而直接执行特权指令如停机指令。这种绕过翻译的做法突破了系统对资源的控制。

 

本书在讲完进程和内存后,将再次讨论内核态与用户态的议题。

 

3.4 操作系统结构

操作系统的结构也和操作系统历史类似,经历了好几个阶段。在操作系统刚刚出现时,人们还没有意识到操作系统的存在,也没有将那些库函数称为操作系统。这个时候,人们想到什么功能,就把这个功能加进来,并没有对所有这些功能进行统筹兼顾的计划。自然,这个时候的操作系统就是杂乱的、无结构的。

 

而随着操作系统的进化,人们对操作系统的认识逐步加深,操作系统慢慢变得有一些结构了。各种功能被归为不同的功能块,每个功能块相对独立,又经过固定的界面互相联系。任意一个功能块可以调用另一个功能块的服务。整个操作系统本身是一个巨大单一体(Monolithical System),运行在内核态下,为用户提供服务。如图3-7所示。

OSPrinciple3-7

3-7 单一体的操作系统结构

 

后来人们发现单一体的操作系统结构有很多缺点:功能块之间的关系复杂,修改任意功能块导致其它所有功能块都需要修改,从而导致操作系统设计开发的困难;这种没有层次关系的网状联系容易造成循环调用,形成死锁,从而导致操作系统可靠性降低。这时候,人们想到了人类社会里面的层次关系,何不将人类熟悉的层次关系搬到操作系统设计里来,给操作系统也定义个层次关系呢?即将操作系统的功能分成不同层次,低层次的功能为紧邻其上一个层次的功能提供服务,而高层次的功能又为更高一个层次的功能提供服务。就像人类团体里面的结构:村长à镇长à县长à市长à....。如图3-8所示。

 

OSPrinciple3-8

3-8 层次化的操作系统结构

 

 

从图3-73-8可以看出,操作系统的所有功能都在内核态下运行。而这带来几个问题。首先,操作系统的所有服务都需要进入内核态才能使用,而从用户态转换为内核态是有时间成本的,这样就造成操作系统效率低下。在操作系统还比较简单时这个问题并不突出,但随着操作系统功能和复杂性的增加,这种效率的低下就十分明显了。

 

其次,我们前面说过,在内核态运行的程序可以访问所有资源,因此其安全性和可靠性要求十分高。在操作系统很小时,将其设计得可靠和安全不是特别困难。再说,在操作系统历史的早期也没有那么多坏人(当然,不是那个时候坏人少,而时那个时候大部分坏人还不会用计算机),自然安全上的考虑就不用太多。但随着操作系统越来越大,坏人的水平越来越高,操作系统的可靠性和安全性就变得很难达到。只要想一想,1400行的操作系统和40,000,000行的操作系统有什么区别就知道了。

 

因此,人们又想出了一个办法,就是微内核结构,即只将操作系统核心中的核心放在内核态运行,其它功能都移到用户态。这样就同时提高了效率和安全性。

 

OSPrinciple3-9

3-9 微内核的操作系统结构

 

各种操作系统结构各有优缺点,但当前的趋势是第三种模式,即微内核的操作系统结构。至于这个微内核到底有多“微”,则是仁者见仁、智者见智的。例如,美国卡内基梅农大学开发的Mach操作系统的内核非常小,而微软的Windows XP的内核就大多了。

 

3.3 进程、内存、和文件

进程是操作系统里面的核心概念。它指的是一个运动中的程序。从名字上看,进程表示的就是展中的序。即一旦一个程序在计算机里动起来,它就成为一个进程。操作系统对进程的管理通过进程表来实现。进程表里存放的是关于进程的一切信息。在任何一个时候,进程所占有的全部资源,包括分配给该进程的内存、内核数据结构和软资源形成一个所谓的进程核Core。而一个核快照Core Image代表的是进程在某一特定时刻的状态。

 

如果在linuxUNIX下编程序,在出现分段错误Segmentation Fault时,操作系统会自动进行核倒出(Core Dump)。Core Dump把所有计算机的状态保存在一个文件中,通过阅读这个文件的内容可以得知出界时的进程状况,从而帮助对程序的调试。

 

进程与进程之间可以进行通信、同步、竞争,并在一定情况下可能形成死锁。这些概念都将在进程一章进行详细的阐述。

 

内存是操作系统里面的另一个核心概念。它是进程的存放场所。如何对内存进行管理,使得数据的读写具有高效率、高安全、高空间利用率和位置透明的特性是内存管理所要达到的目的。

 

文件是操作系统提供的外部存储设备的抽象,它是程序和数据的最终存放地点。如何让用户的数据存放变得容易、方便、可靠、和安全是文件系统要解决的问题。

 

3.4 系统调用

我们说过,操作系统是一个系统程序,即为别的程序提供服务的程序。那操作系统的服务是通过什么方式提供的呢?答案是系统调用(System Call)。系统调用就是操作系统提供的应用程序界面API。用户程序通过调用这些API而获得操作系统的服务。例如,如果用户程序需要进行读磁盘操作,在C程序代码里将使用下面的语句:

 

result=read(fd, buffer, nbytes);

 

这个read函数是C语言提供的库函数,而这个库函数本身则是调用的操作系统的read系统调用。注意这里有两个read,一个是read库函数,由程序语言提供;一个是read系统调用,由操作系统提供。编译器在看到上述语句后将read库函数扩展为read系统调用。在真正执行时,操作系统将完成上述文件的读操作。

 

系统调用按照功能可以划分为六大类:

  • 进程控制类
  • 文件管理类
  • 设备管理类
  • 内存管理类
  • 信息维护类
  • 通信类

 

系统调用一般不在操作系统原理的课程中论述,而是留给操作系统编程或系统编程的课程来覆盖。这里我们简单说一下系统调用的过程。系统调用分为三个阶段,分别是:

  • 参数准备阶段
  • 系统调用识别阶段
  • 系统调用执行阶段

 

在参数准备阶段,需要使用系统服务的程序将系统调用所需要的参数,如上述例子中的fd, buffer, nbytes,压到栈上。然后调用库函数read。库函数read将系统调用read的代码放在一个约定好的寄存器里,通过陷入(trap,一种中断方式)将控制交给操作系统。由此进入到第二个阶段。操作系统获得控制后,将系统调用代码从寄存器里取出,与操作系统维护的一张系统调用表进行比较,获得系统调用read的程序体所在的内存地址。之后跳到该地址,进入到第三阶段,执行系统调用函数。系统调用执行完毕后返回到用户程序。

OSPrinciple3-8

3-10 Read系统调用的过程

 

系统调用中的参数传递

从图3-10可以看出,read系统调用的参数被压入到栈里面。即参数传递是通过栈来进行。但这并不是唯一的参数传递办法。事实上,这还不是效率最高的传递方法。效率最高的方法是将参数存放在指定的寄存器里面。由于寄存器的访问速度高于栈,这种参数传递将可以提升系统调用执行的效率。例如,在x64体系结构下,最前面的8个参数由寄存器传递。只有超过8个参数时,后面的参数才通过栈来传递。

 

3.5 Shell

前面一节说明了操作系统是如何给用户程序提供服务的。用户程序通过调用操作系统提供的系统调用API来获得操作系统的各种服务。但使用API需要编程。对于不编程序的用户来说,或对于需要与操作系统进行交互的用户来说,又怎么使用操作系统的服务呢?

 

为这些不编程的用户,操作系统提供了一个壳,即Shell,来与用户交互。每个操作系统都提供某种壳,以便于与用户进行交互。这个壳是覆盖在操作系统服务上面的一个用户界面,既可以是图形界面,也可以是文本界面。用户在这个界面上输入命令,而操作系统对则执行这些命令。当然,用户输入的命令不是直接的操作系统服务,而是所谓的UtilitiesUtilities的功用相当于C里面的库函数。因为用户不能直接调用系统调用(为什么不能呢?读者知道吗),C语言提供了库函数来解决这个问题。

 

同理,在壳上用户也不能直接使用操作系统的服务,而是通过Utilities来获得操作系统服务。UnixLinux的壳都是文本形式,而windows的壳是图形界面的。在UnixLinux里,要启动一个壳只需要运行shell即可。在Windows里面,要启动壳需要执行explore.exe。在LinuxUnix下可以同时启动多个壳,而在Windows下只能启用一个壳。2006年,微软推出的了Powershell,从此改变了在Windows下只能启用一个壳的限制。Powershell是一个文本命令壳,可以运行在Windows XP SP2Windows Server 2003, Windows Vista, Windows Server 2008上。Powershell提供了一种类似于UnixLinuxutilities的东西,称作cmdlets。用户通过键入cmdlets里面不同的命令而指挥计算机进行各种操作。

 

一个壳的具体功能包括如下几项:

l  显示提示符,如Unix下的提示符通常为$%

l  接受用户命令并执行

l  实现输入输出间接(或间接输入输出)

l  启动后台进程

l  进行工作控制

l  提供伪终端服务

 

例如:我们可以在Unix shell上执行下述命令:

键入:$ date

显示:$ September 16, 2008

 

我们还可以进行输出间接(或间接输出):

键入:$ date >file

显示:$

上述命令行里面的”>”符号是间接符合,将输出从显示屏转移到了文件file。如果打开文件file,里面的内容将是September 16, 2008

 

我们还可以同时进行输入和输出间接(或间接输入输出)

$ sort <file1 >file2

上述命令将file1里面的内容进行排序,然后将排序后的结果存放在文件file2里。

 

我们还可以将输出间接到打印机:

$ cat file1 file2 file3 > /dev/lp1

上述命令将file1file2file3里面的内容进行连接,然后将结果发送到打印机lp1上打印。

 

我们可以启动后台程序:

$ make all >log &

上述命令启动一个编译后台程序,将编译的结果输出到文件log里。

 

那么壳是怎么实现的呢?我们下面看一个最为简单的壳,Unix Shell

while (TRUE) {                                                /* 循环往复,以致无穷 */

type_prompt( );                                     /* 显示命令提示符*/

read_command (command, parameters)   /* 从用户获得命令 */

if (fork() == 0) {                                         /* fork一个子进程 */

    execve (command, parameters, 0);   /* 执行用户命令*/

}

else {                                                         /* 父进程代码段 */

    waitpid( -1, &status, 0);                  /* 等待子进程结束 */

}

}

 

这个壳一旦启动,就循环往复直到无穷。它所做的事情很简单:

1.       显示命令提示符

2.       等待用户输入命令

3.       使用fork创建一个子进程

4.       使用execve在创建的子进程里执行用户输入的命令

5.       重复步骤1-4

 

上述程序片断里的forkexecve均为Unix操作系统提供的系统调用。Fork的功能是创建一个子进程,并将自己的一切数据复制到子进程里,也就是说,fork完成的实际上是自我复制。Execve的功能是用另外一个程序的内容覆盖自己,也就是即执行新的程序。

 

这里值得一提的是,在上述程序代码里,fork命令有两次返回:一次返回值为0,表明是子进程(被创建的新进程),对应的是if-else复合语句里面的if部分;另一次返回不是0,是为父进程,对应if-else复合语句的else部分,而这个返回值就是子进程的进程ID。也就是说,复合语句if-elseifelse部分在fork完成后都将被执行。这听上去奇怪吗?

 

如果奇怪,那时因为你看到的是一个程序。我们知道,在一个程序里,if-else语句的ifelse两个部分只能有一个部分被执行,而不能两个部分同时执行。我们还知道,一个函数是无法返回两次的。因为你只要写了return,后面的语句就不能再执行了。也就是说,return后面无法再return了。即一个函数不能返回两次。那么Fork命令为什么能返回两次呢?

 

这是因为fork的特殊功能使然。Fork的功效是创建一个和自己完全一样的进程。在fork系统调用完成后,我们面对的是两个进程,而不再是一个进程。这两个进程的程序代码完全一样,就是我们上面给出的代码。在其中一个进程中,执行的是if部分,在另一个进程中,执行的是else部分。因此,从每一个进程内部来看,复合语句if-else的语义并没有被破坏。

 

而且,既然在fork后有了两个拷贝,而每个拷贝里都有同样的fork调用语句。从另一个角度讲,有两个程序调用了fork。这两次调用都需要返回。从内存结构看,而这两个程序拷贝或者进程都有自己的调用堆栈,并都有调用返回的位置。而对于每一个进程来说,它调用了一次fork,获得了一次返回。因此,从每个进程内部看,函数返回一次的语义也得到遵守。

 

那么系统中的两个一模一样的进程如何区分彼此呢?简单,给每个进程一个不同的返回值即可。返回给父进程的是子进程的进程ID,对应else部分;返回给子进程的是0,对应if部分。

 

本章小结

本章论述了操作系统的基本概念。这些概念是为下一步的进程、内存、文件、和输入输出等章节的学习打下基础。在这些概念里面,最为关键的是内核态和用户态的定义极其关系。当然,抽象是操作系统的根本,而系统调用和操作系统结构也非常重要。

 

思考题

1.         操作系统提供的用户界面有几种?分别是什么?

2.         举出几个人类社会中采用的抽象,并与操作系统提供的抽象进行对比。

3.         有人说,内核态程序可以访问任何资源的权利对系统安全造成严重威胁,你怎么看?

4.         处理器的状态进行设置需要在何种态势下完成?为什么?

5.         处理器从用户态转为内核态时面临的关键问题是什么?如何解决?

6.         下述操作是否可以在用户态执行?

a)         保护中断现场

b)        因系统调用陷入内核

c)         启动外部设备

d)        外设与内存经DMA进行数据交换

e)         缺页中断

7.         论述系统调用和壳之间的关系。

8.         操作系统采用层次结构的优点是什么?层次结构有什么缺点吗?

9.         Fork是如何实现一次调用,两次返回的?它必要吗?为什么?

10.     请调研当前工业界通用系统里面缓存、主存、磁盘、磁带的平均容量。

11.     挑战题:内核态的特权是如何实现的?

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值