《Java架构师的第一性原理》11计算机基础之操作系统

1 为什么要有操作系统

现代计算机系统是基于冯·诺依曼架构设计的,主要由存储器、控制器、运算器、输入输出设备组成。

一般而言,现代计算机系统是一个复杂的系统。

其一:如果每位应用程序员都必须掌握该系统所有的细节,那就不可能再编写代码了(严重影响了程序员的开发效率:全部掌握这些细节可能需要一万年....)

其二:并且管理这些部件并加以优化使用,是一件极富挑战性的工作,于是,计算安装了一层软件(系统软件),称为操作系统。它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理刚才提到的所有设备。

总结:

程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。

2 什么是操作系统

2.1 什么是操作系统

精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。

操作系统位于计算机硬件与应用软件之间,本质也是一个软件。
操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成,
所以,单纯的说操作系统是运行于内核态的,是不准确的。

细说的话,操作系统应该分成两部分功能:

  • 隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节)
  • 将应用程序对硬件资源的竞态请求变得有序化。例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到打印机资源,也可能是c,这就导致了无序,打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序

举例来说:

作用一:为应用程序提供如何使用硬件资源的抽象

例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制

注意:

操作系统提供给应用程序的该抽象是简单,清晰,优雅的。为何要提供该抽象呢?

硬件厂商需要为操作系统提供自己硬件的驱动程序(设备驱动,这也是为何我们要使用声卡,就必须安装声卡驱动。。。),厂商为了节省成本或者兼容旧的硬件,它们的驱动程序是复杂且丑陋的操作系统就是为了隐藏这些丑陋的信息,从而为用户提供更好的接口。这样用户使用的shell,Gnome,KDE看到的是不同的界面,但其实都使用了同一套由linux系统提供的抽象接口。

作用二:管理硬件资源
现代的操作系统运行同时运行多道程序,操作系统的任务是在相互竞争的程序之间有序地控制对处理器、存储器以及其他I/O接口设备的分配。
例如:
同一台计算机上同时运行三个程序,它们三个想在同一时刻在同一台计算机上输出结果,那么开始的几行可能是程序1的输出,接着几行是程序2的输出,然后又是程序3的输出,最终将是一团糟

(程序之间是一种互相竞争资源的过程)

操作系统将打印机的结果送到磁盘的缓冲区,在一个程序完全结束后,才将暂存在磁盘上的文件送到打印机输出,同时其他的程序可以继续产生更多的输出结果(这些程序的输出没有真正的送到打印机),这样,操作系统就将由竞争产生的无序变得有序化。

2.2 两个问题

 (1)操作系统到底是什么鬼?

  操作系统是介于计算机和应用软件之间的一个软件系统,操作系统的上层和下层都有其他的对象存在:

  从上图可以看出,OS上边是应用软件,下边是硬件平台。

  (2)操作系统到底操控什么事?

  最原始的计算机并没有OS,直接由人来掌管事情。随着计算机复杂性的增长,人已经不能胜任直接掌控计算机了。于是,OS这个软件被编写出来帮我们掌控计算机,使人类从日益复杂的掌控任务中解脱出来。既然OS是专门掌控计算机的,那么计算机上发生的所有事情都需要OS的知晓和认可,未经OS同意的任何事情均被视为非法的(想想病毒和入侵攻击者试图做的事情)。

2.3 两种角色

魔术师(丑陋变得美好、没有变为有、少变为多)和管理者(管理计算机资源以达到公平和效率的双重境界)

(1)魔术家

魔术家的目标是把差的东西变好,把少的东西变多,把复杂变简单。同样,OS将计算机以一个更加容易、更加方便、更加强大的方式呈现给用户。

Example:OS通过进程抽象让每一个用户感觉有一台自己独享的CPU,通过虚拟内存抽象,让用户感觉物理内存空间具有无限扩张性,这就是把少变多的一个实例。

(2)管理者

操作系统管理计算机上的软硬件资源,如CPU、内存、磁盘等,使得不同用户之间或者同一用户的不同程序之间可以安全有序地共享这些硬件资源。

  那么,问题来了,如何让用户很好地利用这些硬件资源呢?这就是分块(Parcel Out),把硬件分块给应用程序使用。这就涉及到有效和公平的原则,这也是一个管理者的必备素质,更是设计操作系统时的不懈追求!  

操作系统的两个角色之间既有区别又有联系,为了完成不同的任务,OS有时需要扮演魔术师,有时有需要扮演管理者,还有时需要同时扮演两个角色。

3  操作系统历史

1)计算机发展过程

  • 第一代计算机(1940~1955):真空管和穿孔卡片
  • 第二代计算机(1955~1965):晶体管和批处理系统
  • 第三代计算机(1965~1980):集成电路芯片和多道程序设计
  • 第四代计算机(1980~至今):个人计算机

2)操作系统发展过程

  • 人工操作方式:用户独占全机,CPU等待人工操作--带(卡)装卸
  • 脱机输入/输出方式:事先将装有用户程序和数据的纸带装入纸带输入机,外围机控制,把纸带内容输入到磁带上(类似于磁盘),CPU需要时,从磁带高速调入内存。反之类同。优点:减少了CPU的空闲时间、提高了I/O速度
  • 单道批处理:首先监督程序将磁带第一个作业装入内存,运行控制权在该作业,处理完改作业后,控制权回到监督程序,然后进行重复过程,系统自动对作业成批处理。(内存始终只保持一道作业---单道批处理) 缺点:内存浪费,不能充分利用系统资源
  • 多道批处理:所提交的作业先存入外存,排成“后备队列”,再由作业调度程序按算法从队列调若干作业入内存。优缺点:资源利用率高、系统吞吐量大、平均周转时间长、无交互能力
  • 分时系统:作业直接进入内存,采用轮转运行方式,系统配置一个多路卡(实现分时多路复用),及时接收用户终端命令(数据)。特征:多路性、独立性、及时性、交互性
  • 实时系统:系统能及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务的协调一致的运行。特征:多路性(周期性信息采集,多个对象或执行机构进行控制)、独立性、及时性、交互性、可靠性(多级容错措施)

4  操作系统导论

4.1 程序运行的四大要素

