操作系统之哲学原理(第2版)

 

 

 

 

第一篇 基础原理篇

 

 

 

 

 中断是操作系统获得计算机控制权的根本保证。若没有中断,很难想象操作系统能够完成人们所赋予的任务。中断的基本原理是:设备在完成自己的任务后向CPU发出中断,CPU判断优先级,然后确定是否

响应。如果响应,则执行中断服务程序,并在中断服务程序执行完后继续执行原来的程序。

 

 

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

 

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

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

 

系统调用分为三个阶段,分别是:
●参数准备阶段。
●系统调用识别阶段。
●系统调用执行阶段。

 

 

第二篇 进程原理篇

 

 

进程从根本上说是操作系统对CPU进行的抽象和装扮。进程让每个用户感觉到自己独占CPU。

进程出现的动机是人类渴望的并发。因为要并发,所以我们发明了进程。

进程的出现也让操作系统的复杂性大为增加:由于需要对进程进行分离存储而导致出现内存管理;由于需要让不同进程有条不紊地往前推进而导致进程调度的出现。

 

 

 

进程管理、内存管理和文件管理是操作系统的三大核心功能。

进程就是进展中的程序,或者说进程是执行中的程序。就是说,一个程序加载到内存后就变为进程。即:
进程=程序+执行

 

人们发明进程是为了支持多道编程,而进行多道编程的目的则是提高计算机CPU的效率,或者说系统的吞吐量。

 

 

 

 

进程空间也称为地址空间。地址空间就是进程要用的所有资源。

操作系统用于维护进程记录的结构就是进程表或进程控制块(Process Control Block, PCB)。

 

 

 

进程的缺点:
1、只能在一个时间做一件事情。如果想同时做两件或多件事情,进程就不够用了。
2、如果进程在执行的过程中阻塞,例如等待输入,整个进程就将挂起(暂停),而无法继续执行。


而为了解决上述两个问题,人们就发明了线程。

 

 

第三篇 线程原理篇

 

 

 从根本上说,线程是操作系统给进程模型提供并发能力的手段。

 

 

 

 

●线程之间如何通信?

●线程之间如何同步?
如何通信(沟通)和如何同步(协调)

 

并发既提高了系统的效率或者说吞吐率,又改善了用户感觉到的响应时间。

虽然线程的优势很明显,但带来的问题也是显而易见的,那就是系统运行的不确定性。

那么如何在保持线程这个概念的同时,消除其执行结果的不确定性呢?答案是线程的同步。

线程之间的同步则对程序的正确运行至关重要。

 


同步就是让所有线程按照一定的规则执行,使得其正确性和效率都有迹可寻。

协调的目的就是在任何时刻都只能有一个人在临界区里,这称为互斥(mutualexclusion)。互斥就是说一次只有一个人使用共享资源,其他人皆排除在外,并
且互斥不能违反前面给出的进程模型。因此,正确互斥需要满足4个条件:
●不能有两个进程同时在临界区里面。
●进程能够在任何数量和速度的CPU上正确执行。
●在互斥区域外不能阻止另一个进程的运行。
●进程不能无限制地等待进入临界区。

一个程序调用sleep后将进入休眠状态,将释放其所占用的CPU。一个执行wakeup的程序将发送一个信号给指定的接收进程,

信号量(semphore)就是一个计数器,其取值为当前累积的信号数量。它支持两个操作:加法操作up和减法操作down

 

锁解决了同步问题,但带来的是循环等待。为了消除循环等待,发明了睡觉与叫醒。但睡觉与叫醒又带来了死锁,因此发明了信号量。

如果能够将信号量的这些组织工作交给一个专门的构造来负责,程序员不就解脱了吗?于是发明了管程。管程的英文单词是monitor,即监视器的意思。它监视的就是进程或线程的同步操作。


管程就是一组子程序、变量和数据结构的组合。言下之意,把需要同步的代码用一个管程的构造框起来,即将需要保护的代码置于begin monitor和endmonitor之间,即可获得同步保护。在任何时候只能有一个线程活跃在管程里面。那谁来保证这一点呢?编译器。编译器在看到begin monitor和end monitor时知道其间的代码需要同步保护,在翻译成低级代码时就会将需要的操作系统原语添上,使得两个线程不能同时活跃于同一个管程内。

管程的中心思想是运行一个在管程里面睡觉的线程。但是在睡觉前需要把进入管程的锁或信号量释放,否则在其睡觉后别的线程将无法进入管程,就会造成死锁。


实现锁的释放和睡觉这两件事情必须是原子操作,即中间不能有空当,否则将造成两个线程同时活跃在管程里,这样就违反了关于管程的约定。


管程里面的两个操作wait和signal的语义分别如下:
wait(x)以原子操作完成下述3个步骤:
1)释放锁。
2)将本线程挂在条件变量x的等待队列上。
3)睡觉,等待被叫醒。

signal则与我们前面讲过的一样,将等在指定条件变量上面的第1个线程叫醒。在叫醒方面,管程还提供另外一个所谓的广播(broadcast)原语,其语义是将指定条件变量上面的所有等待线程全部叫醒。
这里需要注意的是,在一个线程调用wait、signal或者broadcast之时,该线程必须持有与管程相连的锁。

使用各种机制动态地调整每个线程获取锁的优先级。这种管程就称为MESA管程。

MESA管程的signal处理方式如下:叫醒者在发出signal后释放锁;被叫醒者与叫醒者同时竞争这把锁。谁先获得锁,谁先执行。