(1)程序设计语言

  首先,我们得使用一门程序设计语言进行编程,一般我们使用的都是高级程序设计语言(如C、C++、Java、C#等)。

(2)编译系统

  我们写好了代码,但是由于计算机不认识高级语言编写的程序,需要编译成计算机能够识别的机器语言,这就需要编译器和汇编器的帮助。

(3)操作系统

  机器语言程序需要加载到内存,才能形成一个运动中的程序(即进程),这就需要操作系统的帮助。

About:进程需要在计算机芯片即CPU上执行才算是真正在执行,而将进程调度到CPU上运行也是由操作系统完成的,这里也就不难理解为什么进程管理会在我们的教科书中排在最重要的位置了。

(4)指令集结构(计算机硬件系统)

  在CPU上执行的机器语言指令需要变成能够在一个个时钟脉冲里执行的基本操作,这就需要指令集结构和计算机硬件的支持。

4.2 程序运行的基本流程

  基于上面提到的四大要素,我们可以得出下面一幅图,该图从一个线性角度展示了程序的演变过程,能够帮助我们理解整个程序是如何在计算机上执行的。

  事实上,程序可以执行在机器语言或汇编语言上编写,用这种被称为“低级”(我更愿意称其为底层)的语言编写出来的机器语言程序无需经过编译器的翻译就可以在计算机指令集上执行。如果是在汇编语言上编写的汇编程序,则只需要经过汇编器的翻译即可加载执行。

4.3 操作系统的基本特征

  • 并发:引入进程,并行并发,系统程序独立并发执行,提高资源利用率,增加系统吞吐量。
  • 共享:① 互斥共享,一段时间内只允许一个进程访问资源(临界资源)。② 同时共享,允许一段时间内多进程“同时”访问。
  • 虚拟:提高通信信道的利用率--“虚拟”技术,通过“空分复用”或“时分复用”,将一条物理信道(实)变为若干条逻辑信道(虚)。① 空分复用:利用存储器的空闲空间分区域存放和运行其他的多道程序 ② 时分复用:利用处理机的空闲时间运行其他程序
  • 异步:进程以不可预知的速度向前推进。

4.4 操作系统的主要功能

  • CPU管理功能:

    ①进程控制:创建和撤销进程,分配资源、资源回收,控制进程运行过程中的状态转换。

    ②进程同步:多进程运行进行协调--进程互斥(临界资源上锁)、进程同步。

    ③进程通信:实现相互合作之间的进程的信息交换。

    ④调度:作业调度,进程调度。

  • 内存管理功能:为多道程序的运行提供良好的环境,提高存储器的利用率,方便用户使用,并能从逻辑上扩充内存。

            ①内存分配:静态分配、动态分配。

    ②内存保护:各在其内存空间内运行(设两界限寄存器),互不干扰。

    ③地址映射:地址空间中的逻辑地址转换为内存空间中与之对应的物理地址。

    ④内存扩充:借助于虚拟存储技术,逻辑上扩充内存容量。

  • 外存(文件)管理功能:对用户文件和系统文件进行管理以方便用户使用,并保证文件的安全性。

    ①文件存储空间的管理:为文件分配合理外存空间,文件存储空间的使用情况。

    ②目录管理:为每个文件建立一个目录项。

    ③文件的读/写管理和保护

  • 设备(I/O)管理功能:完成用户进程提出的I/O请求,为其分配所需I/O设备,完成指定I/O操作;提高CPU和I/O设备的利用率,提高I/O速度,方便用户使用I/O设备。

    ①缓冲管理

    ②设备分配

    ③设备处理:设备驱动程序,用于实现CPU和设备控制器之间的通信。

  • 操作系统与用户之间的接口:用户接口,程序接口
  • 微内核操作系统:

       ①内核足够小

       ②基于客户/服务器

       ③“机制与策略分离”原理

       ④面向对象技术

5  操作系统基本概念

5.1 总线结构

  从概念上讲,计算机的结构是总线型的:布置一根总线将各种硬件设备挂在总线(Bus)上

  (1)所有的设备都有一个控制设备,外部设备通过控制器与CPU进行通信。

  (2)所有的设备之间的通信也需要通过总线。

5.2 流水线结构

  为了提高计算机的效率,人们又设计出了流水线结构:仿照工业流水装配线,将计算机的功能部件分为多个梯级,并将计算机的每条指令拆分为同样多个步骤,使每条指令在流水线上流动,到流水线最后一个梯级时指令执行完毕

  下图展示了5个梯级的流水线结构,流水线上的每个梯级都可以容纳一条指令并同时执行。

  在流水线的基础之上,为了进一步提高效率,人们又发明了多流水线、超标量计算和超长指令字等多指令发射机制。

5.3 存储结构

  除了指令执行单元外,计算机中的另一个重要部件是指令的存放单元,被称为存储架构。存储架构包括了缓存、主存、磁盘、磁带等。下图展示了一个包括寄存器在内的5级存储介质构成的存储架构。

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

5.4 中断机制

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

6  内核态与用户态

  操作系统作为计算机的管理者,享有着更多的方便或权限。为了区分不同的程序的不同权限,人们发明了内核态和用户态的概念。

6.1 两种状态的概念

  内核态就是拥有资源多的状态(或访问资源多的状态),也称为特权态。而用户态则是非特权态,在用户态下访问的资源会受到限制。例如,要访问OS的内核数据结构,如进程表等,则需要在特权态下才能做到。如果只需要访问用户程序里的数据,则在用户态下就可以了。

6.2 两种状态的优势

  内核态:访问资源多,但可靠性、安全性要求高,维护管理都比较复杂;

  用户态:访问资源有限,但可靠性、安全性要求低,维护起来比较简单;

  那么,一个程序到底应该运行在内核态还是用户态呢?这取决于其对资源和效率的需求;下图展示了Windows操作系统的内核态与用户态的界限,我们可以看到哪些需要在内核态下运行,哪些只在用户态下运行。

6.3 两种状态的本质

  计算机对于内核态和用户态的识别是通过CPU的一个状态位来实现的,这个状态位是CPU状态字里面的一个字位。所谓的用户态、内核态实际是CPU的一种状态,而不是程序的状态。通过设置该状态字,可以使CPU处于内核态、用户态或者其他的子态(有的CPU有更多种子态)。

  换句话说:一个程序运行时,CPU是什么态,这个程序就运行在什么态。

  那么,知道了是怎么实现的,那又是如何对用户态的访问进行限制的呢?在对用户态下程序执行的每一条指令进行检查,这种检查又被称为地址翻译,即对程序发出的每一条指令都要经过这个地址翻译过程(你可以将其理解为我们在实际开发中所作的权限管理,对用户发出的每个操作请求首先都经过一个Filter进行过滤),通过对翻译的控制,就可以限制程序对资源的访问。

7  层次化结构

将操作系统的功能分成不同的层次,低层次的功能为紧邻其上一个层次的功能提供服务,而高层次的功能又为更高一个层次的功能提供服务,如下图所示。

 

8  微内核结构

从单一体和层次化结构的图中可以看出,操作系统的所有功能都在内核态下运行。但是,从用户态转为内核态是有时间成本的,这样就会造成OS的效率低下。于是,人们将操作系统的核心中的核心才放在内核态运行,其他功能都迁移到用户态运行,于是就有了下面的微内核结构。

 

 

10  操作系统的核心

操作系统的四个核心功能如下图所示:

操作系统的技巧也应用于很多领域,如抽象、缓存、并发等。

操作系统简单说来就是实现抽象:进程抽象、文件抽象、虚拟存储抽象等。

而很多领域都会使用抽象,例如数据结构和程序设计(抽象数据类型?抽象类?);很多地方也会用到缓存,例如开发Web应用程序时使用缓存降低数据库访问压力,加快页面响应速度等等。

更为重要的是,对于一个程序员来说,要想知道计算机在软件层面是怎么运转的,就得学习操作系统。

10.1 CPU管理(进程原理篇)

进程是OS中的核心概念,它指的是一个运动中的程序。一个程序一旦在计算机里运行起来,它就称为一个进程。进程与进程之间可以进行通信、同步、竞争,并在一定情况下可能形成死锁。

如何分配CPU给不同应用和用户,对于进程管理坚持三个目标:

  • 一是公平(每个程序都有机会使用CPU)
  • 二是非阻塞(任何程序不能无休止地阻挠其他程序的正常推进)
  • 三是优先级(优先级高的程序开始运行则优先级低的就需要让出资源—>让一部分人先富起来)

10.1.1 进程原理:进程概要

1)进程是什么?

顾名思义,进程就是进展中的程序,或者说进程是执行中的程序。当一个程序被加载到内存之后就变为了进程。因此,我们可以得到:进程=程序+执行。

进程的出现,让每个用户感觉到自己在独占CPU。因此,可以说进程就是为了在CPU上实现多道编程而出现的概念。

2)进程模型

  • 三个视角看进程模型

  (1)物理视角:从物理内存的分配来看,每个进程占用一片内存空间,从这点上看,进程其实就是内存的某片空间。由于在任意时刻,一个CPU只能执行一条指令,因此任意时刻在CPU上执行的进程只有一个,而到底执行哪条指令是由物理程序计数器指定。因此,在物理层面,所有进程共用一个程序计数器,只是CPU在不停地做进程切换

  (2)逻辑视角:从逻辑层面来看,每个进程都可以执行,也可以暂时挂起让别的进程执行,之后又可以接着执行。所以,进程需要想办法保持状态才能在下次接着执行时从正确的地点开始。因此,每个进程都有自己的计数器,记录其下一条指令所在的位置。(从逻辑上来说,程序计数器可以有多个)

  (3)时序视角:从时间来看,每个进程都必须往前推进。在运行一定时间后,进程都应该完成了一定的工作量。换句话说,每次进程返回,它都处在上次返回点之后。哲学家有云:“一个人不能两次踏入同一条河流”。

  • 模型如何实现

  (1)物理基础:进程的物理基础是程序,程序又运行在计算机上,因此计算机上要运行程序首先要解决进程的存储:给进程分配内存,使其安身立命。由于多个进程可能同时并存,因此需要考虑如何让多个进程共享同一个物理内存而不发生冲突。OS通过内存管理来解决这个问题。

  (2)进程切换:进程运行实际上是指进程在CPU上执行,那么如何将CPU在多个进程之间进行切换也是一个问题。OS通过进程调度来解决这个问题。所谓进程调度,就是决定在什么时候让什么进程来使用CPU。

3)进程的层次与状态

  • 进程的层次结构

  一个进程在执行过程中可以通过系统调用创建新的进程,这个新的进程就称为子进程,而创建子进程的进程则被称为父进程

  子进程又可以再创建子进程,于是这样子子孙孙地创建下去就形成了所谓的进程树

  • 进程的状态转换

  基本的进程状态主要有3种:执行、阻塞和就绪,如下图所示:

  那么,进程被挂起阻塞有哪些原因呢?首先是一个进程在运行过程中执行了某种阻塞操作,例如读写磁盘。(由于阻塞操作需要等待结果后才能继续执行,因此OS将把这个进程挂起,让其他进程运转)其次是一个进程执行的时间太长了,为了公平,OS将其挂起从而让其他进程也有机会执行。

PS:当然,上述阐述的3种典型状态并不是唯一的分类方式,事实上,许多商业OS的进程都不止3个,比如Windows的进程就有7种状态。

4)进程管理概要

  • 需要的手段

  与一个社会管理人的过程类似,OS要管理进程就需要维护进程的一些信息,OS用于维护进程记录的结构就是进程表或进程控制块(Process Control Block,PCB)。那么进程表里有什么记录呢?一般来说,维护的记录应该包含:寄存器、程序计数器、状态字、栈指针、优先级、进程ID、创建时间、所耗CPU时间、当前持有的各种句柄等等。

  • 创建的过程

  一般来说,OS创建进程的步骤如下图所示:

  在不同的OS中,创建进程的方法也不同,例如Windows中是通过系统调用完成进程创建的,这个系统调用就是CreateProcess。

  • 处理的问题

  人类社会最大的问题就是资源分配,进程管理的最大问题也是如此。虽然进程没有自我意识,但我们的本性还是追求公平的。除了公平,还需要考虑:效率。于是,公平与效率就成了进程管理中永恒的主题。

  • 进程的缺陷

进程的缺点,如果想要同时做两件或多件事情,进程就不够用了。并且,如果进程在执行的过程中发生阻塞,例如等待输入,整个进程就将被挂起(暂停),而无法继续执行。这样,即使进程里面有部分工作不依赖于输入数据,也无法推进。

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

10.1.2 进程原理:进程调度

在多进程并发的环境里,虽然从概念上看,有多个进程在同时执行,但在单个CPU下,在任何时刻只能有一个进程处于执行状态,而其他进程则处于非执行状态。那么问题来了,我们是如何确定在任意时刻到底由哪个进程执行,哪些不执行呢?这就涉及到进程管理的一个重要组成部分:进程调度。

1)进度调度基础

  • 进程调度定义

  进程调度是操作系统进程管理的一个重要组成部分,其任务是选择下一个要运行的进程

  • 进程调度目标

  首先,一般的程序任务分为三种:CPU计算密集型、IO密集型与平衡(计算与IO各半)型,对于不同类型的程序,调度需要达到的目的也有所不同。对于IO密集型,响应时间最重要;对于CPU密集型,则周转时间最重要;而对于平衡型,进行某种响应和周转之间的平衡就显得比较重要。

  因此,进程调度的目标就是要达到极小化平均响应时间、极大化系统吞吐率、保持系统各个功能部件均处于繁忙状态和提供某种貌似公平的机制

2)基本调度算法

  • 先来先服务算法