管程最大的问题是对编译器的依赖。

消息传递是通过同步双方经过互相收发消息来实现。它有两个基本操作,发送send和接收receive。它们均是操作系统的系统调用,而且既可以是阻塞调用,也可以是非阻塞调用。
-send(destination,&message);
-receive(source,&message);

那么消息传递有什么问题没有?有。最大的问题就是消息丢失和身份识别。使用消息传递的另外一个缺点就是效率。往返发送消息存在系统消耗。另外,数据传输也存在延迟。如果网络速度很慢怎么办呢?


栅栏(barrier)是本章最后要讲的一个通信原语。顾名思义,栅栏就是一个障碍。到达栅栏的线程必须停止下来,直到除去栅栏后才能往前推进。该原语主要用来对一组线程进行协调。因为有时候一组进程协同完成一个问题,所以需要所有进程都到同一个地方汇合之后一起再向前推进。

 

线程使用资源的顺序通常如下:
1)请求资源。
2)使用资源。
3)释放资源。

 

 

死锁的发生必须满足4个条件:
条件1:死锁发生的必要条件是资源有限。
条件2:持有等待。即一个线程在请求新的资源时,其已经获得的资源并不释放,而是继续持有。
条件3:不能抢占。
条件4:循环等待条件。

 

 

第四篇 内存原理篇


内存管理从根本上说是操作系统对存储设备进行的抽象和装扮。

通过虚拟内存这种机制,操作系统为程序员或用户提供了4种抽象:
●程序的地址独立性
●地址空间的保护
●内存空量的巨量或无限扩大
●内存访问速度的大幅度提升
操作系统为实现虚拟内存而使用的“原料”却很简单:一点点缓存,一些主存和便宜的磁盘;其采用的机制也简单至极:动态地址翻译!

 

 

在这个内存的现实架构中,缓存的特点是低容量(相对主存来说)、高速度、高价格;主存的特点则是中容量、中速度和中价格;磁盘则属于大容量、低速度、低成本的存储媒介;磁带则通常更持久但速度更慢。

 

内存管理要达到的目标:
●地址保护:一个程序不能访问另一个程序地址空间。
●地址独立:程序发出的地址应与物理主存地址无关。


虚拟内存的中心思想是将物理主存扩大到便宜、大容量的磁盘上,即将磁盘空间看做主存空间的一部分。

虚拟内存是操作系统发展历史上的一个革命性突破(它也是使操作系统变得更加复杂的一个主要因素)。因为有了虚拟内存,我们编写的程序从此不再受尺寸的限制(当然还受制于虚地址空间大小的限制)。

11.4 操作系统在内存中的位置
从根本上来说,计算机里面运转的程序有两种:管理计算机的程序和使用计算机的程序。

内存管理的第一个问题是操作系统本身在内存中的存放位置。应该将哪一部分的内存空间用来存放操作系统呢?或者说,我们如何将内存空间在操作系统和用户程序之间进行分配呢?

多道编程的代价是什么呢?当然是操作系统的复杂性。因为多道编程的情况下,无法将程序总是加到固定的内存地址上,也就是无法使用静态地址翻译。这样我们就必须在程序加载完毕后才能计算物理地址,也就是在程序运行时进行地址翻译,这种翻译称为动态地址翻译。

 

 

那么多道编程的内存管理是如何进行动态地址翻译的呢?那得看内存管理的策略。多道编程下的内存管理策略有两种:固定分区和非固定分区。

 

在管理内存的时候,操作系统需要知道内存空间有多少空闲。如何才能知道有哪些空闲呢?这就必须跟踪内存的使用。跟踪的办法有两种:第1种办法是给每个分配单元赋予一个字位,用来记录该分配单元是否闲置。

例如,字位取值0表示分配单元闲置,字位取值1则表示该分配单元已被占用。这种表示法就是所谓的位图表示法

 

 

另外一种办法是将分配单元按是否闲置链接起来,这种办法称为链表表示法。

 

 

位图表示和链表表示各有优缺点。如果程序数量很少,那么链表比较好,因为链表的表项数量少。位图表示法的空间成本是固定的,它不依赖于内存中程序的数量。
从可靠性上看,位图表示法没有容错能力。
从时间成本上,位图表示法在修改分配单元状态时,操作很简单,直接修改其位图值即可,而链表表示法则需要对前后空间进行检查以便做出相应的合并。

 

 

 

 

 

 

 

 

第八篇 操作系统设计篇

●保证操作系统本身运行正确。
●提供尽可能多的功能。
●尽量提高系统的效率。
●在追求效率的基础上尽量顾及公平。

 

 

 

操作系统设计的第1条哲学原理:层次架构
第2条哲学原理:没有对错
第3条哲学原理:懒人哲学
第4条哲学原理:让困于人
第5条哲学原理:留有余地
第6条哲学原理:子虚乌有——海市蜃楼之美
第7条哲学原理:时空转换——沧海桑田之变
第8条哲学原理:策机分离与权利分离
第9条哲学原理:简单为美——求于至简,归于永恒
第10条哲学原理:适可而止

操作系统本身并无对错之分,只有好坏之分。


越简单的架构效率越高。而且其正确性也比较容易确认。而复杂的东西,要证明其正确则通常颇费周折。

操作系统中策略与权力分离主要不是为了公平,而是为了实现的灵活性。

posted on 2020-01-10 20:14  trp  阅读(1186)  评论(0编辑  收藏  举报

导航