FCFS

  先来先服务(FCFS)算法是一种最常见的算法,它是人的本性中的一种公平观念。其优点就是简单且实现容易,缺点则是短的工作有可能变得很慢,因为其前面有很长的工作在执行,这样就会造成用户的交互式体验也比较差。

  例如排队办理业务时,你要办理的业务只需要几分钟就可以办好,但是你前面的一个人办理的事情很复杂需要1个小时,这时你需要在他后面等很久,于是你就想到:要是每个人轮流办理10分钟事务的话,那该多好!于是就出现了时间片轮转算法。

  • 时间片轮转算法

  时间片轮转是对FCFS算法的一种改进,其主要目的是改善短程序的响应时间,实现方式就是周期性地进行进程切换。时间片轮转的重点在于时间片的选择,需要考虑多方因素:如果运行的进程多时,时间片就需要短一些;进程数量少时,时间片就可以适当长一些。因此,时间片的选择是一个综合的考虑,权衡各方利益,进行适当折中。

  但是,时间片轮转的系统响应时间也不一定总是比FCFS的响应时间短。时间片轮转是一种大锅饭的做法,但是现实生活中却是走的“一部分人先富,先富带动后富”的路线。例如,如果有30个任务,其中一个任务只需要1秒时间执行,而其他29个任务需要30秒钟执行,如果因为某种原因,这个只要1秒钟的任务排在另外29个任务的后面轮转,则它需要等待29秒钟才能执行(假定时间片为1秒)。于是,这个任务的响应时间和交互体验就变得非常差。因此,短任务优先算法被提出。

  • 短任务优先算法

  短任务优先算法的核心是所有的任务并不都一样,而是有优先级的区分。具体来说,就是短任务的优先级比长任务的高,而我们总是安排优先级高的任务先运行

  短任务优先算法又分为两种类型:一种是非抢占式,一种是抢占式。非抢占式当已经在CPU上运行的任务结束或阻塞时,从候选任务中选择执行时间最短的进程来执行。而抢占式则是每增加一个新的进程就需要对所有进程(包括正在CPU上运行的进程)进行检查,谁的时间短就运行谁

  由于短任务优先总是运行需要执行时间最短的程序,因此其系统平均响应时间在以上几种算法中是最优的,这也是短任务优先算法的优点。但短任务优先算法也有缺点:一是可能造成长任务无法得到CPU时间从而导致“肌饿”。二是如何知道每个进程还需要运转多久?于是为了解决第一个缺点,优先级调度算法被提出。而第二个缺点则可以采取一些启发式的方法来进行估算,目前很多的人工智能算法都可以做这个事。

  • 优先级调度算法

  优先级调度算法给每个进程赋予一个优先级,每次需要进程切换时,找一个优先级最高的进程进行调度。这样如果赋予长进程一个高优先级,则该进程就不会再“饥饿”。事实上,短任务优先算法本身就是一种优先级调度,只不过它给予短进程更高的优先级而已。

  该算法的优点在于可以赋予重要的进程以高优先级以确保重要任务能够得到CPU时间,其缺点则有二:一是低优先级的进程可能会“饥饿”,二是响应时间无法保证。第一个缺点可以通过动态地调节任务的优先级解决,例如一个进程如果等待时间过长,其优先级将因持续提升而超越其他进程的优先级,从而得到CPU时间。第二个缺点可以通过将一个进程优先级设置为最高来解决,但即使将优先级设置为最高,但如果每个人都将自己的进程优先级设置为最高,其响应时间还是无法保证。

  • 混合调度算法

  之前的算法都存在一定缺点,那么可否有一个算法混合他们的优点,摒弃它们的缺点,这就是所谓的混合调度算法。混合调度算法将所有进程分为不同的大类,每个大类为一个优先级。如果两个进程处于不同的大类,则处于高优先级大类的进程优先执行;如果处于同一个大类,则采用时间片轮转算法来执行。混合调度算法的示意图如下图所示:

  • 混合调度算法

  之前的算法都存在一定缺点,那么可否有一个算法混合他们的优点,摒弃它们的缺点,这就是所谓的混合调度算法。混合调度算法将所有进程分为不同的大类,每个大类为一个优先级。如果两个进程处于不同的大类,则处于高优先级大类的进程优先执行;如果处于同一个大类,则采用时间片轮转算法来执行。混合调度算法的示意图如下图所示:

  • 进程调度的过程

 

3)调度异常之优先级倒挂

  • 何为优先级倒挂

  优先级倒挂指的是一个低优先级任务持有一个被高优先级任务所需要的共享资源。这样高优先级任务因为资源缺乏而处于受阻状态,一直到低优先级任务释放资源为止。这样实际上造成了这两个任务的优先级倒挂。

  • 优先级倒挂的表现形式

  (1)不持有资源的低优先级进程阻碍需要资源的高优先级进程的执行;

  (2)持有资源的优先级进程阻碍需要资源的高优先级进程的执行;

  • 优先级倒挂的预防办法

  (1)针对第一种形式,可以使用中断禁止的方法,其核心是通过禁止中断来保护临界区。

  (2)针对第二种形式,不能让低优先级进程持有高优先级进程所需要的资源,则可以通过优先级上限和优先级继承来实现。

10.1.3 进程原理:进程通信

进程作为人类的发明,自然也免不了脱离人类的习性,也有通信的需求。如果进程之间不进行任何通信,那么进程所能完成的任务就要大打折扣。人类的通信方式无外乎对白(通过声音沟通)、打手势、写信、发电报、拥抱等方法。同理,进程也可以通过同样的方式来进行通信。

1)进程对白:管道、套接字

  • 管道

  一个进程向存储空间的一端写入信息,另一个进程存储空间的另外一端读取信息,这个就是管道。就像两个人对白的媒介是空气也可以是线缆一样,管道所占的空间既可以是内存也可以是磁盘。

  要创建一个管道,一个进程只需要调用管道创建的系统调用即可,该系统调用所做的事情就是在某种存储介质上划出一片空间,赋给其中一个进程写的权利,另一个进程读的权利即可。

  • 套接字

socket

  套接字(Socket)的功能非常强大,可以支持不同层面、不同应用、跨网络的通信。使用套接字进行通信需要双方均创建一个套接字,其中一方作为服务器方,另外一方作为客户方。服务器方必须首先创建一个服务区套接字,然后在该套接字上进行监听,等待远方的连接请求。客户方也要创建一个套接字,然后向服务器方发送连接请求。服务器套接字在受到连接请求之后,将在服务器方机器上新建一个客户套接字,与远方的客户方套接字形成点到点的通信通道。之后,客户方和服务器方便可以直接通过类似于send和recv的命令在这个创建的套接字管道上进行交流了。

例如,在C#中我们可以轻松地创建一个服务器方的Socket:

    // 创建Socket->绑定IP与端口->设置监听队列的长度->开启监听连接
    socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socketWatch.Bind(new IPEndPoint(IPAddress.Parse(txtIPAddress.Text), int.Parse(txtPort.Text)));
    socketWatch.Listen(10);
  • 不足之处

  (1)必须首先在通信的进程间建立连接(管道或套接字),这需要消耗系统资源;

  (2)通信是自愿的,而管道和套接字需要强制双方进行通信;

  (3)由于建立连接需要消耗时间,一旦建立就应该尽可能多的通信,如果通信信息量很小,则就是“杀鸡用牛刀”了;

2)进程电报与旗语:信号与信号量

  • 电报:信号

      在计算机中,信号就是一个内核对象或者是一个内核数据结构。发送方将该数据结构的内容填好,并指明该信号的目标进程后,发出特定的软件中断(这就是一个发电报的操作)。OS接收到特定的中断请求后,知道是有进程要发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程则对信号进行相应处理。

  • 旗语:信号量

  信号量来源于铁路的运行:在一条单轨铁路上,任何时候只允许有一列火车行驶在该铁路上,而管理这条铁路的系统就是信号量。任何一列火车必须等到表明该铁路可以行驶的信号后才能进入轨道。当列车进入后,需要将信号改为禁止状态进入来防止别的列车同时进入。而当列车驶出单轨后,则需要将信号变回允许进入状态,这很像以前的旗语。当然,通过联想到我们实际开发中经常用的锁,这就更容易理解了。

  在计算机中,信号量实际上就是一个简单整数。一个进程在信号变为0或1的情况下推进,并将信号变为1或0来防止别的进程同时推进。当该进程完成任务后,则将信号再改为0或1,从而允许其他进程执行。从而我们也可以看出,信号量已经不只是一种通信机制,更是一种同步机制

3)进程拥抱:共享内存

  • 共享内存

  进程的拥抱就是共享内存,两个进程共同拥有同一片内存。对于这片内存中的任何内容,二者均可以访问。要使用共享内存进行通信,进程A首先需要创建一片内存空间作为通信用,而其他进程B则将片内存映射到自己的(虚拟)地址空间。这样,进程A读写自己地址空间中对应共享内存的区域时,就是在和进程B进行通信。

  • 不足之处

  (1)使用共享内存机制通信的两个进程必须在同一台物理机上;

  (2)安全性脆弱,假如一个进程有病毒,会很容易传给另外一个进程;

4)信件发送:消息队列

  消息队列是一列具有头和尾的消息排列,新来的消息放在队列尾部,而读取消息则从队列头部开始,如下图所示:

  这样看来,它和管道十分类似,一头读,一头写?的确,看起来很像管道,但又不是管道:

  (1)消息队列无固定的读写进程,任何进程都可以读写;而管道需要指定谁读和谁写;

  (2)消息队列可以同时支持多个进程,多个进程可以读写消息队列;即所谓的多对多,而管道是点对点;

  (3)消息队列只在内存中实现,而管道还可以在磁盘上实现; 

10.1.4 线程原理:线程基础与线程同步

1)线程基础

  • 线程概念

      线程是进程的“分身”,是进程里的一个执行上下文或执行序列。of course,一个进程可以同时拥有多个执行序列。

  在线程模式下,一个进程至少有一个线程,也可以有多个线程,如下图所示:

  将进程分解为线程可以有效地利用多处理器和多核计算机。例如,当我们使用Microsoft Word时,实际上是打开了多个线程。这些线程一个负责显示,一个负责接收输入,一个定时进行存盘......这些线程一起运转,让我们感觉到输入和显示同时发生,而不用键入一些字符等待一会儿才显示到屏幕上。在不经意间,Word还能定期自动保存。

  • 线程管理

  线程管理与进程管理类似,需要一定的基础:维持线程的各种信息,这些信息包含了线程的各种关键资料。于是,就有了线程控制块。

  由于线程间共享一个进程空间,因此,许多资源是共享的(这部分资源不需要存放在线程控制块中)。但又因为线程是不同的执行序列,总会有些不能共享的资源。一般情况下,统一进程内的线程间共享和独享资源的划分如下表所示:

  • 线程模型

现代操作系统结合了用户态和内核态的线程模型,其中用户态的执行系统负责进程内部在非阻塞时的切换,而内核态的操作系统则负责阻塞线程的切换,即同时实现内核态和用户态线程管理。其中,内核态线程数量极少,而用户态线程数量较多。每个内核态线程可以服务一个或多个用户态线程。换句话说,用户态线程会被多路复用到内核态线程上。

  • 多线程的关系

  推出线程模型的目的就是实现进程级并发,因为在一个进程中通常会出现多个线程。多个线程共享一个舞台,时而交互,时而独舞。但是,共享一个舞台会带来不必要的麻烦,这些麻烦归结到下面两个根本问题:

  (1)线程之间如何通信?

  (2)线程之间如何同步?

  上述两个问题在进程层面同样存在,在前面的进程原理部分已经进行了介绍,从一个更高的层次上看,不同的进程也共享着一个巨大的空间,这个空间就是整个计算机。

2)线程同步

  • 同步的原因和目的

  (1)原因

  线程之间的关系是合作关系,既然是合作,那么久得有某种约定的规则,否则合作就会出问题。例如下图中,一个进程的两个线程因为操作不同步而造成线程1运行错误:

  出现上述问题原因在于两点:一是线程之间共享的全局变量;二是线程之间的相对执行顺序是不确定的。针对第一点,如果所有资源都不共享,那就违背了进程和线程设计的初衷:资源共享、提高资源利用率。针对第二点,需要让线程之间的相对执行顺序在需要的时候可以确定

  (2)目的

  线程同步的目的就在于不管线程之间的执行如何穿插,其运行结果都是正确的。换句话说,就是要保证多线程执行下结果的确定性。与此同时,也要保持对线程执行的限制越少越少。

  • 同步的方式

一些必要概念

  ① 两个或多个线程争相执行同一段代码或访问同一资源的现象称为竞争

  ② 可能造成竞争的共享代码段或资源就被称为临界区

  ③ 在任何时刻都能有一个线程在临界区中的现象被称为互斥。(一次只有一个人使用共享资源,其他人皆排除在外)

(1)锁

  ① 关于锁

  当两个教师都想使用同一个教室来为学生补课,如何协调呢?进到教室后将门锁上,另外一个教师就无法进来使用教室了。即教室是用锁来保证互斥的,那么在操作系统中,这种可以保证互斥的同步机制就被称为锁。

  ② 睡觉与叫醒

  当对方持有锁时,你就不需要等待锁变为打开状态,而是去睡觉,锁打开后对方再来把你叫醒,这是一种典型的生产者消费者模式。用计算机来模拟生产者消费者并不难:一个进程代表生产者,一个进程代表消费者,一片内存缓冲区代表商店。生产者将生产的物品从一端放入缓冲区,消费者则从另外一端获取物品,如下图所示:

  (2)信号量

  信号量(Semaphore)是一个计数器,其取值为当前累积的信号数量。它支持两个操作:加法操作up和减法操作down。执行down减法操作时,请求该信号量的一个线程会被挂起;而执行up加法操作时,会叫醒一个在该信号量上面等待的线程。down和up操作在历史上被称为P和V操作,是操作系统中最重要的同步原语的两个基本操作。

  (3)管程

  管程(Monitor)即监视器的意思,它监视的就是进程或线程的同步操作。具体来说,管程就是一组子程序、变量和数据结构的组合。言下之意,把需要同步的代码用一个管程的构造框起来,即将需要保护的代码置于begin monitor和end monitor之间,即可获得同步保护,也就是任何时候只能有一个线程活跃在管程里面。

  同步操作的保证是由编译器来执行的,编译器在看到begin monitor和end monitor时就知道其中的代码需要同步保护,在翻译成低级代码时就会将需要的操作系统原语加上,使得两个线程不能同时活跃在同一个管程内。

  在管程中使用两种同步机制:锁用来进行互斥,条件变量用来控制执行顺序。从某种意义上来说,管程就是锁+条件变量

About:条件变量就是线程可以在上面等待的东西,而另外一个线程则可以通过发送信号将在条件变量上的线程叫醒。因此,条件变量有点像信号量,但又不是信号量,因为不能对其进行up和down操作。

管程最大的问题就是对编译器的依赖,因为我们需要将编译器需要的同步原语加在管程的开始和结尾。此外,管程只能在单台计算机上发挥作用,如果想在多计算机环境下进行同步,那就需要其他机制了,而这种其他机制就是消息传递。

  (4)消息传递

  消息传递是通过同步双方经过互相收发消息来实现,它有两个基本操作:发送send和接收receive。他们均是操作系统的系统调用,而且既可以是阻塞调用,也可以是非阻塞调用。而同步需要的是阻塞调用,即如果一个线程执行receive操作,就必须等待受到消息后才能返回。也就是说,如果调用receive,则该线程将挂起,在收到消息后,才能转入就绪。

  消息传递最大的问题就是消息丢失和身份识别。由于网络的不可靠性,消息在网络间传输时丢失的可能性较大。而身份识别是指如何确定收到的消息就是从目标源发出的。其中,消息丢失可以通过使用TCP协议减少丢失,但也不是100%可靠。身份识别问题则可以使用诸如数字签名和加密技术来弥补。

  (5)栅栏

  栅栏顾名思义就是一个障碍,到达栅栏的线程必须停止下来,知道出去栅栏后才能往前推进。该院与主要用来对一组线程进行协调,因为有时候一组线程协同完成一个问题,所以需要所有线程都到同一个地方汇合之后一起再向前推进。

  例如,在并行计算时就会遇到这种需求,如下图所示:

10.1.5 线程原理:死锁基础原理

1)死锁初窥

  • 为何会发生死锁?
  • 死锁的定义与必要条件

 (1)死锁的定义

  如果有一组线程,每个线程都在等待一个事件的发生,而这个事件只能有该线程里面的另一线程发出,则称这组线程发生了死锁。这里的事件主要是资源的释放,在死锁状态中,没有线程可以执行、释放资源或被叫醒。

  例如,有线程A和线程B如下:

  如果线程A和线程B交替执行,那么线程A和线程B均会因为无法获得对应的资源而无法继续执行也不能释放锁,从而造成死锁,如下图所示:

  (2)死锁的4个必要条件

  ① 资源有限:即一个系统里面的资源数量是有限的,以致于无法同时满足所有线程的资源需求。

  ② 持有等待:即一个线程在请求新的资源时,其已经获得的资源并不释放,而是继续持有。

  ③ 不可抢占:即如果可以抢占一个资源,则也不会发生死锁。(凡是可以抢占的资源,均不会称为死锁的原因)

  ④ 循环等待:即如果你等我、我等你,大家都这样等着对方,就产生了死锁。

2)应对死锁

  • 引子:哲学家就餐问题
  • 死锁的应对方法

操作系统应对死锁的策略可以分为两大种、四小种。

两大种是:允许死锁发生和不让死锁发生。

四小种是:允许死锁发生有两个子对策,一是假装看不见不予理睬,二是死锁发生后想办法解决;不让死锁发生也有两个子对策,一是通过平时的仔细检点避免难题出现,二是通过将发生死锁的必要条件消除杜绝死锁的发生。

  (1)顺其自然不予理睬

  此种策略就是操作系统不做任何措施,任由死锁发生。老子曾说,无为而治,说的就是这个策略。但是,如果牵涉到高可靠性系统、实时控制系统,那就另当别论了,那绝对不允许死锁。

  (2)死锁的检测与恢复

  在死锁检测上,一般会利用到两个矩阵:一个是资源分配矩阵,另一个是资源等待矩阵,如下图所示:

资源分配矩阵

资源等待矩阵

  此外,还维持两个矢量:一个是系统资源总量矢量(表示系统中所有资源的总数是多少),另一个是系统当前可用资源矢量(代表系统现在还有多少可用的资源),如下图所示:

  

  有了上面的矩阵和矢量,我们就可以通过简单地矩阵运算来判断系统是否发生了死锁。例如,将上图中的资源可用数量矩阵与资源等待矩阵的每一行相减,都会出现负值,那么该系统将要发生死锁。

  在死锁恢复上,首先可以抢占(即将某个线程所占有的资源强行拿走,分配给别的线程),其次可以将整个线程Kill杀掉(因为抢占一个线程的资源有可能造成该线程无法再正确运行了),最后则是Rollback回滚(即将整个系统回滚到过去的某个状态,大家从那个状态重新来过)

  (3)死锁的动态避免

  死锁的检测与恢复属于后发制人,这时死锁的消极后果已经产生,即使修复也已经浪费了时间,降低了效率,甚至造成了其他损失。因此,需要更加积极主动一点,不要等到死锁发生了再亡羊补牢,而是在运行中就小心翼翼,不让思索发生。

  动态避免的原则在于:在每次进行资源分配时,必须经过仔细计算,确保该资源请求批准后系统不会进入死锁或潜在的死锁状态。例如,有一种资源的数量为10个,当前有3个线程正在运行。每个线程需要资源的最大数和当前已经占用的资源数如下表所示:

  可以通过以下分配过程得知,存在一个资源分配顺序使得所有线程都能获得其需要的资源,从而得知当前状态是安全状态,不会产生死锁。相反,如果不存在这样一个顺序,那么就有可能产生死锁。

  动态避免的优点就是无需等待死锁的发生,而是在死锁有可能发生的时候采取先发制人的措施,断然拒绝有可能进入死锁的资源请求。但是,计算一个状态是否安全并不是一件容易的事情。

  (4)死锁的静态防止

  该策略的中心思想是:清除死锁发生的土壤(即死锁的4个必要条件),只要清除4个必要条件中的一个,那么死锁将无法发生。

  ① 清除资源独占条件:一是增加资源到所有线程满足的资源需要,但这并不实际,因为资源是有限的;二是将资源变为共享,但并不适合与所有的资源(例如键盘输入就无法共享)。

  ② 清除保持和请求条件:一个线程必须一次请求其所需的所有资源,而不是一般情况下的请求一点资源做一点事情。由于一个线程一次就获得了其所需的所有资源,该线程就可以顺利执行,不会发生死锁。

  ③ 清除非抢占条件:允许抢占资源,也就是说可以从一个线程手上将资源抢夺过来。

  ④ 清除循环等待条件:出现循环等待是因为线程请求资源的顺序是随机的,所以只要约定线程对资源的使用顺序,那么死锁就不能发生。

  • 银行家算法

  顾名思义,银行家算法就是仿照银行发放贷款时采用的控制方式而设计的一种死锁避免算法,该算法的策略是实现动态避免死锁

  银行家算法的基本思想是:分配资源之前,判断系统是否是安全的;若是,才分配。每分配一次资源就测试一次是否安全,不是资源全部就位后才测试。我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。概括起来基本思想就是:

   ① 分配检测:Request < Need

                        Request < Available

      ② 安全序列检测算法

总的来说,银行家算法是一个动态避免死锁算法,通过对资源的仔细分配以避免死锁。其特点是可以超额批准客户的信用额度,即所有客户的信用额度之和可以超过银行的全部资本,这就是杠杆(Leverage)

  • 解决:哲学家就餐问题

10.2 内存管理(内存原理篇)

操作系统的两个角色分别是魔术师和管理者,在管理者这个角色中,除了CPU之外,内存是操作系统要管理的另外一个重要资源。内存管理需要达到两个目标:一是地址保护,即一个程序不能访问另一个程序的地址空间。二是地址独立,即程序发出的地址应该与物理主存地址无关。这两个目标就是衡量一个内存管理系统是否完善的标准,它是所有内存管理系统必须提供的基本抽象。

内存是另一个核心概念,它是进程的存放场所。OS要做的就是对内存进行管理,使得数据读写高效、安全、简单。

如何分配内存给不同应用和用户,主要管理缓存、主存、磁盘、磁带等存储介质所形成的内存架构。为此母的,操作系统设计人员发明了虚拟内存的概念,即将物理内存(缓存和主存)扩充到外部存储介质(磁盘、光盘和磁带)上。这样内存的空间就达达的增加了,能够运行的程序的大小也大大的增加了。

其目的主要有二:

  • 一是将少变多(比如虚拟内存的使用能够使得运行程序的大小大大地增加)
  • 二是让多个程序共享同一个物理内存(这就需要对物理内存进行分割和保护,不让一个程序访问另一个程序所占的内存空间,专业术语称为运行时不能越界访问)

10.2.1 基本内存管理

1)内存管理二三事

  • 内存管理的目标

  (1)地址保护:一个程序不能访问另一个程序地址空间。

  (2)地址独立:程序发出的地址应该与物理主存地址无关。

  这两个目标是衡量一个内存管理系统是否完善的标准,它是所有内存管理系统必须提供的基本抽象。 

  • 虚拟内存的概念

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

虚拟内存也有同样的缺点:硬盘的容量比内存大,但也只是相对的,速度却非常缓慢,如果和硬盘之间的数据交换过于频繁,处理速度就会下降,表面上看起来就像卡住了一样,这种现象称为抖动(Thrushing)。相信很多人都有过计算机停止响应的经历,而造成死机的主要原因之一就是抖动。

2)基本内存管理

  • 单道编程的内存管理

  在单道编程环境下,整个内存里面只有两个程序:一个是用户程序,另一个是操作系统。

  由于只有一个用户程序,而操作系统所占用的内存空间是恒定的,所以我们可以将用户程序总是加载到同一个内存地址上,即用户程序永远从同一个地方开始执行

  这样,用户程序里面的地址都可以事先计算出来,即在程序运行之前就计算出所有的物理地址。这种在运行前即将物理地址计算好的方式叫做静态地址翻译。下面看看此方式如何达到两个目标。

  (1)地址独立:用户在编写程序时无需考虑具体的物理内存,用户程序始终都被加载到同一个物理地址上。

  (2)地址保护:整个系统里面只有一个用户程序,因此,固定地址的内存管理因为只运行一个用户程序而达到地址保护。

  • 多道编程的内存管理

  在多道编程环境下,无法将程序总是加到固定的内存地址上,也就是无法使用静态地址翻译。因此,必须在程序加载完毕之后才能计算物理地址,也就是在程序运行时进行地址翻译,这种翻译称为动态地址翻译

  多道编程环境下的内管管理策略有两种:

  (1)固定分区

  顾名思义,固定分区管理就是讲内存分为固定的几个区域,每个区域大小固定。最下面的分区为OS占用,其他分区由用户程序使用。分区大小通常各不相同,当需要加载程序时,选择一个当前闲置且容量足够大的分区进行加载,如下图所示,这是一种共享队列的固定分区(多个用户程序排在一个共同的队列里面等待分区):

  由于程序大小和分区大小不一定匹配,有可能形成一个小程序占用一个大分区的情况,从而造成内存里虽然有小分区闲置但无法加载大程序的情况。这时,我们就想到也许可以采用多个队列,给每个分区一个队列,程序按照大小排在相应的队列里,如下图所示,这时一种分开队列的固定分区:

  上图这种方式也有缺点:如果还有空闲分区,但等待的程序不在该分区的等待队列上,就将造成有空间而不能运行程序的尴尬。

  (2)非固定分区

  非固定分区的思想在于除了划分给OS的空间之外,其余的内存空间是作为一个整体存在的。当一个程序需要占用内存空间时,就在该片空间里面分出一个大小刚刚满足程序所需的空间。再来一个程序时,则在剩下的空间里再这样分出一块来。在这种模式下,一个程序可以加载到任何地方,也可以和物理内存一样大。

  例如,一开始内存中只有OS,这时候进程A来了,于是分出一片与进程A大小一样的内存空间;随后,进程B来了,于是在进程A之上分出一片给进程B;然后进程C来了,就在进程B上面再分出一片给C。如此,进程A、B和C的起始地址都不是固定的,如下图所示:

  仔细一看,这种方式存在一个重大问题:每个程序像叠罗汉一样累计,如果程序B在成型过程中需要更多空间怎么办?(例如在实际程序中,很多递归嵌套函数调用的时候回造成栈空间的增长)因此,我们可以想到可以再一开始的时候给程序分配空间时就分配足够大的空间,留有一片闲置空间供程序增长使用,如下图所示:

  不过,OS怎么知道应该分配多少空间给一个程序呢?分配多了,就是浪费;而分配少了,则可能造成程序无法继续执行。

  因此,可以在空间不够时,给程序换一个空间,这种方式将程序倒到磁盘上,再加载到内存中,被称为交换swap)。但是,如果在交换模式下程序的增长超过了物理内存,就不能再交换了。此时,可以将程序按照功能分成一段一段功能相对完整的单元,一个单元执行完成后再执行下一个单元,这就是重叠overlay)。

  但是,交换内存管理这种方式存在两个重要问题:

  (1)空间浪费:随着程序在内存与磁盘间的交换,内存将变得越来越碎片化,即内存将被不同程序分割成尺寸大小无法使用的小片空间。

  (2)程序大小受限:这有两层意思,一是指空间增长效率低下(由于磁盘操作耗时,交换出去再找一片更大的空间来增长程序空间的做法效率非常低),二是空间增长存在天花板限制(单一程序不能超过物理内存空间)。

  • 闲置空间管理

  在管理内存的时候,OS需要知道内存空间有多少空闲?这就必须跟踪内存的使用,跟踪的办法有两种:

  (1)给每个分配单元赋予一个字位,用来记录该分配单元是否闲置。例如,字位取值为0表示单元闲置,取值为1则表示已被占用,这种表示方法就是位图表示法,如下图所示:

  (2)将分配单元按照是否闲置链接起来,这种方法称为链表表示法。如上图所示的的位图所表示的内存分配状态,使用链表来表示的话则会如下图所示:

  在链表表示下,寻找一个给定大小的闲置空间意味着找到一个类型为H的链表项,其大小大于或等于给定的目标值。不过,扫描链表速度通常较慢。

10.2.2 分页内存管理

在上一篇介绍的几种多道编程的内存管理模式中,以交换内存管理最为灵活和先进。但是这种策略也存在很多重大问题,而其中最重要的两个问题就是空间浪费和程序大小受限。那么有什么办法可以解决交换内存存在的这些问题呢?答案是分页,它是我们解决交换缺陷的“不二法门”。

1)分页内存管理

  • 解决问题之道
  • 虚拟地址的构成与地址翻译
  • 页表
  • 分页系统的优缺点

  优点:

  (1)分页系统不会产生外部碎片,一个进程占用的内存空间可以不是连续的,并且一个进程的虚拟页面在不需要的时候可以放在磁盘中。

  (2)分页系统可以共享小的地址,即页面共享。只需要在对应给定页面的页表项里做一个相关的记录即可。

  缺点:页表很大,占用了大量的内存空间。

  • 缺页中断处理

  在分页系统中,一个虚拟页面既有可能在物理内存,也有可能保存在磁盘上。如果CPU发出的虚拟地址对应的页面不在物理内存,就将产生一个缺页中断,而缺页中断服务程序负责将需要的虚拟页面找到并加载到内存。缺页中断的处理步骤如下,省略了中间很多的步骤,只保留最核心的几个步骤:

2)页面置换算法

如果发生了缺页中断,就需要从磁盘上将需要的页面调入内存。如果内存没有多余的空间,就需要在现有的页面中选择一个页面进行替换。使用不同的页面置换算法,页面更换的顺序也会各不相同。如果挑选的页面是之后很快又要被访问的页面,那么系统将很开再次产生缺页中断,因为磁盘访问速度远远低于内存访问速度,缺页中断的代价是非常大的。因此,挑选哪个页面进行置换不是随随便便的事情,而是有要求的。

  • 页面置换的目标

  页面置换时挑选页面的目标主要在于降低随后发生缺页中断的次数或概率

  因此,挑选的页面应当是随后相当长时间内不会被访问的页面,最好是再也不会被访问的页面。BTW,如果可能,最好选择一个没有修改过的页面,这样替换时就无须将被替换页面的内容写回磁盘,从而进一步加快缺页中断的响应速度。

  所以,为了达到这个目的,先驱们设计出了各种各样的页面置换算法,下面就来看看这些算法。

  • 随机更换算法
  • 先进先出算法
  • 第二次机会算法
  • 时钟算法
  • 最优更换算法
  • NRU(最近未被使用)算法
  • LRU(最近最少使用)算法
  • 工作集算法
  • 工作集时钟算法

2.10.2.3 段式内存管理

1)分页系统的缺点

2)分段管理系统

3)段页式内存管理

4)内存管理的演变 

10.3 外存管理(文件原理篇)

如何分配外存(磁盘)给不同应用和用户,外存管理也称存储管理,也就是我们所说的文件系统,其目的是将磁盘变为一个很容易使用的存储介质以提供给用户使用。

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

10.3.1 磁盘基础

  计算机是处理数据的机器,而数据就需要有地方存放。在计算机中,可供数据存放的地方并不太多,除了内存之外,最主要的存储数据的媒介就是磁盘。对于大多数计算机领域的人来说,磁盘通常被看做是一种外部设备。可是,对于现代操作系统来说,磁盘是不可或缺的。虽然早期的操作系统可以基于磁带,但由于操作系统复杂性和性能的不断提升,用磁带作为操作系统的载体已经不合时宜,取而代之的是磁盘。由于操作系统需要存放在磁盘上,且操作系统内的文件系统也是基于磁盘,所以,从某种程度来说,磁盘是操作系统不可分割的一部分,理解磁盘将对理解操作系统的原理具有重要的意义。

1)磁盘的结构

  • 什么是磁盘?
  • 磁盘的结构
  • 盘面的结构

2)磁盘驱动器的访问速度

  • 寻道时间
  • 磁道到磁道的访问时间
  • 旋转延迟时间

平均访问时间 = 寻道时间 + 旋转延迟时间

3)磁盘调度算法

  • 磁盘读写时间的影响因素
  • 先来先服务算法
  • 短任务优先算法
  • 短寻道优先算法
  • 电梯调度算法
  • 提前查看电梯调度算法

4)关于固态盘

回想一下我们之前提到的内存,不就是没有机械运动的存储介质嘛?于是,使用与内存介质相同或相仿的存储介质构建的磁盘就是所谓的固态盘(Solid State Disk,SSD)横空出世了。

  固态的原始意义是半导体驱动器,但在今天的存储工业界已经演化为表示没有机械运动部件的驱动器由于没有移动的部件,整个驱动器似乎是固定的,因而也就被称为固态盘

  由于木有移动的机械部件,固态盘具有一些机械盘所不具备的优点:

  (1)可靠性高,没有噪音(没有风扇);

  (2)访问速度快,接近内存的访问速度;

  (3)热耗低于机械盘,且更省电;

  (4)不需要旋转,其启动时间更短;

  但是,固态盘最大的问题是成本,其单位容量成本大约为机械盘的10~20倍,这对于大容量的固态盘来说,这个成本十分可观。对于小容量的固态盘来说,我们已经能够承受。

10.3.2 文件系统

1)为何需要文件系统

  磁盘具有大容量、低成本以及持久化的特点,即使发生断电,磁盘上的数据也不会丢失。但是,对于一般用户而言,使用磁盘是非常苦难的,因为他们不知道如何驱动一个磁盘,以及计算数据在磁盘上的存放位置。从上一篇《磁盘基础》可以知道,了解磁盘的各项技术细节将使用户不堪重负。

  操作系统是一个魔术师,其提供给用户的就是各种幻想:抽象。进程抽象的是CPU,虚拟内存抽象的是内存,对于磁盘来说,操作系统提供给用户的帮助就是在磁盘外面包裹一层容易使用的抽象,用户直接与这层抽象打交道,而无需了解磁盘的技术细节。在操作系统中,这层为磁盘提供的抽象就是:文件系统

2)文件系统的基本概念

  • 文件系统是什么

  文件系统是操作系统为磁盘和用户之间提供的一个抽象,它是一个子虚乌有的,看不见摸不着的接口,如下图所示:

  (1)文件系统使得用户能够很方便的使用磁盘:将用户从数据存放的细节中解放出来,用户不需要知道内容存放在什么地方,也不需要知道如何存放,更不需要知道磁盘到底是如何工作的。

  (2)简单地说,文件系统将其接触的磁盘物理特性转换为用户看到的路径名和文件名。用户对磁盘进行访问只需要给出文件名和路径名即可,而无需知道磁柱、磁道、扇面、数据块等信息

  (3)文件系统的主要特性就是存储大量的信息,多个进程可以同时访问一个文件,进程结束也不会影响文件的持续存在

  • 文件系统的目标

  (1)地址独立

  一个文件在产生的时候无需担心其存放的磁盘地址,即文件数据的产生与文件将来存放的磁盘地址相互独立。

  (2)地址保护

  地址保护需要对文件的访问进行一定的限制,即不是任何人都可以访问任何文件。

Difference:文件系统的地址保护与内存管理下的地址保护不同,内存管理下地址保护指的是一个进程不能访问另一个进程空间,而文件系统下的地址保护不是一个文件不能访问另一个文件空间,而是一个文件的访问时有限制的。

  • 文件的基本知识

3)文件夹实现地址独立

4)文件系统的调用

10.4 设备管理(I/O原理篇)

如何分配输入输出设备给应用和用户,也称为设备管理,也就是管理输入输出设备。

其目的主要有两个:

  • 一是屏蔽不同设备的差异性(用户用同样的方式访问不同的设备,从而减低编程的难度)
  • 二是提供并发访问(即将那些看上去并不具备共享特征的设备如打印机变得可以共享)

1)I/O的基本知识

  • 为何要有I/O
  • I/O管理要达到的目的

2)I/O的硬件和软件

I/O的硬件

I/O的软件

3)I/O软件的分层

  为了完成一个繁琐的工作,人们通常将其分为更小的任务来处理。在I/O软件上自然也不会例外。I/O软件通常按照I/O功能进行分层,每一层有提供独特的功能,并与相邻的层面设计有标准界面。一般来说,I/O软件都有以下几层:

  (1)用户层I/O软件

  (2)设备独立的操作系统软件

  (3)设备驱动程序

  (4)中断服务程序

  • 中断服务程序
  • 设备驱动程序
  • 设备独立的操作系统软件
  • 用户层I/O软件

10.5 多核管理(多核原理篇)

99. 直接读这些牛人的原文

《计算机的心智(操作系统之哲学原理)》

操作系统简介

如何学习操作系统原理?

[操作系统-心得]漫谈如何学习操作系统原理

Edison Zhou:操作系统原理

30天自制操作系统(23)

20 张图揭开「内存管理」的迷雾,瞬间豁然开朗

民工哥技术之路:全网最新、最全Linux面试题(2020版)!

 

posted @ 2021-05-26 22:47  沙漏哟  阅读(166)  评论(0编辑  收藏  举报