admin 管理员组

文章数量: 887170


鉴于有人需要离线版的PDF文档,这里给出本文章的PDF版本,下载地址如下:https://pan.itnxd/123Pan/csdn-share/operating-system.pdf


一、操作系统概述

1、操作系统定义

操作系统(Operating System,OS):是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境,它是计算机系统中最基本的系统软件。

  1. 是系统最基本最核心的软件,属于系统软件
  2. 控制和管理整个计算机的硬件和软件资源
  3. 合理的组织、调度计算机的工作与资源的分配
  4. 为用户和其它软件提供方便的接口和环境

2、操作系统功能和目标

作为系统资源的管理者

  • 处理机调度(管理):在多道程序环境下,cpu的分配和运行都以进程(或线程)为基本单位,因此对cpu的管理可理解为对进程的管理。进程管理的主要功能包括进程控制、进程同步、进程通信、死锁处理、处理机调度
  • 存储器管理:为多道程序的运行提供良好的环境,方便用户使用及提高内存的利用率,主要包括内存分配与回收、地址映射、内存保护与共享和内存扩充等功能。
  • 文件管理:计算机中所有的信息都是以文件的形式存在的,操作系统中负责文件的管理的部分称为文件系统,文件管理包括文件存储空间的管理、目录管理及文件读写管理和保护等。
  • 设备管理:主要任务是完成用户的I/O请求,方便用户使用各种设备,并提高设备的利用率,主要包括缓存管理、设备分配、设备处理和虚拟设备等功能。

作为用户与计算机硬件系统之间的接口

命令接口: 允许用户直接使用

  • 联机命令接口:用户说一句,系统做一句 = 交互式命令接口
  • 脱机命令接口:用户说一堆,系统做一堆 = 批处理命令接口

程序接口: 允许用户通过程序间接使用

由一组系统调用组成(程序接口=系统调用)

系统调用 = 系统调用命令 = 广义指令

GUI: 现代操作系统中最流行的图形用户接口

作为最接近硬件的层次

需要提供的功能和目标:实现对硬件机器的拓展

没有任何软件支持的计算机称为裸机。在裸机上安装的操作系统,可以提供资源管理功能和方便用户的服务功能,将裸机改造成功能更强、使用更方便的机器。

通常把覆盖了软件的机器称为扩充机器,又称之为虚拟机

3、操作系统的特征

并发

并发:指两个或多个事件在同一时间间隔内发生。这些事件宏观上是同时发生的,但微观上是交替发生的。

并行:指两个或多个事件在同一时刻同时发生。

操作系统的并发性指计算机系统中同时存在着多个运行着的程序。

一个单核处理机(CPU)同一时刻只能执行一个程序,因此操作系统会负责协调多个程序交替执行(这些程序微观上是交替执行的,但宏观上看起来就像是在同时执行)

事实上,操作系统就是伴随着“多道程序技术”而出现的。因此,操作系统和程序并发是一起诞生的。

共享

共享:即资源共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。

所谓的“同时”往往是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问的(即分时共享)

  • 互斥共享方式
  • 同时共享方式

并发和共享的关系(互为存在条件)

通过上述例子来看并发与共享的关系:

使用QQ发送文件A,同时使用微信发送文件B。

1、两个进程正在并发执行(并发性)

如果失去并发性,则系统中只有一个程序正在运行,则共享性失去存在的意义

2、需要共享地访问硬盘资源(共享性)

如果失去共享性,则QQ和微信不能同时访问硬盘资源,就无法实现同时发送文件,也就无法并发。

虚拟

虚拟:指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物(后者)是用户感受到的。

虚拟技术:用于实现虚拟的技术

虚拟处理器(CPU):通过多道程序设计技术,采用让多道程序并发执行的方法,分时来使用一个CPU,实际物理上只有一个CPU,但是用户感觉到有多个CPU

虚拟存储器:从逻辑上扩充存储器容量,用户感觉到的但实际不存在的存储器

虚拟设备:将一台物理设备虚拟为逻辑上的多台设备,使多个用户在同一时间段内访问同一台设备,即同时共享,用户宏观上感觉是同时的,但实际上是微

观交替访问同一台设备的

虚拟技术

  • 时分复用技术:如处理器的分时共享
  • 空分复用技术:如虚拟存储器

没有并发,就探不上虚拟性

异步

异步: 指在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。

显然,如果失去了并发性,则系统只能串行地处理各个进程,每个进程地执行会一贯到底。只有系统拥有并发性,才有可能导致异步性。

没有并发和共享,就谈不上虚拟和异步,因此并发和共享是操作系统的两个最基本的特征。

4、操作系统的发展和分类

手工操作阶段

过程: 用户把程序写在纸带上(其实就是在纸带上打孔),然后输入到计算机中,计算机随后会处理这个程序,把输出结果又放在纸带中(其实还是打孔),展示给用户看。

由于用户在纸带上编写程序的速度很慢,纸带输入输出的速度也很慢,而计算机的处理速度快,所以系统资源的利用率极低。

主要缺点:用户独占全机、人机速度矛盾导致资源利用率极低

批处理阶段 — 单道批处理系统

引入脱机输入/输出技术(用磁带完成),并使用监督程序(操作系统的雏形)负责控制作业的输入、输出。

由于磁带录入到处理器中的速度比纸带快得多,所以单道批处理系统一定程序上缓和了人机速度矛盾,资源利用率有所提升。

主要优点: 缓解了一定程度的人机速度矛盾,资源利用率有所提升。

主要缺点: 内存中仅能有一道程序运行,只有该程序运行结束之后才能调入下一道程序。CPU有大量的时间是在空闲等待I/O完成。资源利用率依然很低。

批处理阶段 — 多道批处理系统

每次往内存中输入多道程序,操作系统正式诞生,并引入了中断技术,由操作系统负责管理这些程序的运行。各个程序并发执行

主要优点: 多道程序并发执行,共享计算机资源。资源利用率大幅提升,CPU和其他资源保持“忙碌”状态,系统吞吐量增大。

主要缺点: 用户响应时间长,没有人机交互功能(用户提交自己的作业之后就只能等待计算机处理完成,中间不能控制自己的作业执行)

分时操作系统

计算机以时间片为单位轮流为各个用户/作业服务,各个用户可通过终端与计算机进行交互

主要优点: 用户请求可以被即时响应,解决了人机交互问题。允许多个用户同时使用一台计算机,并且用户对计算机的操作相互独立,感受不到别人的存在。

主要缺点不能优先处理一些紧急任务。操作系统对各个用户/作业都是完全公平的,循环地为每个用户/作业服务一个时间片,不区分任务的紧急性。

实时操作系统

在实时操作系统的控制下,计算机系统接收到外部信号后及时进行处理,并且要在严格的时限内处理完事件。实时操作系统的主要特点是及时性和可靠性

主要优点: 能够优先响应一些紧急任务,某些紧急任务不需时间片排队。

  • 硬实时系统:必须在绝对严格的规定时间内完成处理(导弹系统)
  • 软实时系统:能接受偶尔违反时间规定(12306火车票)

其他操作系统

网络操作系统

工作方式:把计算机网络中的各台计算机有机的结合起来,提供统一、经济而有效的使用各台计算机的方法、实现各台计算机之间的数据互相传送

特点:有主从关系、网络中资源共亨、网络中的计算机通过协议通信

分布式操作系统

工作方式︰系统中的各计算机相互协同并行完成同一任务

特征

  • 系统中任意两台计算机通过通信方式交换信息

  • 系统中的计算机都具有同等地位,无主从关系

  • 每台计算机上的资源为所有用户共享

  • 系统中的任意台计算机都可以构成一个子系统,并且还能重构

  • 任何工作都可以分布在几台计算机上、由他们并行工作、协同完成

特点:分布性和并行性

嵌入式操作系统

固话在硬件里面的系统、比如手机、路由器等

特点

  • 完成某一项特定的功能、不具有通用性
  • 广泛用于文字处理、电子表格、游戏中等

个人计算机操作系统

常见的有Windows、Linux、Macintosh等

5、操作系统运行机制

两种指令

指令:就是处理器(CPU)能识别、执行的最基本命令

  • 特权指令: 如内存清零指令 --> 不允许用户程序使用。
  • 非特权指令: 如普通的运算指令

CPU如何判断当前是否可以执行特权指令?

程序状态寄存器(PSW)中的某标志位来标识当前处理器处于什么状态,如0为用户态,1为核心态。

  • 用户态(目态): 此时CPU只能执行非特权指令
  • 核心态(管态):特权指令、非特权指令都可以执行

两种程序

  • 内核程序: 操作系统的内核程序是系统的管理者,既可以执行特权指令,也可以执行非特权指令,运行在核心态
  • 应用程序: 为了保证系统能安全运行,普通应用程序只能执行非特权指令,运行在用户态

6、操作系统内核

内核:计算机上配置的底层软件,是操作系统最基本、最核心的部分。

实现操作系统内核功能的那些程序就是内核程序

时钟管理: 实现计时功能

中断处理: 负责实现中断机制

原语

  • 是一种特殊的程序
  • 处于操作系统最底层,是最接近硬件的部分
  • 这种程序的运行具有原子性 —— 其运行只能一气呵成,不可中断
  • 运行时间较短,调用频繁

对系统资源进行管理的功能 (有的操作系统不把这部分功能归为 “ 内核功能 ”。也就是说不同的操作系统,对内核功能的划分可能并不一样)

  • 进程管理
  • 存储器管理
  • 设备管理

7、操作系统体系结构

  • 大内核
    • 将操作系统的主要功能模块都作为系统内核,运行在核心态
    • 优点:高性能
    • 缺点:内核代码庞大,结构混乱,难以维护
  • 微内核
    • 只把最基本的功能保留在内核
    • 优点:内核功能少,结构清晰,方便维护
    • 缺点:需要频繁地在核心态和用户态之间切换,性能低

8、中断和异常

中断机制诞生

早期的计算机:各程序只能串行执行,系统资源利用率低

为了解决上述问题,人们发明了操作系统(作为计算机的管理者),引入中断机制,实现了多道程序并发执行。

本质:发生中断就意味着需要操作系统介入,开展工作。

中断概念和作用

1、当中断发生时,CPU立即进入核心态

2、当中断发生后,当前运行的进程暂停运行,并由操作系统内核对中断进行处理

3、对于不同的中断信号,会进行不同的处理

由于操作系统的管理工作(比如进程切换、分配I/O设备等)需要使用特权指令,因此CPU要从用户态转为核心态。中断可以使CPU从用户态切换为核心态,使操作系统获得计算机的控制权。有了中断,才能实现多道程序并发执行。

用户态、核心态之间的切换是怎么实现的?

  • “ 用户态 --> 核心态 ” 是通过中断实现,并且中断是唯一途径。
  • “ 核心态 --> 用户态 ” 的切换是通过执行一个特权指令,将程序状态字(PSW)的标志位设置为用户态。

用户态和核心态就是程序状态寄存器0或1标志的,因此核心态到用户态可以通过特权指令进行设置!

中断分类

  • 内中断
  • 外中断

分类二

外中断

外中断处理过程

  1. 执行完每个指令之后,CPU都要检查当前是否有外部中断信号
  2. 如果检测到外部中断信号,则需要保护被中断进程的CPU环境(如程序状态字PSW、程序计数器PC、各种通用寄存器)
  3. 根据中断信号类型转入相应的中断处理程序
  4. 恢复原进程的CPU环境并退出中断,返回原进程继续往下执行

更详细过程

9、系统调用

什么是系统调用

系统调用: 是操作系统提供给应用程序(程序员/编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以发出系统调用请求来获得操作系统的服务

功能: 应用程序通过系统调用全球操作系统的服务。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序中,凡是与资源有关的操作(如存储分配、I/O操作、文件管理等),都必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。

系统调用相关处理涉及到对系统资源的管理、对进程的控制,这些功能需要执行一些特权指令才能完成,因此系统调用的线管处理需要在核心态下进行。

系统调用按功能分类

  • 设备管理:完成设备的请求/释放/启动等功能
  • 文件管理:完成文件的读/写/创建/删除等功能
  • 进程控制:完成进程的创建/撤销/阻塞/唤醒等功能
  • 进程通信:完成进程之间的消息传递/信号传递等功能
  • 内存管理:完成内存的分配/回收等功能

系统调用与库函数的区别

系统调用背后的过程

  1. 陷入指令是在用户态执行的,执行陷入指令之后立即引发一个内中断,从而CPU进入核心态
  2. 发出系统调用请求是在用户态,而对系统调用的相应处理在核心态下进行。
  3. 陷入指令是唯一一个只能在用户态执行的内中断指令(特权指令),而不可在核心态执行的指令。

二、进程管理

0、大纲

  • 进程与线程
  • 处理机调度及调度算法
  • 进程同步与互斥
  • 死锁

1、进程概述

进程定义

程序:就是一个指令序列

  • 早期的计算机(只支持单道程序)
  • 引入多道程序技术之后:为了方便操作系统管理,完成各程序并发执行,引入了进程、进程实体的概念。

PCB: 系统为每个运行的程序配置一个数据结构,称为进程控制块(PCB),用来描述进程的各种信息(如代码存放位置)

PCB、程序段、数据段三部分构成了进程实体(进程映像)。一般情况下,我们把进程实体简称为进程,例如,所谓创建进程,实质上是创建进程实体中的PCB;而撤销进程,实质上是撤销进程实体中的PCB。PCB是进程存在的唯一标志

定义: 强调动态性

  1. 进程是程序的一次执行过程
  2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
  3. 进程是具有独立功能的程序在数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。

严格来说,进程实体和进程不一样,进程实体是静态的,进程则是动态的

进程组成

进程(进程实体)由程序段、数据段、PCB三部分组成。

  • 数据段:程序运行时使用、产生的运算数据。如全局变量
  • 程序段:程序代码即存放在此
  • PCB:操作系统通过PCB来管理进程,因此PCB中应该包含操作系统对其进行管理所需的各种信息。

进程组织

进程的组成讨论的是一个进程内部由哪些部分构成的问题,而进程的组织讨论的是多个进程之间的组织方式问题。

进程的组织方式:

  • 链接方式
    • 按照进程状态将PCB分为多个队列
    • 操作系统持有指向各个队列的指针
  • 索引方式
    • 根据进程状态的不同,建立几张索引表
    • 操作系统持有指向各个索引表的指针

进程特征

  • 动态性 (进程最基本的特征):进程是程序的一次执行过程,是动态地产生、变化和消亡地
  • 并发性:内存中有多个进程实体,各进程可并发执行
  • 独立性 (进程是资源分配、接受调度的基本单位):进程是能独立运行、独立获得资源、独立接受调度的基本单位
  • 异步性 (会倒置并发程序执行结果的不确定性):各进程按各自独立的、不可预知的速度向前推进,操作系统要提供“进程同步机制”来解决异步问题
  • 结构性:每个进程都会配置一个PCB。结构上看,进程由程序段、数据段、PCB组成

2、进程状态与转换

进程状态

进程是程序的一次执行过程。在这个执行过从中,有时进程正在被CPU处理,有时又需要等待CPU服务,可见 ,进程的状态是会有各种变化。为了方便对各个进程的管理,操作系统需要将进程合理地划分为几种状态。

三种基本状态

运行态(Running):

  • 占有CPU,并在CPU上运行
  • 注意:单核处理机环境下,每一个时刻最多只有一个进程处于运行态。双核环境下可以同时有两个进程处于运行态

就绪态(Ready):

  • 已经具备运行条件,但由于没有空闲CPU,而暂时不能运行。
  • 进程已经拥有了除处理机之外所有需要的资源,一旦获得处理机,即可立即进入运行态开始运行。

阻塞态(Waiting/Blocked,又称:等待态):

  • 因等待某一事件而暂时不能运行
  • 如:等待操作系统分配打印机、等待读磁盘操作的结果。CPU是计算机中最昂贵的部件,为了提高CPU的利用率,需要先将其他进程需要的资源分配到位,才能得到CPU的服务

另外两种状态:

创建态(New,又称:新建态)

  • 进程正在被创建,操作系统为进程分配资源、初始化PCB

终止态(Terminated,又称:结束态)

  • 进程正在从系统中撤销,操作系统会回收进程拥有的资源、撤销PCB

进程转换

  • 处理机是否占用
  • 其他是否拥有

根据以上即可知道处于哪个状态!

3、进程控制

进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。

如何实现进程控制?

原语实现进程控制。原语的特点是执行期间不允许中断,只能一气呵成。

这种不可被中断的操作即原子操作

原语采用“关中断指令”和“开中断指令”实现

显然,关/开中断指令的权限非常大,必然只允许在核心态下执行的特权指令

原语任务

  • 更新PCB中的信息(如修改进程状态,将运行环境保存到PCB、从PCB恢复运行环境)
    • 所有的进程控制原语一定都会修改进程状态标志
    • 剥夺当前运行进程的CPU使用权必然需要保存其运行环境
    • 某进程开始运行前必然要恢复其运行环境
  • 将PCB插入合适的队列
  • 分配/回收资源

进程的创建

进程的终止

进程的阻塞

进程的切换

4、进程通信

进程通信是指进程之间的信息交换。PV操作是低级通信方式,高级通信方式是指以较高的程效率传输大量数据的通信方式。

进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。

为了保证安全,一个进程不能直接访问另一个进程的地址空间。

但是进程之间的信息交换又是必须实现的。为了保证进程间的安全通信,操作系统提供了一些方法。

共享存储

两个进程对共享空间的访问必须是互斥的(互斥访问通过操作系统提供的工具实现)。

操作系统只负责提供共享空间和同步互斥工具(如P、v操作)

共享存储

  • 基于数据结构共享:比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
  • 基于存储区共享:基于在内存中画出一块共享存储区,数据的形式、存放位置都由进程控制,而不是操作系统。相比之下,这种共享方式速度更快,是一种高级通信方式。

管道通信

管道”是指用于连接读写进程的一个共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的缓冲区

  • 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道
  • 各进程要互斥地访问管道。
  • 数据以字符流的形式写入管道,当管道写满时,写进程的write() 系统调用将被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的read() 系统调用将被阻塞。(缓冲区的特性)
  • 如果没写满,就不允许读。如果没读空,就不允许写。(缓冲区的特性)
  • 数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。

消息传递

进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息 / 接收消息”两个原语进行数据交换。

消息

  • 消息头:包括发送进程ID、接受进程ID、消息类型、消息长度等格式化的信息(计算机网络中发送的“报文”其实就是一种格式化的消息)
  • 消息体

消息传递方式

  • 直接消息传递:消息直接挂到接收进程的消息缓冲队列上
  • 间接消息传递:消息要先发送到中间实体(信箱)中,因此也称“信箱通信方式”。Eg:计网中的电子邮件系统

5、线程概念&多线程模型

线程概述

有的进程可能需要“同时”做很多事,而传统的进程只能串行地执行一系列程序。为此,引入了“线程”,来增加并发度

  • 传统的进程是程执行流的最小单位
  • 引入线程后,线程成为了程序执行流的最小单位

可以把线程理解为“轻量级进程”。
线程是一个基本的CPU执行单元,也是程序执行流的最小单位

引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
引入线程后,进程只作为除CPU之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。

引入线程带来的变化

资源分配、调度

  • 传统进程机制中,进程是资源分配、调度的基本单位
  • 引入线程后,进程资源分配的基本单位,线程调度的基本单位

并发性

  • 传统进程机制中,只能进程间并发
  • 引入线程后,各线程间也能并发,提升了并发度

系统开销

  • 传统的进程间并发,需要切换进程的运行环境,系统开销很大
  • 线程间并发,如果是同一进程内的线程切换,则不需要切换进程环境,系统开销小
  • 引入线程后,并发所带来的系统开销减小

线程属性

  • 线程是处理机调度的单位
  • 多CPU计算机中,各个线程可占用不同的CPU
  • 每个线程都有一个线程ID、线程控制块(TCB)
  • 线程也有就绪、阻塞、运行三种基本状态
  • 线程几乎不拥有系统资源
  • 同一进程的不同线程间共享进程的资源
  • 由于共享内存地址空间,同- -进程中的线程间通信甚至无需系统干预
  • 同一进程中的线程切换,不会引起进程切换
  • 不同进程中的线程切换,会引起进程切换
  • 切换同进程内的线程,系统开销很小
  • 切换进程,系统开销较大

线程实现方式

用户级线程

  • 用户级线程由应用程序通过线程库实现。所有的线程管理工作都由应用程序负责(包括线程切换)
  • 用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预。
  • 在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。(用户级线程对用户不透明,对操作系统透明)
  • 可以这样理解,“用户级线程”就是“从用户视角看能看到的线程”

内核级线程

  • 内核级线程的管理工作由操作系统内核完成。线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
  • 可以这样理解,“内核级线程”就是“从操作系统内核视角看能看到的线程”

在同时支持用户级线程和内核级线程的系统中,可采用二者组合的方式:将n个用户级线程映射到m个内核级线程上 ( n >= m)

  • 操作系统只“看得见”内核级线程,因此只有内核级线程才是处理机分配的单位。
  • 例如:该进程由两个内核级线程,三个用户级线程,在用户看来,这个进程中有三个线程。但即使该进程在一个4核处理机的计算机上运行,也最多只能被分配到两个核,最多只能有两个用户线程并行执行(只能分配到有限的两个内核级线程)。

多线程模型

在同时支持用户级线程和内核级线程的系统中,由几个用户级线程映射到几个内核级线程的问题引出了“多线程模型”问题。

多对一模型:多个用户及线程映射到一个内核级线程。每个用户进程只对应一个内核级线程。

  • 优点:用户级线程的切换在用户空间用户态即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
  • 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行(只有一个内核级线程)

一对一模型:一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。

  • 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强多线程可在多核处理机上并行执行
  • 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换核心态,因此线程管理的成本高,开销大。

多对多模型:n用户及线程映射到m个内核级线程(n >= m) 。每个用户进程对应m个内核级线程。

  • 克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。

6、处理机调度

基本概念

当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是“调度”研究的问题。

在多道程序系统中,进程的数量往往是多于处理机的个数的,这样不可能同时并行地处理各个进程。处理机调度,就是从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程的并发执行。

高级调度

由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序。

高级调度(作业调度):按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。

高级调度是辅存(外存)内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。

中级调度

引入了虚拟存储技术之后,可将暂时不能运行的进程调至外存等待。等它重新具备了运行条件且内存又稍有空闲时,再重新调入内存。

这么做的目的是为了提高内存利用率和系统吞吐量

暂时调到外存等待的进程状态为挂起态。值得注意的是,PCB并不会一起调到外存,而是会常驻内存。PCB中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的PCB来保持对各个进程的监控、管理。被挂起的进程PCB会被放到的挂起队列中。

中级调度(内存调度):就是要决定将哪个处于挂起状态的进程重新调入内存。

一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高。

低级调度

低级调度(进程调度):其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。

进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。

进程调度的频率很高,一般几十毫秒一次。

三层调度对比

七状态模型

暂时调到外存等待的进程状态为挂起态

挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态

五状态模型 升级为 七状态模型

注意“挂起”和“阻塞”的区别

两种就绪挂起 状态都是暂时不能获得CPU的服务,但挂起态是将进程映像调到外存去了,而阻塞态下进程映像还在内存中。

有的操作系统会把就绪挂起、阻塞挂起分为两个挂起队列,甚至会根据阻塞原因不同再把阻塞挂起进程进一步细分为阻塞挂起 多个队列。

进程调度时机

需要进行进程调度与切换的情况

  • 当前运行的进程主动放弃处理机
    • 进程正常终止
    • 运行过程中发生异常而终止进程主动请求阻塞(如等待l/O)
  • 当前运行的进程被动放弃处理机
    • 分给进程的时间片用完
    • 有更紧急的事需要处理(如I/O中断)有更高优先级的进程进入就绪队列

不能进行进程调度与切换的情况

  • 处理中断的过程中。中断处理过程复杂,与硬件密切相关,很难做到在中断处理过程中进行进程切换。
  • 进程在操作系统内核程序临界区中。
  • 原子操作过程中(原语)。原子操作不可中断,要一气呵成(如之前讲过的修改PCB中进程状态标志,并把PCB放到相应队列)

临界资源:一个时间段内只允许一个进程使用的资源。各进程需要互斥地访问临界资源。

临界区:访问临界资源的那段代码。

进程调度的切换和过程

“狭义的进程调度”与“进程切换”的区别:

狭义的进程调度指的是从就绪队列中选中一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程,后一种情况就需要进程切换)

进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。

过程

广义的进程调度包含了选择一个进程和进程切换两个步骤

进程切换的过程主要完成了:

  • 对原来运行进程各种数据的保存
  • 对新的进程各种数据的恢复(如:程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)

注意进程切换是有代价的,因此如果过于频繁的进行进程调度、切换,必然会使整个系统的效率降低,使系统大部分时间都花在了进程切换上,而真正用于执行进程的时间减少。

进程调度的方式

非剥夺调度方式,又称非抢占方式。即,只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。

  • 实现简单,系统开销小但是无法及时处理紧急任务,适合于早期的批处理系统

剥夺调度方式,又称抢占方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。

  • 可以优先处理更紧急的进程,也可实现让各进程按时间片轮流执行的功能(通过时钟中断)。适合于分时操作系统、实时操作系统

7、进程调度算法

调度算法评价指标

CPU利用率:指CPU “忙碌”的时间占总时间的比例。

利用率 = 忙碌的时间 / 总时间

系统吞吐量:单位时间内完成作业的数量

系统吞吐量 = 总共完成了多少道作业 / 总共花了多少时间

周转时间,是指从作业被提交给系统开始,到作业完成为止的这段时间间隔。

它包括四个部分:

  • 作业在外存后备队列上等待作业调度(高级调度)的时间、
  • 进程在就绪队列上等待进程调度(低级调度)的时间、
  • 进程在CPU上执行的时间、
  • 进程等待I/O操作完成的时间。

后三项在一个作业的整个处理过程中,可能发生多次

  • 周转时间=作业完成时间–作业提交时间
  • 平均周转时间=各作业周转时间之和/作业数
  • 带权周转时间=作业周转时间/作业实际运行的时间=(作业完成时间–作业提交时间)/作业实际运行的时间
  • 平均带权周转时间=各作业带权周转时间之和/作业数

带权周转时间与周转时间都是越小越好

等待时间,指进程 / 作业处于等待处理机状态时间之和,等待时间越长,用户满意度越低。

无I/O处理:等待时间 = 周转时间 - 运行时间

有I/O处理:等待时间 = 周转时间 - 运行时间 - I/O处理时间

  • 对于进程来说,等待时间就是指进程建立后等待被服务的时间之和,在等待I/O完成的期间其实进程也是在被服务的,所以不计入等待时间。
  • 对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中等待的时间。

一个作业总共需要被CPU服务多久,被I/O设备服务多久一般是确定不变的,因此调度算法其实只会影响作业 / 进程的等待时间。当然,与前面指标类似,也有“平均等待时间”来评价整体性能。

响应时间,指从用户提交请求到首次产生响应所用的时间。

先来先服务FCFS

公平

按照作业 / 进程到达的先后顺序进行服务

用于作业 / 进程调度:用于作业调度时,考虑的是哪个作业先到达后备队列;用于进程调度时,考虑的是哪个进程先到达就绪队列

非抢占式的算法

优点:公平、算法实现简单

缺点:排在长作业(进程)后面的短作业需要等待很长时间,带权周转时间很大,对短作业来说用户体验不好。即,FCFS算法对长作业有利,对短作业不利

不会产生饥饿现象

短作业优先SJF

算法思想:追求最少的平均等待时间,最少的平均周转时间、最少的平均平均带权周转时间(选择当前已到达的时间最短的执行)

算法规则: 最短的作业 / 进程优先得到服务(所谓“最短”,是指要求服务时间最短)

**即可用于作业调度,也可用于进程调度。**用于进程调度时称为“短进程优先(SPF, Shortest Process First)算法”

SJF和SPF是非抢占式的算法。但是也有抢占式的版本——最短剩余时间优先算法(SRTN, Shortest Remaining Time Next)(新到达的进程的剩余时间比当前运行的进程剩余时间更短,新进程抢占处理机)

优点:“最短的”平均等待时间、平均周转时间(抢占式的SRTN更短)

缺点:不公平。对短作业有利,对长作业不利。可能产生饥饿现象。另外,作业/进程的运行时间是由用户提供的,并不一定真实,不一定能做到真正的短作业优先

会产生饥饿。如果源源不断地有短作业/进程到来,可能使长作业/进程长时间得不到服务,产生“饥饿”现象。如果一直得不到服务.川称为“饿死”

高响应比优先HRRN

算法思想:要综合考虑作业/进程的等待时间和要求服务的时间

算法规则:在每次调度时先计算各个作业/进程的响应比,选择响应比最高的作业/进程为其服务

响应比= 等待时间 + 要求服务时间 / 要求服务时间

即可用于作业调度,也可用于进程调度

非抢占式的算法。因此只有当前运行的作业/进程主动放弃处理机时,才需要调度,才需要计算响应比

优缺点

  • 综合考虑了等待时间和运行时间(要求服务时间)
  • 等待时间相同时,要求服务时间短的优先(SJF的优点)
  • 要求服务时间相同时,等待时间长的优先(FCFS的优点)
  • 对于长作业来说,随着等待时间越来越久,其响应比也会越来越大,从而避免了长作业饥饿的问题

不会产生饥饿

三种算法总结1

  • 这几种算法主要关心对用户的公平性、平均周转时间、平均等待时间等评价系统整体性能的指标,但是不关心“响应时间”,也并不区分任务的紧急程度
  • 因此对于用户来说,交互性很糟糕。因此这三种算法一般适合用于早期的批处理系统,当然,FCFS算法也常结合其他的算法使用,在现在也扮演着很重要的角色。

时间片轮转调度算法

算法思想:公平地、轮流地为各个进程服务,让每个进程在一定时间间隔内都可以得到响应

算法规则:按照各进程到达就绪队列的顺序,轮流让各个进程执行一个时间片(如100ms)。若进程未在一个时间片内执行完,则剥夺处理机,将进程重新放到就绪队列队尾重新排队。

用于进程调度(只有作业放入内存建立了相应的进程后,才能被分配处理机时间片)

若进程未能在时间片内运行完,将被强行剥夺处理机使用权,因此时间片轮转调度算法属于抢占式的算法。由时钟装置发出时钟中断来通知CPU时间片已到

优点:公平;响应快,适用于分时操作系统;

缺点:由于高频率的进程切换,因此有一定开销,不区分任务的紧急程度。时间片太大会退化为FCFS算法,太小进程切换开销太大!

不会产生饥饿

优先级调度算法

算法思想:随着计算机的发展,特别是实时操作系统的出现,越来越多的应用场景需要根据任务的紧急程度来决定处理顺序

算法规则:每个作业/进程有各自的优先级,调度时选择优先级最高的作业/进程

既可用于作业调度,也可用于进程调度。甚至,还会用于在之后会学习的I/O调度中

抢占式、非抢占式都有。做题时的区别在于:非抢占式只需在进程主动放弃处理机时进行调度即可,而抢占式还需在就绪队列变化时,检查是否会发生抢占。(即新到达的是否比正在运行的优先级要高,高则抢占处理机)

优点:用优先级区分紧急程度、重要程度,适用于实时操作系统。可灵活地调整对各种作业/进程的偏好程度。

缺点:若源源不断地有高优先级进程到来,则可能导致饥饿

会产生饥饿

  • 就绪队列未必只有一个,可以按照不同优先级来组织。另外,也可以把优先级高的进程排在更靠近队头的位置
  • 根据优先级是否可以动态改变,可将优先级分为静态优先级和动态优先级两种。
    • 静态优先级:创建进程时确定,之后一直不变。
    • 动态优先级:创建进程时有一个初始值,之后会根据情况动态地调整优先级。

合理设置优先级

  • 合理高于用户进程
  • 前台进程优先级高于后台进程
  • 操作系统更偏好l/O型进程(或称IO繁忙型进程)

注:与I/O型进程相对的是计算型进程(或称CPU繁忙型进程)

什么时候跳转优先级?

  • 可以从追求公平、提升资源利用率等角度考虑
  • 如果某进程在就绪队列中等待了很长时间,则可以适当提升其优先级,如果某进程占用处理机运行了很长时间,则可适当降低其优先级如果发现一个进程
  • 频繁地进行l/O操作,则可适当提升其优先级

多级反馈队列调度算法

**算法思想:**对其他调度算法的折中权衡

算法规则:

  • 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
  • 新进程到达时先进入第1级队列,按FCFS原则排队等待被分配时间片,若用完时间片进程还未结束,则进程进入下一级队列队尾。如果此时已经是在最下级的队列,则重新放回该队列队尾
  • 只有第k 级队列为空时,才会为k+1级队头的进程分配时间片用于进程调度

抢占式的算法。在k级队列的进程运行过程中,若更上级的队列(1~k-1级)中进入了一个新进程,则由于新进程处于优先级更高的队列中,因此新进程会抢占处理机,原来运行的进程放回k级队列队尾。

优缺点:

对各类型进程相对公平(FCFS的优点),每个新到达的进程都可以很快就得到响应(RR的优点),短进程只用较少的时间就可完成(SPF的优点),不必实现估计进程的运行时间(避免用户作假),可灵活地调整对各类进程的偏好程度,比如CPU密集型进程、I/O密集型进程(拓展:可以将因I/O而阻塞的进程重新放回原队列,这样/o型进程就可以保持较高优先级)

会产生饥饿 (连续新进程到达,则会导致当前进程饿死)

例子

三种算法总结2

比起早期的批处理操作系统来说,由于计算机造价大幅降低,因此之后出现的交互式操作系统(包括分时操作系统、实时操作系统等)更注重系统的响应时间、公平性、平衡性等指标。而这几种算法恰好也能较好地满足交互式系统的需求。因此这三种算法适合用于交互式系统。(比如UNIX使用的就是多级反馈队列调度算法)

8、进程同步&进程互斥

概述

进程同步:同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。(解决异步的不可预知性)

进程互斥:对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。

临界资源:我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。

临界资源互斥访问,可划分为四个部分:

  • 进入区:负责检查是否可进入临界区,若可进入,则应设置正在访问临界资源的标志(可理解为“上锁”),以阻止其他进程同时进入临界区
  • 临界区:访问临界资源的那段代码
  • 退出区:负责解除正在访间临界资源的标志(可理解为“解锁”)
  • 剩余区:做其他处理

临界资源的互斥访问原则

  • 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区;
  • 忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待;
  • 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿);
  • 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

进程互斥软件实现

单标志法

算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予

该算法可以实现“同一时刻最多只允许一个进程访问临界区”

单标志法存在的主要问题是:违背“空闲让进”原则。

由于执行顺序一定是P1P2轮流执行,因此P1只要不触发执行,P2一定无法执行!

双标志先检查法

算法思想:设置一个布尔型数组flag[],数组中各个元素用来标记各进程想进入临界区的意愿,比如“flag[0]=ture”意味着0号进程P0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。

双标志先检查法存在的主要问题是:违背“忙则等待”原则。

第一步和第五步若发生进程切换,则对邻接资源的访问就不互斥了!

原因在于先检查的两句代码不是原子操作!

双标志后检查法

算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。

解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”

同样第一步和第五步进程切换,就会发生二者都进不去临界区!

Peterson算法

算法思想:双标志后检查法中,两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区。Gary L. Peterson想到了一种方法,如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”,主动让对方先使用临界区。

Peterson算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。

进程互斥硬件实现

中断屏蔽方法

利用“开 / 关中断指令”实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)

  • 关中断
  • 临界区
  • 开中断

优点:简单、高效

缺点不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开 / 关中断指令 只能运行在内核态,这组指令如果能让用户随意使用会很危险)

TestAndSet指令

简称TS 指令,也有地方称为TestAndSetLock 指令,或TSL 指令 . TSL 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。

相比软件实现方法,TSL指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境

缺点不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

Swap指令

有的地方也叫Exchange 指令,或简称XCHG 指令。

Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。

逻辑上来看Swap 和TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在old量上),再将上锁标记lock 设置为true,最后检查 old,如果 old 为false 则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境

缺点不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

9、信号量机制

信号量机制

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。

信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量) ,可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1 的信号量。

原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。

一对原语:wait(S) 原语和signal(S) 原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S 其实就是函数调用时传入的一个参数。

wait、signal 原语常简称为P、V操作(来自荷兰语proberen 和verhogen)。因此,做题的时候常把wait(S) 、signal(S) 两个操作分别写为P(S) 、V(S)

整型信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。

与普通整数变量的区别:对信号量的操作只有三种,即初始化、P操作、V操作

记录型信号量

整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。

wait(S) 、signal(S) 也可以记为P(S) 、V(S) ,这对原语可用于实现系统资源的“申请”和“释放”

S.value 的初值表示系统中某种资源的数目

对信号量S 的一次P 操作意味着进程请求一个单位的该类资源,因此需要执行S.value-- ,表示资源数减1,当S.value < 0 时表示该类资源已分配完毕,因此进程应调用block 原语进行自我阻塞(当前运行的进程从运行态 ----> 阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L 中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。

对信号量S 的一次V 操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是 S.value <= 0,表示依然有进程在等待该类资源,因此应调用wakeup 原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态 ----> 就绪态)。

信号量机制实现进程互斥

  1. 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
  2. 设置互斥信号量mutex,初值为1
  3. 在进入区P(mutex)——申请资源
  4. 在退出区V(mutex)——释放资源

理解:信号量mutex 表示“进入临界区的名额”

注意:

  • 对不同的临界资源需要设置不同的互斥信号量。
  • P、V操作必须成对出现。缺少P(mutex) 就不能保证临界资源的互斥访问。缺少V(mutex) 会导致资源永不被释放,等待进程永不被唤醒。

信号量机制实现进程同步

进程同步:要让各并发进程按要求有序地推进。

比如,P1、P2 并发执行,由于存在异步性,因此二者交替推进的次序是不确定的。若P2 的“代码4”要基于 P1 的“代码1”和“代码2”的运行结果才能执行,那么我们就必须保证“代码4”一定是在“代码2”之后才会执行。

这就是进程同步问题,让本来异步并发的进程互相配合,有序推进。

用信号量实现进程同步:

  1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)
  2. 设置同步信号量S, 初始为0
  3. 在“前操作”之后执行V(S)
  4. 在“后操作”之前执行P(S)

技巧口诀:前V后P

信号量机制实现前驱关系

进程P1 中有句代码S1,P2 中有句代码S2 ,P3中有句代码S3 …… P6 中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行

其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)

因此:

  • 要为每一对前驱关系各设置一个同步信号量

  • 在“前操作”之后对相应的同步信号量执行V 操作

  • 在“后操作”之前对相应的同步信号量执行P 操作

10、经典进程同步问题

生产者消费者问题

问题描述

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)

生产者、消费者共享一个初始为空、大小为n的缓冲区。

  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。(同步关系
  • 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。(同步关系
  • 缓冲区是临界资源,各进程必须互斥地访问。(互斥关系)
问题分析

PV操作题目分析步骤:

  1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
  2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
  3. 设置信号量。并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
semaphore mutex = 1; // 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0; // 同步信号量,表示产品的数量,也即非空缓冲区的数量
如何实现

能否改变相邻的PV操作顺序?

① -> ② -> ③
若此时缓冲区内已经放满产品,则empty=0,full=n。

则生产者进程执行①使mutex变为0,再执行②,由于已没有空闲缓冲区,因此生产者被阻塞。由于生产者阻塞,因此切换回消费者进程。消费者进程执行③,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”。

③ -> ④ -> ①
同样的,若缓冲区中没有产品,即full=0,empty=n。按③④① 的顺序执行就会发生死锁。

因此,实现互斥的P操作一定要在实现同步的P操作之后。
V操作不会导致进程阻塞,因此两个V操作顺序可以交换。

多生产者多消费者问题

问题描述

桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。

问题分析
  • 互斥关系:(mutex = 1):对缓冲区(盘子)的访问要互斥地进行
  • 同步关系(一前一后):
    • 父亲将苹果放入盘子后,女儿才能取苹果
    • 母亲将橘子放入盘子后,儿子才能取橘子
    • 只有盘子为空时,父亲或母亲才能放入水果
如何实现

不设置互斥信号量?

原因在于:本题中的缓冲区大小为1,在任何时刻,apple、orange、plate三个同步信号量中最多只有一个是1。因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利地进入临界区…

如果缓冲区大小为2,则两个生产者会都进入临界区(可能造成后者将前者的数据覆盖掉),这时候就必须得设置互斥信号量了!

注意:

  • 互斥信号量加不加,加上肯定没错
  • 实现互斥的P操作一定要在实现同步的P操作之后,否则可能导致死锁
  • 分析问题应该以事件角度考虑,而不是以进程角度考虑

吸烟者问题

问题描述

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。

三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。

供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

问题分析

同步关系

  • 桌上有组合一 → 第一个抽烟者取走东西

  • 桌上有组合二 → 第二个抽烟者取走东西

  • 桌上有组合三 → 第三个抽烟者取走东西

  • 发出完成信号 → 供应者将下一个组合放到桌上

互斥关系

  • 桌子抽象为容量为1的缓冲区,需要互斥访问

如何实现

读者写者问题

问题描述

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。

因此要求:

  • 允许多个读者可以同时对文件执行读操作
  • 只允许一个写者往文件中写信息
  • 任一写者在完成写操作之前不允许其他读者或写者工作
  • 写者执行写操作前,应让已有的读者和写者全部退出。

问题分析

两类进程:写进程、读进程

互斥关系:写进程—写进程、写进程—读进程。读进程与读进程不存在互斥问题。

  • 写者进程和任何进程都互斥,设置一个互斥信号量rw,在写者访问共享文件前后分别执行P、V操作。读者进程和写者进程也要互斥,因此读者访问共享文件前后也要对rw执行P、V操作.
  • P(rw)和V(rw)其实就是对共享文件的“加锁”和“解锁”。既然各个读进程需要同时程与写进程又必须互斥访问,那么我们可以让第一个访问文件的读进程“加锁”,让最后一个访问完文件的读进程“解锁”。可以设置一个整数变量count 来记录当前有几个读进程在访问文件。
如何实现

此种实现是读进程优先的,读进程一直进入,则会导致写进程饿死!

写优先,但不是真正的写优先,相对公平的先来先服务原则,又叫做读写公平法

哲学家进餐问题

问题描述

一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

问题分析

这些进程之间只存在互斥关系,但是与之前接触到的互斥关系不同的是,每个进程都需要同时持有两个临界资源,因此就有“死锁”问题的隐患。

  • 关系分析。系统中有5个哲学家进程,5位哲学家与左右邻居对其中间筷子的访问是互斥关系。
  • 整理思路。这个问题中只有互斥关系,但与之前遇到的问题不同的是,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的精髓。
  • 信号量设置。定义互斥信号量数组 chopstick[5]={1,1,1,1,1} 用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家 i 左边的筷子编号为i,右边的筷子编号为 (i+1)%5。
如何实现

错误方案

合理方案

  • 可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的
  • 要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况。
  • 仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。

第三种方案的实现:

可以保证至少有一人可以吃到饭!

11、管程

为什么要引入管程

信号量机制存在的问题:编写程序困难、易出错

能不能设计一种机制,让程序员写程序时不需要再关注复杂的PV操作,让写代码更轻松呢?

1973年,Brinch Hansen 首次在程序设计语言 (Pascal)中引入了“管程”成分,即一种高级同步机制

管程定义

管程是一种特殊的软件模块,由这些部分组成:

  1. 局部于管程的共享数据结构说明;
  2. 对该数据结构进行操作的一组过程(函数);
  3. 对局部于管程的共享数据设置初始值的语句;
  4. 管程有一个名字。

类似于

管程基本特征

  1. 局部于管程的数据只能被局部于管程的过程所访问;
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据;
  3. 每次仅允许一个进程在管程内执行某个内部过程。

前两句类似:类对象对类的访问!

管程解决生产者消费者问题

引入管程的目的无非就是要更方便地实现进程互斥和同步

简单来说:封装思想!

  • 需要在管程中定义共享数据(如生产者消费者问题的缓冲区)
  • 需要在管程中定义用于访问这些共享数据的“入口”——其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)
  • 只有通过这些特定的“入口”才能访问共享数据
  • 管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区 注意:这种互斥特性是由编译器负责实现的,程序员不用关心)
  • 可在管程中设置条件变量及等待 / 唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。

12、死锁

脑图

什么是死锁

哲学家进餐问题中,如果5位哲学家进程并发执行,都拿起了左手边的筷子…

每位哲学家都在等待自己右边的人放下筷子,这些哲学家进程都因等待筷子资源而被阻塞。即发生“死锁”

并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是“死锁”。发生死锁后若无外力干涉,这些进程都将无法向前推进。

死锁&饥饿&死循环

死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。

饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF) 算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机, 从而发生长进程“饥饿”。

死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug导致的,有时是程序员故意设计的。

死锁产生必要条件

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生

互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。

不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

注意:发生死锁时一定有循环等待,但是发生循环等待时未必死锁( 循环等待是死锁的必要不充分条件)

如果同类资源数大于1,则即使有循环等待,也未必发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。

什么时候发生死锁

对系统资源的竞争。各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(CPU)的竞争是不会引起死锁的。

进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程P1、P2分别申请并占有了资源R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。

信号量的使用不当也会造成死锁。如生产者~消费者问题中,如果实现互斥的P操作在实现同步的P操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资源)

总之,对不可剥夺资源的不合理分配,可能导致死锁。

死锁处理策略

  1. 预防死锁。破坏死锁产生的四个必要条件中的一个或几个。
  2. 避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
  3. 死锁的检测和解除。允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。

预防死锁

脑图

破坏互斥条件

互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。

如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如 : SPOOLing技术

操作系统可以采用SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备…(打印机添加任务队列即可)

该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件。

破坏不剥夺条件

不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。

方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)

该策略的缺点:

  • 实现起来比较复杂。
  • 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。
  • 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
  • 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿
破坏请求和保持条件

请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。

该策略实现起来简单,但也有明显的缺点:

  • 有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿
破坏循环等待条件

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完

原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。

该策略的缺点:

  1. 不方便增加新的设备,因为可能需要重新分配所有的编号;
  2. 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费
  3. 必须按规定次序申请资源,用户编程麻烦

避免死锁

安全序列&不安全状态&死锁

安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。

如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)

因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“银行家算法”的核心思想。

银行家算法

银行家算法是荷兰学者Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁

核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。

银行家算法步骤:

  • 检查此次申请是否超过了之前声明的最大需求数
  • 检查此时系统剩余的可用资源是否还能满足这次请求
  • 试探着分配,更改各数据结构
  • 用安全性算法检查此次分配是否会导致系统进入不安全状态

数据结构:

  • 长度为m 的一维数组Available 表示还有多少可用资源
  • n * m 矩阵Max 表示各进程对资源的最大需求数
  • n * m 矩阵Allocation 表示已经给各进程分配了多少资源
  • Max – Allocation = Need 矩阵表示各进程最多还需要多少资源
  • 用长度为m 的一位数组Request 表示进程此次申请的各种资源数

安全性算法步骤:

  • 检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,
  • 并把该进程持有的资源全部回收。
  • 不断重复上述过程,看最终是否能让所有进程都加入安全序列。

死锁的检测和解除

脑图

死锁检测

为了能对系统是否已发生了死锁进行检测,必须:

  • 某种数据结构来保存资源的请求和分配信息;
  • 提供一种算法,利用上述信息来检测系统是否已进入死锁状态。

如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去。如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程…

如果按上述过程分析,最终能消除所有边,就称这个图是可完全简化的。此时一定不发生死锁(相当于能找到一个安全序列

如果最终不能消除所有边,那么此时就是发生了死锁

死锁例子:

死锁解除

一旦检测出死锁的发生,就应该立即解除死锁

补充:并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程

解除死锁的主要方法有:

  • 资源剥夺法挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
  • 撤销进程法(或称终止进程法)。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来。
  • 进程回退法。让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点

如何绝对对谁动手?

  • 进程优先级低的
  • 已执行时间少的
  • 还有很久才能完成的
  • 进程使用资源多的
  • 交互式和批处理式,优先处理批处理式

三、内存管理

1、内存基础知识

内存

  • 内存是用于存放数据的硬件
  • 程序执行前需要先放到内存中才能被cpu处理
  • 如果计算机"按字节编址", 每个存储单元大小为 8 bit
  • 如果计算机"按字编址", 每个存储单元大小为 16 bit

指令的编指一般采用逻辑地址,即相对地址

物理地址 = 起始地址 + 逻辑地址

相对地址又称逻辑地址,绝对地址又称物理地址。

进程运行基本原理

  • 编辑
  • 编译:由编译程序将用户源代码编译成若干个目标模块
  • 链接
  • 装入

三种链接方式

链接:由连接程序将编译后形成的一组目标模块以及所需函数连接在一起, 形成一个完整的装入模块

  • 静态链接:在程序运行之前,先将各目标模块以及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
  • 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
  • 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。

三种装入方式

装入:由装入程序将装入模块装入内存运行

为了使编程更方便,程序员写程序时应该只需关注指令、数据的逻辑性。而逻辑地址到物理地址的转换(这个过程称为地址重定位),应该由操作系统负责,这样就保证了程序员写程序时不需要关注物理内存的实际情况。

  • 绝对装入

在编译时,如果知道程序将放到内存的哪个位置,编译程序将产生绝对地址的目标代码, 装入程序按照装入模块中的地址,将程序和数据装入内存【灵活性低, 只适合单道程序环境】

绝对装入只适用于单道程序环境。还未产生操作系统,由编译器完成!

程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。通常情况下都是编译或汇编时再转换为绝对地址。

  • 静态重定位

又称为可重定位装入。 编译, 链接后的装入模块地址都是从 0 开始的,指令中使用的地址和数据存放的地址都是相对于起始地址而言的逻辑地址。可以根据内存的当前状况将装入模块装入到内存的适当位置。装入时对地址进行"重定位",逻辑地址变换为物理地址(地址变换是在装入时一次完成的)。【早期多道批处理系统

静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。

  • 动态重定位

又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的,装入程序把装入模块装入内存后不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器(存放装入模块的起始位置)的支持。【现代操作系统

操作系统内存管理

  1. 操作系统负责内存空间的分配与回收
  2. 操作系统需要提供某种技术从逻辑上对内存空间进行扩充
  3. 操作系统需要提供地址转换功能,负责程序的逻辑地址与物理地址的转换(即三种装入方式
  4. 操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰

1、2、4 将在下方讲解!

2、内存保护采用的方法

  1. 在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,CPU检查是否越界
  2. 采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。"

3、内存空间的扩充

大纲

  • 覆盖技术
  • 交换技术
  • 虚拟存储技术

覆盖技术

覆盖技术:将程序分为多个段,常用的段常驻内存,不常用的段在需要的时候调入内存

内存中分为一个"固定区" 和若干个"覆盖区",常用的段放在固定区,不常用的段放在覆盖区

缺点:必须由程序员声明覆盖结构, 对用户不透明, 增加了用户的编程负担,覆盖技术只用于早期的操作系统中。

交换技术

交换技术:内存空间紧张时, 系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(即进程在内存与磁盘间动态调度)

应该在外存(磁盘)的什么位置保存被换出的进程?

具有对换功能的操作系统中,通常把磁盘空间分为文件区对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的I/O速度比文件区的更快。

什么时候应该交换?

交换通常在许多进程运行内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。

应该换出哪些进程?

可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间。

PCB仍然得常驻内存!

虚拟存储技术

大纲

传统存储方式问题
  • 一次性:作业必须一次性全部装入内存后才能开始运行

    • 作业很大时,无法装入导致大作业无法运行
    • 大量作业要求运行时内存无法容纳所有作业,导致多道程序并发度下降
  • 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直到作业运行结束,这样会导致内存中驻留大量的,暂时用不到的数据,浪费内存资源。

高速缓冲技术

是对局部性原理的应用!

将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速存储器中。

虚拟内存的定义和特征
  • 在程序装入时,将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
  • 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息由外存调入内存,然后继续执行程序。请求调页/段
  • 内存空间不够时, 操作系统负责将内存中暂时用不到的信息换出到外存。页面置换
  • 在用户看来,就有一个比实际内存大很多的内存,这就叫虚拟内存

虚拟内存的最大容量是由计算机的地址结构(CPU的寻址范围)确定的

虚拟内存的实际容量 = min(内存容量+外存容量,CPU寻址范围)

特征

多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。

对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。

虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量远大于实际容量。

虚拟内存的实现

虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配(非连续分配)的内存管理方式基础上。

对应传统的三种非连续分配存储管理,有三种虚拟存储的实现:

  • 请求分页存储管理
  • 请求分段存储管理
  • 请求段页式存储管理

调入:在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。(操作系统要提供请求调页 / 调段功能)

调出:若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。(操作系统要提供页面置换 / 段置换的功能)

请求分页存储管理

与基本分页管理方式的区别:

  • 调入:请求调页
  • 调出:页面置换

大纲

页表机制

与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。

当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。

缺页中断机构

在请求分页操作系统中,每当要访问的页面不在内存时,便产生一个缺页中断, 然后由操作系统的缺页中断处理程序处理中断。

此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒, 放回就绪队列。

  • 如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项
  • 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存,未修改过的页面不用写回外存。

缺页中断是因为当前执行的指令想要访问目标页面未调入内存而产生的,因此属于内中断(故障) 。

一条指令在执行期间,可能产生多次缺页中断。(如: copy A to B,即将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)

地址变换机构

  • 新增步骤1:请求调页(查到页表项时进行判断)
  • 新增步骤2:页面置换(需要调入页面,但没有空闲内存块时进行)
  • 新增步骤3:需要修改请求页表中新增的表项

  1. 只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。
  2. 和普通的中断处理一样,缺页中断处理依然需要保留CPU现场。
  3. 需要用某种“页面置换算法”来决定一个换出页面(下节内容)
  4. 换入/换出页面都需要启动慢速的l/O操作,可见,如果换入/换出太频繁,会有很大的开销。
  5. 页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。
页面置换算法

大纲

详细参考: https://www.itnxd/posts/8398.html#五、页面置换算法

页面的换入、换出需要磁盘I/O,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率!

最佳置换算法(OPT)

优先淘汰以后永不使用或者在最长时间内不会使用的页面,保证最低的缺页率。

但是操作系统无法预判页面访问序列,这种算法是无法实现的。

先进先出置换算法(FIFO)

优先淘汰最早进入内存的页面。

实现 :将调入内存的页面根据调入的先后顺序排成一个队列,需要置换页面的时候选择队首的页面。

实现简单;算法性能差,不适应进程实际运行时的规律,可能出现 Belady 异常。

Belady异常――当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。

只有FIFO算法会产生Belady异常。

最近最久未使用置换算法(LRU)

优先淘汰最近最久未使用的页面。

性能好,但实现起来需要专门的硬件支持,算法开销大。

时钟置换算法(CLOCK)

又叫做 最近未用算法 NRU

为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列

循环扫描各页面,第一轮淘汰访问位 = 0 的,并将扫描过的页面访问位改为1。若第一轮没选中,则进行第二轮扫描。(最多会扫描两轮!

实现简单,算法开销小;但未考虑页面是否被修改过

简单的时钟置换算法仅考虑到了一个页面最近是否被访问过,但是事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。

因此, 除了考虑一个页面最近有没有被访问过之外,操作系统还应该考虑页面有没有被修改过。

在其他条件都相同时,应该优先淘汰没有修改过的页面, 避免I/O操作,这就是改进型的时钟置换算法的思想。

改进型的时钟置换算法

利用**(访问位,修改位)** 的形式表示各页面状态

  • 第一轮 : 找第一个 (0, 0)的帧用于替换 ( 不修改标志位 )
  • 第二轮 : 找第一个 (0, 1)的帧用于替换 ( 将所有扫描过的帧访问位设为0)
  • 第三轮 : 找第一个 (0, 0)的帧用于替换 ( 不修改标志位 )
  • 第四轮 : 找第一个 (0, 1)的帧用于替换

简而言之:按照00,01,10,11的优先级进行替换!

算法开销小,性能也不错。

最多扫描四轮!

页面分配策略

大纲

驻留集:请求分页存储管理器中给进程分配的物理块的集合。

在采用虚拟存储技术的系统中, 驻留集的大小一般小于进程的总大小

  • 如果驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少。
  • 如果驻留集太大,会导致多道程序并发度下降,资源利用率降低。

策略

  • 固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变。
  • 可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变。

局部置换:发生缺页时只能选进程自己的物理块进行置换。

全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。

页面分配&置换策略?

固定分配局部置换:系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。这种策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理。(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)

可变分配全局置换:刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定的页面换出外存,再将该物理块分配给缺页的进程。采用这种策略时,只要某进程发生缺页,都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加。

可变分配局部置换:刚开始会为每个进程分配一定数量的物理块。当某进程发生缺页时,只允许从该进程自己的物理块中选出一个进行换出外存。如果进程在运行中频繁地缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块。

  • 可变分配全局置换:只要缺页就给分配新物理块
  • 可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块

何时调入页面?

预调页策略:根据局部性原理(主要指空间局部性),一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入,由程序员指出应该先调入哪些部分。(运行前调入

请求调页策略:进程在运行期间发现缺页时才将所缺页面调入内存。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘I/O操作,因此I/O开销较大。(运行时调入

何处调入页面?

  • 系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
  • 系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。
  • UNIX方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。

抖动现象?

刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸

产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数( 分配给进程的物理块不够)。

  • 为进程分配的物理块太少,会使进程发生抖动现象。
  • 为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率

为了研究为应该为每个进程分配多少个物理块,Denning提出了进程“工作集”的概念

工作集?

  • 工作集:指在某段时间间隔里,进程实际访问页面的集合。
  • 驻留集:指请求分页存储管理中给进程分配的内存块的集合。

工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为5,经过一段时间的监测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。

一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。

拓展:基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法――选择一个不在工作集中的页面进行淘汰。

4、内存空间的分配和回收

大纲

  • 连续分配管理方式
    • 单一连续分配
    • 固定分区分配
    • 动态分区分配
  • 非连续分配管理方式
    • 基本分页存储管理
    • 基本分段存储管理
    • 段页式存储管理

连续分配管理方式

连续分配:指为用户进程分配的必须是一个连续的内存空间。

单一连续分配

在单一连续分配的方式中, 内存被分为系统区和用户区, 系统区用于存放操作系统的相关数据,用户区用于存放用户进程的相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间。

优点 : 实现简单, 无外部碎片; 可以采用覆盖技术扩充内存; 不一定需要采取内存保护

缺点 : 只能用于单用户, 单任务的操作系统中; 有内部碎片; 存储器利用率极低

内部碎片 : 分配给某进程的内存区域有一部分没有用上,即存在" 内部碎片 ".

外部碎片 : 内存中的某些空闲分区由于太小而难以利用

固定分区分配

在产生了支持多道程序的系统后,为了能在内存中装入多道程序而互相之间不产生干扰,将整个用户区划分为若干个固定大小的分区(分区大小可以相等也可以不相等),在每个分区中只能装入一道作业, 形成了最早的可运行多道程序的内存管理方式。

固定分区分配

  • 分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合
  • 分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分

操作系统如何记录内存中的各个分区?

操作系统建立分区说明表来实现各个分区的分配和回收。

每个表对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小,起始地址,状态(是否已分配)

数据结构的数组(或链表)即可表示这个表。

当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为 ” 已分配 “ 。

优点:实现简单,无外部碎片

缺点

  • 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能。
  • 会产生内部碎片,内存利用率低。

动态分区分配

动态分区分配又称为可变分区分配, 这种分配方式不会预先划分内存分区。而是在进程装入内存时根据进程大小动态地建立分区,并使得分区的大小正好适合进程的需要。系统分区的大小和数目是可变的!

系统要用什么样的数据结构记录内存的使用情况?

  • 空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息。
  • 空闲分区链:每个分区的其实部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息。

动态分配不会产生内部碎片, 而会产生外部碎片, 外部碎片可以通过" 紧凑"的方式解决。

当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?

把一个新作业装入内存时,须按照一定的动态分区分配算法(后面详细讲解),从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法算法对系统性能有很大的影响,因此人们对它进行了广泛的研究。

如何进行分区的分配与回收操作?

  • 修改表项或链表节点值(分区还有剩余)
  • 删除表项或链表节点(分区恰好分配完毕)
  • 两个空闲分区的合并

动态分区分配算法

首次适应算法

每次都从低地址开始查找,找到第一个能满足大小的空闲分区。

实现:把空闲分区按地址递增的次序排列

每次分配内存时顺序地查找空闲分区链,找到大小能满足要求的第一个空闲分区。

优点:综合看性能最好。算法开销小,回收分区后一般不需要对空闲分区队列重新排序。

最佳适应算法

优先使用小的空闲分区

实现: 空闲分区按容量递增次序排序

每次分配内存时顺序查找空闲分区链, 找到大小能满足要求的第一个空闲分区。

优点:会有更多的大分区被保留下来,更能满足大进程需求。

缺点:每次都选择最小的分区进行分配,会留下越来越多的容量很小难以利用的内存块,即产生很多的外部碎片;算法开销大,回收分区后可能需要对空闲分区队列重新排序。

最坏适应算法

优先使用大的空闲分区

实现: 空闲分区按容量递减次序排序

优点:可以减少难以利用的小碎片。

缺点:每次都选用最大的分区进行分配,当较大的连续空闲区被小号之后,如果有大进程到来则没有内存分区可以利用;算法开销大。

邻近适应算法

在首次适应算法的基础上,每次都从上次查找结束的位置开始查找空闲分区链(表),找到大小能满足的第一个空闲分区。

优点:不用每次都从低地址的小分区开始检索。算法开销小。

缺点:邻近适应算法导致无论低地址还是高地址的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用划分为小分区,最后导致没有大分区可用。

小总结

算法开销大:最佳适应法, 最坏适应法 ( 需要经常排序)

算法开销小:首次适应算法, 邻近适应算法

非连续分配管理方式

连续分配管理方式缺点:

  • 固定分区分配:缺乏灵活性,会产生大量的内部碎片,内存的利用率很低。
  • 动态分区分配:会产生很多外部碎片,虽然可以用“紧凑”技术来处理,但是“紧凑”的时间代价很高

基于这一思想,产生了“非连续分配方式”,或者称为“离散分配方式”。

非连续:为用户进程分配的可以是一些分散的内存空间!

基本分页存储管理

大纲

分页存储基本概念

思想:把内存分为一个个相等的小分区, 再按照分区大小把进程拆分成一个个小部分.

页框:将内存大小分为一个个大小相等的分区,每个分区就是一个 “ 页框 ”,或称 “ 页帧 ”、“ 内存块 ”、“ 物理块 ”。每个页框有一个编号,即 “ 页框号 ”,或称 “ 内存块号 ”、“ 页帧号 ”、“ 物理块号 ” ,页框号从 0 开始。

页面:将用户进程的地址空间也分为与页框大小相等的一个个区域,称为 “ 页 ” 或 “ 页面 ”。每个页面也有一个编号,即 “ 页号 ”,页号也是从 0 开始。

  • 进程的最后一个页面可能没有一个页框那么大。因此。页框不能太大,否则可能产生过大的内部碎片
  • 操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框一一对应的关系。
  • 各个页面不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。
如何实现地址转换

例子:

CPU执行指令1,需要访问逻辑地址为80的内存单元,如何转化为物理地址?

逻辑地址为80的内存单元,应该在1号页,该页在内存中的起始位置为450,逻辑地址为80的内存单元相对于该页的起始地址而言,“偏移量”应该是30。

实际物理地址 = 450 + 30 = 480

  • 要算出逻辑地址对应的页号
  • 要知道该页号对应页面在内存中的起始地址
  • 要算出逻辑地址在页面内的“偏移量”
  • 物理地址 = 页面始址 + 页内偏移量

如何计算:

页号 = 逻辑地址 / 页面长度(取除法的整数部分)

页内偏移量 = 逻辑地址 % 页面长度(取除法的余数部分)

页面在内存中的起始位置:操作系统需要用某种数据结构记录进程各个页面的起始位置。

页号 = 80 / 50 = 1

页内偏移量 = 80 % 50 = 30

1 号页在内存中存放的起始位置 450

为了方便计算机计算页号、页内偏移量,页面大小一般设为2的整数幂!

逻辑地址结构

逻辑地址结构包含两个部分:前一部分为页号,后一部分为页内偏移量w。在上图所示的例子中,地址长度为32位,其中0 ~ 11位为“页内偏移量”,或称“页内地址”;12~31位为“页号”。

  • 如果有K位表示“页内偏移量”,则说明该系统中,一个页面的大小是2^K个内存单元
  • 如果有M位表示“页号”,则说明在该系统中,一个进程最多允许有2^M个页面
页表

解决地址转换的第二步:计算页号对应页面在内存中的起始地址

为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表

  • 一个进程对应一张页表
  • 进程的每一页对应一个页表项
  • 每个页表项由 “ 页号 ” 和 “ 块号 ” 组成
  • 页表记录进程页面和实际存放的内存块之间的对应关系
  • 每个页表项的长度是相同的,页号是“隐含”的

例子

假设某系统物理内存大小为4GB,页面大小为4KB,则每个页表项至少应该为多少字节?

4GB = 2^32B,4KB = 2^12B

因此4GB的内存总共会被分为 2^32 / 2^12 = 2^20 个内存块,因此内存块号的范围应该是0 ~ 2^20 - 1 因此至少要20个二进制位才能表示这么多的内存块号,因此至少要3个字节才够 (每个字节8个二进制位,3个字节共24个二进制位)

各页表项会按顺序连续地存放在内存中

如果该页表在内存中存放的起始地址为X,则M号页对应的页表项一定是存放在内存地址为 X + 3 * M 因此,页表中的**“页号”可以是“隐含”的**。只需要知道页表存放的起始地址页表项长度,即可找到各个页号对应的页表项存放的位置

在本例中,一个页表项占3B,如果进程由n个页面,则该进程的页表总共会占 3 * n 个字节

基本地址变换机构

大纲

用于实现逻辑地址到物理地址转换(借助进程的页表)的一组硬件机构

  • 通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址 F 和页表长度 M
  • 进程未执行时,页表的始址页表长度放在进程控制块(PCB)中;
  • 进程被调度时,操作系统内核会把它们放到页表寄存器中。

设页面大小为L,逻辑地址A到物理地址E的变换过程如下:

  1. 计算页号 P 和页内偏移量 W。(如果用十进制数手算,则 P = A / L,W = A % L; 但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)
  2. 比较页号 P 和页表长度M,若P ≥ M,则产生越界中断,否则继续执行。(注意:页号是从0开始的,而页表长度至少是1,因此P = M时也会越界)
  3. 页表中页号 P 对应的页表项地址 = 页表起始地址 F + 页号 P * 页表项长度,取出该页表项内容b,即为内存块号。
  4. 计算 E= b * L + W ,用得到的物理地址 E 去访存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了))

页表长度指的是这个页表中总共有几个页表项,即总共有几个页;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间

在分页存储管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。

因此,页式管理中地址是一维的。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中,页内偏移量占多少位。

  • 一共需要访问两次内存:第一次用来查页表,第二次用于访问目标内存单元。
  • 页式管理中地址是一维的。
  • 为了方便找到页表项,页表一般是放在连续的内存块的。
  • 一个页框可以存放的页表项就是页框大小 / 用于表示页表中内存块号所占的字节数(通常内存块号用2的整数次幂的字节表示,可以消除内存块中的页内碎片)

理论上,页表项长度为3B即可表示内存块号的范围,但是,为了方便页表的查询,常常会让一个页表项占更多的字节,使得每个页面恰好可以装得下整数个页表项。

具有快表的地址变换机构

是基本地址变换机构的改进版本!

局部性原理?

  • 时间局部性:如果执行了程序中的某条指令, 那么不久之后这条指令很有可能再次执行; 如果某个数据被访问过, 不久之后该数据很可能再次被访问。( 程序中存在大量的循环 )
  • 空间局部性:一旦程序访问了某个存储单元, 在不久之后, 其附近的存储单元也很有可能被访问到。( 很多数据在内存中连续存放 )

上小节介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项。既然如此,能否利用这个特性减少访问页表的次数呢?

什么是快表TLB?

快表又成为联想寄存器(TLB),是一种访问速度比内存块很多的高速缓冲存储器,用来存放当前访问的若干页表项, 以加速地址变换的过程. 与此对应的,内存中的页表常称为慢表

引入快表的地址变换过程?

  1. CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
  2. 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
  3. 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)。

由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。因为局部性原理,一般来说快表的命中率可以达到90%以上

基本地址变换机构 VS 具有快表的地址变换机构

两级页表

大纲

单级页表存在的问题?

1、假设某计算机系统按字节寻址,支持32位逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为4B。

4KB = 2^12B,因此页内地址要用12位表示,剩余20位表示页号。

因此,该系统中用户进程最多有2^20 页。相应的,一个进程的页表中,最多会有 2^20 个页表项,所以一个页表最大需要2^20 * 4B = 2 ^ 22B。一个页框(内存块)大小为 4B,所以需要2^22 / 2^12 (页面大小为4KB)= 2^10个页框存储该页表。而页表的存储是需要连续存储的,

因为根据页号查询页表的方法:K号页对应的页表项的位置 = 页表起始地址 + K * 4B,所以这就要求页表的存储必须是连续的。当页表很大时,需要占用很多个连续的页框

回想一下,当初为什么使用页表,就是要将进程划分为一个个页面可以不用连续的存放在内存中,但是此时页表就需要1024个连续的页框,与当时的目标相悖

2、根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存

  • 问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。(解决方法:两级页表
  • 问题二:没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面。
    • 可以在需要访问页面时才把页面调入内存(解决方法:虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页资是否已经调入内存
    • 若想访问的页面不在内存中,则产生缺页中断(内中断),然后将目标页面从外存调入内存

解决方法

可将长长的页表进行分组,使每个内存块刚好可以放入一个分组,再将各组离散地放到各个内存块中。

例子,页面大小4KB,每个页表项4B,每个页面可存放1K个页表项,因此每1K个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中!

另外,为离散分配的页表再建立一张页表,称为页目录表,或称外层页表,或称顶层页表

实现地址转换

  1. 按照地址结构将逻辑地址拆成三个部分。
  2. 从PCB中读取页目录起始地址,再根据一级页号查页目录表,找到下一级页表在内存中存放位置。
  3. 根据二级页号查表,找到最终想要访问的内存块号。
  4. 结合页内偏移量得到物理地址。

例子

细节

  • 若采用多级页表机制,则各级页表的大小不能超过一个页面
  • 两级页表的访存次数分析(假设没有快表机构)
    • 第一次访存:访问内存中的页目录表
    • 第二次访存:访问内存中的二级页表
    • 第三次访存:访问目标内存单元

例子

基本分段是存储管理

大纲

什么是分段

分段:进程的地址空间会按照自身的逻辑关系划分为若干个段, 每个段都有一个段名, 每段从0开始编址

内存分配规则以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻

分段系统的逻辑地址结构由段号(段名)和段内地址段内偏移量〉所组成。

段号的位数决定了每个进程最多可以分为几个段

段内地址位数决定了每个段的最大长度是多少

什么是段表

段表:程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表

  1. 每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度
  2. 各个段表项的长度是相同的

例如:某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号16位,段内地址16位),因此用16位即可表示最大段长。物理内存(即基址范围)大小为4GB(可用32位表示整个物理内存地址空间)。

因此,可以让每个段表项占16+32=48位,即6B。

由于段表项长度相同,因此段号可以是隐含的,不占存储空间

若段表存放的起始地址为M,则K号段对应的段表项存放的地址为 M + K * 6(即段号时隐含的!)

如何实现地址转换

分段分页管理对比
  • 页是信息的物理单位。分页的主要目的是为了实现离散分配提高内存利用率(没有页内碎片)。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
  • 段是信息的逻辑单位。分段的主要目的是更好地满足用户需求(便于编程)。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
  • 页的大小固定且由系统决定。段的长度却不固定决定于用户编写的程序
  • 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符(逻辑地址)即可表示一个地址。(各页相邻)
  • 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。(各段间不相邻)
  • 出现的原因分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。

分段比分页更容易实现信息的共享和保护。(类似指针,二者短表项一致即可同时指向共享内存)

不能被修改的代码称为纯代码可重入代码(不属于临界资源),这样的代码是可以共享的.可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)

简单来说:

  • 分页的每页大小是固定的,共享的数据不一定会被分到一个页,就会导致共享的和不允许共享的在一个页中出现!
  • 分段的每段大小是不定的,共享的数据完全可以被特殊处理到一个段,不会发生问题!

访问一个逻辑地址需要几次访存?

  • 分页(单级页表):第一次访存――查内存中的页表,第二次访存――访问目标内存单元。总共两次访存
  • 分段:第一次访存――查内存中的段表,第二次访存――访问目标内存单元。总共两次访存
  • 与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度

段页式存储管理

大纲

分页分段管理方式优缺点

分段管理中产生的外部碎片也可以用“紧凑”来解决,只是需要付出较大的时间代价

分页 + 分段 = 段页式管理

将进程按逻辑模块分段再将各段分页(如每个页面4KB)

再将内存空间分为大小相同的内存块/页框/页帧/物理块

进程前将各页面分别装入各内存块中

段页式逻辑地址结构

段号页号、页内地址(页内偏移量)组成!

分段系统的端内地址拆分为:页号 + 页内偏移

  • 段号的位数决定了每个进程最多可以分几个段
  • 页号位数决定了每个段最大有多少页
  • 页内偏移量决定了页面大小、内存块大小是多少

" 分段 " 对用户是可见的,而将各段"分页"对用户是不可见的,系统会根据段内地址自动划分页号和段内偏移量,因此段页式管理的地址结构是 " 二维 " 的。

段表页表

每一个进程对应一个段表,每一个又对应一个页表,因此一个进程可能对应多个页表。

每个对应一个段表项,每个段表项由段号页表长度页表存放块号(页表起始地址)组成。

每个段表项长度相等,段号是隐含的。

每个页面对应一个页表项,每个页表项由页号、内存存放的内存块号组成。

每个页表项长度相等,页号是隐含的。

如何实现地址转换

1、由逻辑地址得到段号、页号、页内偏移

2、段号与段表寄存器的段长度比较,检查是否越界

3、由段表始址, 段号找到对应段表项

4、根据段表中记录的页表长度,检查页号是否越界

5、由段表中的页表地址, 页号得到查询页表,找到相应页表项

6、由页面存放的内存块号,页内偏移得到最终的物理地址

7、访问目标单元

需要三次访存,第一次查段表,第二次查页表,第三次访问目标单元。

四、文件管理

1、文件管理简介

大纲

文件的属性

文件名、标识符、类型、位置、大小、创建时间、上次修改时间、文件所有者信息、保护信息

文件的分类

  • 无结构文件(流式文件):文件内部的数据是由一系列的二进制或字符流组成,如文本文件(.txt文件)

  • 有结构文件(记录式文件):由一组相似的记录文件组成,每条记录又由若干个数据项组成,如数据库表。一般来说,每条记录有一个数据项作为关键字。根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录。

记录是一组相关数据项的集合

操作系统应向上提供哪些功能

create、delete、open、close、read、write 等系统调用功能!

2、文件的逻辑结构

文件逻辑结构

  • 无结构文件(流式文件):文件内部的数据是由一系列的二进制或字符流组成,如文本文件(.txt文件)
  • 有结构文件(记录式文件):由一组相似的记录文件组成,每条记录又由若干个数据项组成,如数据库表。一般来说,每条记录有一个数据项作为关键字。根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录可变长记录
    • 顺序文件
    • 索引文件
    • 顺序索引文件

逻辑结构:指在用户看来,文件内部的数据应该是如何组织起来的。

物理结构:指的是在操作系统看来,文件的数据是如何存放在外存的。

大纲

顺序文件

顺序文件:文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储(物理上连续)或链式存储(物理上可能不连续)。

  • 串结构:记录之间的顺序与关键字无关(通常按照记录存入的时间决定记录的顺序)
  • 顺序结构:记录之间的顺序按关键字顺序排列

顺序文件(顺序结构)的缺点是增加/删除一个记录比较困难(如果是串结构则相对简单)

索引文件

解决可变长记录的快速查找问题!

索引表本身是定长记录的顺序文件。因此可以快速找到第i个记录对应的索引项。

可将关键字作为索引号内容,若按关键字顺序排列,则还可以支持按照关键字折半查找

每当要增加/删除一个记录时,需要对索引表进行修改。由于索引文件有很快的检索速度,因此主要用于对信息处理的及时性要求比较高的场合。

另外,可以用不同的数据项建立多个索引表。如:学生信息表中,可用关键字“学号”建立一张索引表。也可用“姓名”建立一张索引表。这样就可以根据“姓名”快速地检索文件了。

顺序索引文件

索引文件的缺点:每个记录对应一个索引表项,因此索引表可能会很大。比如:文件的每个记录平均只占8B,而每个索引表项占32个字节,那么索引表都要比文件内容本身大4倍,这样对存储空间的利用率就太低了。

索引顺序文件是索引文件和顺序文件思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是:并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。

若一个顺序文件有10000个记录,则根据关键字检索文件,只能从头开始顺序查找(这里指的并不是定长贡录、顺序结构的顺序文件),平均须查找5000个记录。

若采用索引顺序文件结构,可把10000个记录分为100 组,每组100个记录。则需要先顺序查找索引表找到分组(共100个分组,因此索引表长度为100,平均需要查50次),找到分组后,再在分组中顺序查找记录(每个分组100个记录,因此平均需要查50次)。可见,采用索引顺序文件结构后,平均查找次数减少为50+50= 100次。

同理,若文件共有1000000个记录,则可分为1000个分组,每个分组1000个记录。根据关键字检索一个记录平均需要查找500+500 = 1000次。这个查找次数依然很多,如何解决呢?

多级索引顺序文件

为了进一步提高检索效率,可以为顺序文件建立多级索引表。

套娃即可! 空间换时间!

3、文件目录

大纲

文件控制块

  • 目录本身就是一种有结构文件,由一条条记录组成。每条记录对应一个在该目录下的文件。
  • 目录文件中的一条记录就是一个文件控制块(FCB)
  • FCB 实现了文件名和文件之间的映射。使用户程序可以实现 ” 按名存取 “。

FCB中包含

  • 文件的基本信息( 文件名、物理地址、逻辑地址、物理结构等)
  • 存取控制信息(是否可读/可写、禁止访问的用户名单等)
  • 使用信息(如文件的建立时间、修改时间等)

对目录进行的操作:搜索、创建文件、删除文件、显示目录、修改目录

目录结构

单级目录结构

早期的操作系统并不支持多级目录,整个系统只建立一张目录表,每个文件占一个目录项。

单级目录实现了 " 按名存取 ",但是不允许文件重名。

单级目录不支持多用户操作系统。

两级目录结构

早期的多用户操作系统采用两级目录结构,分为主文件目录 (MFD,Master File Directory)和用户文件目录(UFD,User File Directory)。

  • 主文件目录记录用户名及相应用户文件目录的存放位置
  • 用户文件目录由该用户的文件FCB组成

优点:两级目录允许不同用户的文件重名,也可以在目录上实现访问限制(检查此时登录的用户名是否匹配)。

缺点:缺乏灵活性,用户不能对自己的文件进行分类。

多级目录结构

又称树形目录结构

用户(或用户进程)要访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。各级目录之间用“/”隔开。从根目录出发的路径称为绝对路径。

从当前目录出发的路径称为相对路径

系统根据绝对路径一层一层地找到下一级目录。刚开始从外存读入根目录的目录表;找到“照片”目录的存放位置后,从外存读入对应的目录表;再找到“2015-08”目录的存放位置,再从外存读入对应目录表;最后才找到文件“白拍.jpg”的存放位置。整个过程需要3次读磁盘I/O操作。

相对路径可以减少磁盘 I/O 操作次数。

优点:树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。

缺点:树形结构不便于实现文件的共享。

对此,提出了“ 无环图目录结构 ”。

无环图目录结构

在树形目录结构的基础上,增加一些指向同一节点的有向边,使整个目录成为一个有向无环图

可以更方便地实现多个用户间的文件共享

可以用不同的文件名指向同一个文件,甚至可以指向同一个目录(共享同一目录下的所有内容)。

需要为每个共享结点设置一个共享计数器,用于记录此时有多少个地方在共享该结点。用户提出删除结点的请求时,只是删除该用户的FCB、并使共享计数器减1,并不会直接删除共享结点。减到0时,才能删除该节点!

索引节点

对FCB的改进!

由来:其实在查找各级目录的过程中只需要用到“文件名”这个信息,只有文件名匹配时,才需要读出文件的其他信息。因此可以考虑让目录表“瘦身”来提升效率。

将除了文件名之外的文件描述信息都放到索引结点中。由于目录项长度减小,因此每个磁盘块可以存放更多各目录项,可以大大提升文件检索速度。

当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中的存放位置,根据“ 存放位置 ”即可找到文件。

存放在外存中的索引结点称为“磁盘索引结点”,当索引结点放入内存后称为“内存索引结点”。

相比之下内存索引结点中需要增加一些信息,比如:文件是否被修改、此时有几个进程正在访问该文件等。

4、文件的物理结构(非空闲块)

文件物理结构(文件)分配方式

即文件数据应该怎样存放在外存中。

是对非空闲块的管理!

文件块和磁盘块

磁盘块

类似于内存分页,磁盘中的存储单元也会被分为一个个 ” 块 / 磁盘块 / 物理块 “。

很多操作系统中,磁盘块的大小与内存块、页面的大小相同。

内存与磁盘之间的数据交换(即 读/写操作、磁盘I/O)都是以” “ 为单位进行的。

即每次读入一块,或每次写出一块

文件块

在内存管理中, 进程的逻辑地址空间被分为一个个的页面

同样的, 在外存管理中, 为了方便对文件数据的管理, 文件的逻辑地址空间也被分为了一个个的文件块

于是文件的逻辑地址也可以表示为 **(逻辑块号, 块内地址)**的形式

连续分配

连续分配方式要求每个文件在磁盘上占有一组连续的块。

用户给出要访问的逻辑块号,操作系统找到该文件对应的目录项(FCB) …

物理块号 = 起始块号+ 逻辑块号(就是偏移)

当然,还需要检查用户提供的逻辑块号是否合法(逻辑块号≥长度就不合法)

操作系统可以直接算出逻辑块号对应的物理块号,因此连续分配支持顺序访问和直接访问( 随机访问 )

连续分配方式要求每个文件在磁盘上占有一组连续的块

读取某个磁盘块时,需要移动磁头。访问的两个磁盘块相隔越远,移动磁头所需时间就越长。

优点:连续分配的文件在顺序读/写时速度最快

缺点:采用连续分配的文件不方便拓展;存储利用率低,会产生难以利用的磁盘碎片。( 类似外部碎片,可以采用紧凑的方法来处理碎片, 但是需要耗费很大的时间代价 )

链接分配

链接分配采取离散分配的方式,可以为文件分配离散的磁盘块。

  • 隐式链接
  • 显示链接
隐式链接

隐式链接:除文件的最后一个盘块之外,每个盘块中都存有指向下一个盘块的指针。文件目录包括文件第一块的指针最后一块的指针

实现文件的逻辑块号到物理块号的转变

用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB) …

从目录项中找到起始块号(即0号块),将0号逻辑块读入内存,由此知道1号逻辑块存放的物理块号,于是读入1号逻辑块,再找到2号逻辑块的存放位置…

以此类推。

因此,读入i号逻辑块,总共需要i+1次磁盘I/O。

优点:很方便文件拓展 (挂到文件链尾即可,可通过结束块号找到尾);所有的空闲磁盘块都可以被利用,不会有碎片问题,外存利用率高。

缺点:只支持顺序访问,不支持随机访问,查找效率低,指向下一个盘块的指针也需要耗费少量的存储空间。

显式链接

显示链接:把用于链接文件各物理块的指针显式地存放在一张表中。即文件分配表(FAT,File Allocation Table)。

  • 一个磁盘仅设置一张 FAT。
  • 开机时,将 FAT 读入内存,并常驻内存。
  • FAT 的各个表项在物理上连续存储,且每一个表项长度相同,因此 ”物理块号“ 字段可以是隐含的。

实现文件的逻辑块号到物理块号的转变

  • 用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB)
  • 从目录项中找到起始块号,若i > 0,则查询内存中的文件分配表FAT,往后找到i号逻辑块对应的物理块号。 逻辑块号转换成物理块号的过程不需要读磁盘操作。(FAT常驻内存)

结论:

  • 采用显式链接方式的文件,支持顺序访问,也支持随机访问(想访问i号逻辑块时,并不需要依次访问之前的0~ i-1号逻辑块)。
  • 由于块号转换的过程不需要访问磁盘,因此相比于隐式链接来说,访问速度快很多
  • 显式链接也不会产生外部碎片,也可以很方便地对文件进行拓展

优点:很方便文件拓展,不会有碎片问题,外存利用率高,并且支持随机访问。相比于隐式链接来说,地址转换时不需要访问磁盘,因此文件的访问效率更高。
缺点:文件分配表的需要占用一定的存储空间。

索引分配

简介

索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表一―建立逻辑页面到物理页之间的映射关系)。索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块

假设某个新创建的文件“aaa”的数据依次存放在磁盘块2 -> 5 -> 13 -> 9 。7号磁盘块作为“aaa”的索引块,索引块中保存了索引表的内容。

注:在显式链接的链式分配方式中,文件分配表FAT是一个磁盘对应一张。而索引分配方式中,索引表是一个文件对应一张

可以用固定的长度表示物理块号(如:假设磁盘总容量为1TB=2^40B,磁盘块大小为1KB,则共有 2^30 个磁盘块,则可用4B表示磁盘块号),因此,索引表中的“逻辑块号”可以是隐含的。

实现文件的逻辑块号到物理块号的转变?

  • 用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB)

  • 从目录项中可知索引表存放位置,将索引表从外存读入内存,并查找索引表即可知道 i 号逻辑块在外存中的存放位置。

可见,索引分配方式可以支持随机访问文件拓展也很容易实现(只需要给文件分配一个空闲块,并增加一个索引表项即可)。

问题解决

若每个磁盘块1KB,一个索引表项4B,则一个磁盘块只能存放256个索引项。

如果一个文件的大小超过了256块,那么一个磁盘块是装不下文件的整张索引表的,如何解决这个问题?

  • 链接方案
  • 多层索引
  • 混合索引
链接方案

如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放

多层索引

建立多层索引(原理类似于多级页表)。使第一层索引块指向第二层的索引块。还可根据文件大小的要求再建立第三层、第四层索引块。

混合索引

解决多层索引对于小文件仍需要多层处理的问题!

多种索引分配方式的结合。

例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)。

小总结
  • 链接方案:如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放。
    • 缺点:若文件很大,索引表很长,就需要将很多个索引块链接起来。想要找到i号索引块,必须先依次读入0 ~ i-1号索引块,这就导致磁盘l/O次数过多,查找效率低下。
  • 多层索引:建立多层索引(原理类似于多级页表)。使第一层索引块指向第二层的索引块。还可根据文件大小的要求再建立第三层、第四层索引块。采用K层索引结构,且顶级索引表未调入内存,则访问一个数据块只需要K +1次读磁盘操作。
    • 缺点:即使是小文件,访问一个数据块依然需要K+1次读磁盘。
  • 混合索引:多种索引分配方式的结合。例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)。
    • 优点:对于小文件来说,访问一个数据块所需的读磁盘次数更少。

重点

  • 要会根据多层索引、混合索引的结构计算出文件的最大长度(Key:各级索引表最大不能超过一个块)﹔
  • 要能自己分析访问某个数据块所需要的读磁盘次数(Key: FCB中会存有指向顶级索引块的指针,因此可以根据FCB读入顶级索引块。每次读入下一级的索引块都需要一次读磁盘操作。另外,要注意题目条件――顶级索引块是否已调入内存)

5、文件存储空间管理(空闲块)

对空闲块的管理!

大纲

存储空间划分与初始化

文件卷:将物理磁盘划分为一个个文件卷(逻辑卷,逻辑盘)例如:windows CDE盘

  • 目录区:主要存放文件目录信息(FCB),用于磁盘存储空间管理的信息
  • 文件区:存放文件数据

空闲表法

适用于连续分配方式。

空闲链表法

空闲链表法

  • 空闲盘块链:以盘块为单位组成一条空闲链
  • 空闲盘区链:以盘区为单位组成一条空闲链

空闲盘块链

适用于离散分配的物理结构。

空闲盘区链

以盘区为单位组成一条空闲链

离散分配、连续分配都使用。

为一个文件分配多个盘块时效率更高

位示图法

位示图:每个二进制位对应一个盘块。在本例中,“O”代表盘块空闲,“1”代表盘块已分配。

如何分配

若文件需要K个块

  • 顺序扫描位示图,找到K个相邻或不相邻的“0”;
  • 根据字号、位号算出对应的盘块号,将相应盘块分配给文件;
  • 将相应位设置为“1”。

如何回收

  • 根据回收的盘块号计算出对应的字号、位号;
  • 将相应二进制位设为“ 0 ”。

成组链表法

空闲表法、空闲链表法不适用于大型文件系统,因为空闲表或空闲链表可能过大。

UNIX 系统中采用了成组链接法对磁盘空闲块进行管理。

文件卷的目录区中专门用一个磁盘块作为 “超级块”,当系统启动时需要将超级块读入内存。

并且要保证内存与外存中的 “超级块” 数据一致。

成组链表法详讲:https://blog.csdn/Ajay666/article/details/73569654

6、文件基本操作

创建文件

进行 " create 系统调用 "

需要提供的参数:

  • 所需的外存空间大小(如:一个盘块,即1KB)
  • 文件存放路径(“D:/Demo”)
  • 文件名(这个地方默认为“新建文本文档.txt”)

操作系统在处理create系统调用时,主要做了两件事:

  • 在外存中找到文件所需的空间(例如使用空闲链表法、位示图等管理策略,找到空闲空间)
  • 根据文件的存放路径的信息找到该目录对应的目录文件,在目录中创建该文件对应的目录项 。目录项中包含了文件名,文件在外存中的存放位置等信息。

删除文件

进行 " delete 系统调用 "

需要提供的参数:

  • 文件存放路径(“ D:/Demo ”)
  • 文件名 (“ test.txt ”)

操作系统在处理delete系统调用时,主要做了几件事:

  • 根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的目录项
  • 根据该目录项记录的文件在外存的存放位置、文件大小等信息,回收文件占用的磁盘块。(回收磁盘块时,根据空闲表法、空闲链表法、位图法等管理策略的不同,需要做不同的处理)
  • 从目录表中删除文件对应的目录项

打开文件

进行 ” open 系统调用 “

需要提供的要参数:

  • 文件存放路径 (“D:/Demo”)
  • 文件名 (“test.txt”)
  • 要对文件的操作类型(如:r只读;rw读写等)

操作系统在处理delete系统调用时,主要做了几件事:

  • 根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的的目录项,并检查该用户是否有指定的操作权限。
  • 将目录项复制到内存中的“打开文件表”中。并将对应表目的编号返回给用户。之后用户使用打开文件表的编号来指明要操作的文件。

打开文件表

每个进程有自己的打开文件表,系统中也有一张总的打开文件表

  • 打开计数器:记录此时有多少个进程打开了此文件。
  • 读写指针:记录该进程对文件的读/写操作进行到的位置。
  • 访问权限:如果打开文件时声明的是 ” 只读 “,则该进程不能对文件进行写操作。

关闭文件

进行 ” close 系统调用 “

  • 将进程的打开文件表相应表项删除。
  • 回收分配给该文件的内存空间等资源。
  • 系统打开文件表的打开计数器 count 减1,若count = 0,则删除对应表项。

读文件

进行 ” read 系统调用 “

需要提供的参数

  • 需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可)
  • 还需要指明要读入多少数据(如:读入1KB)
  • 指明读入的数据要放在内存中的什么位置。

操作系统在处理read系统调用时,会从读指针指向的外存中,将用户指定大小的数据读入用户指定的内存区域中。

写文件

进行 ” write 系统调用 “

需要提供的参数

  • 需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可)
  • 还需要指明要写出多少数据(如:写出1KB)
  • 写回外存的数据放在内存中的什么位置

7、文件共享&文件保护

文件共享

基于索引结点的共享方式(硬链接)

索引结点:是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中。这样目录项就只需要包含文件名、索引结点指针。

索引结点中设置一个链接计数变量count,用于表示链接到本索引结点上的用户目录项数。

若 count =2,说明此时有两个用户目录项链接到该索引结点上,或者说是有两个用户在共享此文件。

若某个用户决定“ 删除 ”该文件,则只是要把用户目录中与该文件对应的目录项删除,且索引结点的 count 值减1。

若 count > 0,说明还有别的用户要使用该文件,暂时不能把文件数据删除,否则会导致指针悬空。

当 count = 0 时系统负责删除文件。

基于符号链的共享方式(软链接)

当 User3 访问 “ ccc ” 时,操作系统判断文件 “ ccc ” 属于 Link 类型文件,于是会根据其中记录的层层查找目录,最终找到 User1的目录表中的 “ aaa ” 表项,于是就找到了文件1的索引结点。(类似Windows快捷方式)

速度慢于硬链接!

文件保护

口令保护

为文件设置一个 “ 口令 ” (如: abc112233),用户请求访问该文件时必须提供 “ 口令 ”。

口令一般存放在文件对应的 FCB 或索引结点中。

用户访问文件前需要先输入“口令”,操作系统会将用户提供的口令与FCB中存储的口令进行对比。

如果正确,则允许该用户访问文件。

优点:保存口令的空间开销不多,验证口令的时间开销也很小。

缺点:正确的 “口令” 存放在系统内部,不够安全。

加密保护

使用某个 “ 密码 ” 对文件进行加密,在访问文件时需要提供正确的 “ 密码 ” 才能对文件进行正确的解密。

优点:保密性强,不需要在系统中存储“密码”

缺点:编码/译码,或者说加密/解密要花费一定时间。

访问控制

在每个文件的 FCB (或索引结点)中增加一个访问控制列表(Access-Control List, ACL),该表中记录了各个用户可以对该文件执行哪些操作。

8、文件系统层次结构

从上到下依次是:文件基本操作,文件目录,文件保护,文件逻辑地址,文件物理地址,文件存储空间管理,磁盘设备管理!

用一个例子来辅助记忆文件系统的层次结构:

假设某用户请求删除文件“D:/工作目录/学生信息…xlsx”的最后100条记录。

  1. 用户需要通过操作系统提供的接口发出上述请求――用户接口
  2. 由于用户提供的是文件的存放路径,因此需要操作系统一层一层地查找目录,找到对应的目录项――文件目录系统
  3. 不同的用户对文件有不同的操作权限,因此为了保证安全,需要检查用户是否有访问权限一存取控制模块(存取控制验证层)
  4. 验证了用户的访问权限之后,需要把用户提供的“记录号”转变为对应的逻辑地址――逻辑文件系统与文件信息缓冲区
  5. 知道了目标记录对应的逻辑地址后,还需要转换成实际的物理地址—物理文件系统
  6. 要删除这条记录,必定要对磁盘设备发出请求――设备管理程序模块
  7. 删除这些记录后,会有一些盘块空闲,因此要将这些空闲盘块回收――辅助分配模块

五、磁盘管理

1、磁盘结构

磁盘&磁道&扇区

磁盘:表面由一些磁性物质组成,可以用这些磁性物质来记录二进制数据。

磁道 : 磁盘的盘面被划分成一个个磁道, 一个圈就是一个磁道。

扇区 : 每一个磁道被划分成一个个扇区,每个扇区就是一个个 " 数据块 ",各个扇区存放的数据量相同。

最内侧磁道上的扇区面积最小, 因此数据密度最大

磁盘如何读写

需要把 “ 磁头 ” 移动到想要读/写的扇区所在的磁道。

磁盘会转起来,让目标扇区从磁头下面划过,才能完成对扇区的读/写操作。

盘面&柱面

磁盘物理地址

可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。"在“文件的物理结构”小节中,我们经常提到文件数据存放在外存中的几号块,这个块号就可以转换成(柱面号,盘面号,扇区号)的地址形式。

可根据该地址读取一个“块”

  • 根据“柱面号”移动磁臂,让磁头指向指定柱面;
  • 激活指定盘面对应的磁头;
  • 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写。

磁盘分类

磁头可以移动的称为活动头磁盘。磁臂可以来回伸缩来带动磁头定位磁道。

磁头不可移动的称为固定头磁盘。这种磁盘中每个磁道有一个磁头。


盘片可以更换的称为可换盘磁盘

盘片不可以更换的称为固定盘磁盘

2、磁盘调度算法

寻道时间&延迟时间&传输时间

寻找时间(寻道时间)Ts:在读/写数据前,将磁头移动到指定磁道所花的时间。

  • 启动磁头臂是需要时间的。假设耗时为s;
  • 移动磁头也是需要时间的。假设磁头匀速移动,每跨越一个磁道耗时为m,总共需要跨越n条磁道。则寻道时间 Ts = s + m * n

延退睦潮Tr:通过旋转磁盘,使磁头定位到目标扇区所需要的时间。设磁盘转速为r(单位:转/秒,或转/分),则平均所需的延迟时间Tr =(1/2) * (1/r) = 1/2r

传输时间Tt:从磁盘读出或向磁盘写入数据所经历的时间,假设磁盘转速为r,此次读/写的字节数为b,每个磁道上的字节数为N。则:传输时间Tt=(1/r) * (b/N) = b/(rN)

总的平均存取时间T = Ts + 1/2r + b/(rN)

延迟时间和传输时间都与磁盘转速相关,且为线性相关。而转速是硬件的固有属性,因此操作系统也无法优化延迟时间和传输时间!

但是操作系统的磁盘调度算法会直接影响寻道时间

先来先服务FCFS

根据进程请求访问磁盘的先后顺序进行调度。

优点:公平;如果请求访问的磁道比较集中的话,算法性能还算过得去。

缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。

最短寻道时间优先SSTF

SSTF 算法会优先处理的磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短,但是并不能保证总的寻道时间最短。(其实就是贪心算法的思想,只是选择眼前最优,但是总体未必最优)

优点:性能较好,平均寻道时间短。

缺点:可能产生 “ 饥饿 ” 现象。

产生饥饿的原因在于:磁头在一个小区域内来回来去地移动。

扫描算法SCAN

为了防止饥饿问题,可以规定,只有磁头移动到最外侧磁道的时候才能往内移动,移动到最内侧磁道的时候才能往外移动。

这就是扫描算法(SCAN)的思想。由于磁头移动的方式很像电梯,因此也叫电梯算法

优点:性能较好,平均寻道时间较短,不会产生饥饿现象

缺点:

  • 只有到达最边上的磁道时才能改变磁头移动方向
  • SCAN算法对于各个位置磁道的响应频率不平均

LOOK 调度算法

相对于扫描算法,如果在磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向。

边移动边观察,因此叫 LOOK。

优点:比起SCAN算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短。

循环扫描算法C-SCAN

SCAN算法对于各个位置磁道的响应频率不平均,而C-SCAN算法就是为了解决这个问题。

规定只有磁头朝某个特定方向移动时才处理磁道访问请求,而返回时直接快速移动至起始端而不处理任何请求。

优点:比起SCAN来,对于各个位置磁道的响应频率很平均。

缺点:只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了;并且,磁头返回时其实只需要返回到18号磁道即可,不需要返回到最边缘的磁道。另外,比起SCAN算法来,平均寻道时间更长。

C-LOOK 调度算法

C-SCAN 算法的主要缺点是只有到达最边上的磁道时才能改变磁头移动方向,并且磁头返回时不一定需要返回到最边缘的磁道上。

C-LOOK算法就是为了解决这个问题。如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。

优点:比起C-SCAN算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短。

3、减少磁盘延迟方法

磁头读入一个扇区数据后需要一小段时间处理,如果逻辑上相邻的扇区在物理上也相邻,

则读入几个连续的逻辑扇区,可能需要很长的“延迟时间”(因为扇片在不停的旋转,写一次转到是才可能继续去读)

交替编号

若采用交替编号的策略,即让逻辑上相邻的扇区在物理上有一定的间隔,可以使读取连续的逻辑扇区所需要的延迟时间更小。

磁盘地址结构的设计

磁盘的物理地址是 (柱面号、盘面号、扇区号)

为什么不是 (盘面号、柱面号、扇区号)?

在读取地址连续的磁盘块时,可以减少磁头移动消耗的时间。

  • 同一个柱面无需移动多次磁头
  • 若盘面号在前,则柱面号可能会变,就会造成多次移动磁头
  • 移动磁头开销太大

错位命名

让相邻盘面的扇区编号 “错位”。

即将多个盘面的磁头指向的扇区编号并不一致即可!

若指向的扇区编号一致:则0号扇面读完要读1号扇面时,1号扇面的磁头仍需要时间激活,因此就会将需要读的扇区错过,进行转动第二圈才能读到

若指向的扇区编号不一致:1号扇面磁头开始读时,由于编号不一致,即此时磁头的指向并不是要读的数据,完全有时间激活磁头,激活完毕顺便将需要读的扇区读了,转一圈即可完成!减少延迟时间!

4、磁盘的管理

磁盘初始化

  1. 进行低级格式化(物理格式化),将磁盘的各个磁道划分为扇区。一个扇区通常可分为头、数据区域(如512B大小)、尾三个部分组成。管理扇区所需要的各种数据结构一般存放在头、尾两个部分,包括扇区校验码(如奇偶校验、CRC循环冗余校验码等,校验码用于校验扇区中的数据是否发生错误)
  2. 磁盘分区,每个分区由若干柱面组成(即分为我们熟悉的c盘、D盘、E盘)。
  3. 进行逻辑格式化创建文件系统。包括创建文件系统的根目录、初始化存储空间管理所用的数据结构(如位示图、空闲分区表)

引导块

计算机开机时需要进行一系列初始化的工作,

这些初始化工作是通过执行初始化程序 (自举程序) 完成的。

初始化程序 ( 自举程序 ) 如果直接放在ROM(只读存储器)中。ROM中的数据在出厂时就写入了。并且以后不能再修改,会很不方便。

解决方法:

  • 完整的自举程序放在磁盘的启动块(即引导块/启动分区)上,启动块位于磁盘的固定位置。
  • ROM中只存放很小的 “ 自举装入程序 ”。
  • 开机时计算机先运行 “ 自举装入程序 ”,通过执行该程序就可找到引导块,并将完整的 “ 自举程序 ” 读入内存,完成初始化。
  • 拥有启动分区的磁盘称为启动磁盘或系统磁盘(C盘)

坏块管理

坏了、无法正常使用的扇区就是“坏块”。

这属于硬件故障,操作系统是无法修复的。应该将坏块标记出来,以免错误地使用到它。

对于简单的磁盘,可以在逻辑格式化时(建立文件系统时)对整个磁盘进行坏块检查,

标明哪些扇区是坏扇区,比如:在FAT表上标明。(在这种方式中,坏块对操作系统不透明)

对于复杂的磁盘,磁盘控制器(磁盘设备内部的一个硬件部件)会维护一个坏块链表

在磁盘出厂前进行低级格式化(物理格式化)时就将坏块链进行初始化。

会保留一些“备用扇区”,用于替换坏块。

这种方案称为扇区备用。且这种处理方式中,坏块对操作系统透明

六、设备管理

1、IO设备概念&分类

概念

“ I/O ” 就是“ 输入/输出 ”(lnput/Output)

I/O 设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件。

UNIX系统将外部设备抽象为一种特殊的文件, 用户可以使用与文件操作相同的方式对外部设备进行操作。

Write:向外部设备写出数据。

Read:向外部设备读入数据。

分类

按使用特性

  • 人机交互类外设:鼠标、键盘、打印机等。—— 用于人机交互 —— 数据传输速度慢
  • 存储设备:移动硬盘、光盘等。 —— 用于数据存储 —— 数据传输速度快
  • 网络通信设备:调制解调器等。 —— 用于网络通信 —— 数据传输速度介于上面两者之间

按传输速率

  • 低速设备:鼠标、键盘等 —— 传输速率为每秒几个到几百字节。
  • 中速设备:如激光打印机等 —— 传输速率为每秒数千至上万个字节。
  • 高速设备:如磁盘等 —— 传输速率为每秒数千字至千兆字节。

按信息交换的单位

  • 块设备:如磁盘等 ―― 数据传输的基本单位是 “ 块” 。—— 传输速率较高,可寻址,即对它可随机地读/写任一块。
  • 字符设备:鼠标、键盘等 ―― 数据传输的基本单位是字符。—— 传输速率较慢,不可寻址,在输入/输出时常采用中断驱动方式。

2、IO控制器

用于实现对IO设备的控制!

机械部件 vs 电子部件

IO设备有机械部件和电子部件组成!

l/O 设备的机械部件主要用来执行具体 l/O 操作。

如我们看得见摸得着的鼠标/键盘的按钮;显示器的LED屏;移动硬盘的磁臂、磁盘盘面。

l/O 设备的电子部件通常是一块插入主板扩充槽的印刷电路板。

CPU 无法直接控制 I/O 设备的机械部件,因此 I/O 设备还要有一个电子部件作为 CPU 和 I/O 设备机械部分之间的 “中介”, 用于实现CPU对设备的控制。

这个电子部件就是 I/O 控制器,又称设备控制器。

CPU可控制 I/O 控制器,又由 I/O 控制器来控制设备的机械部件。

I/O 控制器的功能

  • 接受和识别 CPU 发出的命令:如 CPU 发来的 read/write 命令,I/O 控制器中会有相应的控制寄存器来存放命令和参数。
  • 向 CPU 报告设备的状态:I/O 控制器中会有相应的状态寄存器,用于记录 I/O 设备的当前状态。如:1表示空闲,0表示忙碌。
  • 数据交换:I/O 控制器中会设置相应的数据寄存器
    • 输出时,数据寄存器用于暂存CPU发来的数据,之后再由控制器传送设备。
    • 输入时,数据寄存器用于暂存设备发来的数据,之后CPU从数据寄存器中取走数据。
  • 地址识别:类似于内存的地址,为了区分设备控制器中的各个寄存器,也需要给各个寄存器设置一个特定的“地址”。
    • I/O 控制器通过 CPU 提供的 “ 地址 ” 来判断 CPU 要读/写的是哪个寄存器。

I/O 控制器的组成

  • CPU与控制器的接口:用于实现 CPU 与控制器之间的通信,CPU 通过控制线发出命令,通过地址线指明要操作的设备,通过数据线来取出输入数据,或放入输出数据。
  • I/O逻辑:负责接收和识别 CPU 的各种命令, 并负责对设备发出命令。
  • 控制器与设备的接口:用于实现控制器与设备之间的通信。

值得注意的小细节:

  • 一个 I/O 控制器可能会对应多个设备;
  • 数据寄存器、控制寄存器、状态寄存器可能有多个(如:每个控制/状态寄存器对应一个具体的设备),且这些寄存器都要有相应的地址,才能方便CPU操作。有的计算机会让这些寄存器占用内存地址的一部分,称为内存映像 I/O;另一些计算机则采用 I/O 专用地址,即寄存器独立编址

内存印像I/O VS 寄存器独立编址

3、IO控制方式

即用什么样的方式来控制 I/O 设备的数据读/写

程序直接控制方式

通过 轮询 实现,以读操作为例。

数据传送的单位:每次读/写一个字。

CPU千预的频率

很频繁,I/O操作开始之前、完成之后需要CPU介入,并且在等待l/O完成的过程中CPU需要不断地轮询检查。

数据的流向:

  • 读操作(数据输入):I/O 设备 → CPU(指CPU的寄存器) → 内存
  • 写操作(数据输出):内存 → CPU → I/O 设备

每个字的读/写都需要CPU的帮助。

优点:实现简单。在读/写指令之后,加上实现循环检查的一系列指令即可。(因此才称为“程序直接控制方式”)

缺点:CPU 和 I/O 设备只能I/O串行工作,CPU 需要一直轮询检查,长期处于"忙等"状态,CPU 利用率低。

中断驱动方式

引入中断机制

由于 I/O 设备速度很慢,因此在 CPU 发出读/写命令后,可将等待 I/O 的进程阻塞,先切换到别的进程执行。当 I/O 完成后,控制器会向 CPU 发出一个中断信号,CPU检测到中断信号后,会保存当前进程的运行环境信息,转去执行中断处理程序处理该中断

处理中断的过程中,CPU从 I/O 控制器读一个字的数据传送到 CPU 寄存器,再写入主存。接着,CPU恢复等待 I/O 的进程(或其他进程)的运行环境,然后继续执行。

注意:

  • CPU 会在每个指令周期的末尾检查中断;
  • 中断处理过程中需要保存、恢复进程的运行环境,这个过程是需要一定时间开销的。可见,如果中断发生的频率太高,也会降低系统性能。

CPU干预的频率:

每次 I/O 操作开始之前、完成之后需要 CPU 介入。

等待 I/O 完成的过程中 CPU 可以切换到别的进程执行。

数据传送的单位:每次读/写一个字。

数据的流向:

  • 读操作(数据输入):I/O 设备 → CPU → 内存
  • 写操作(数据输出):内存 → CPU → I/O 设备

优点:与 “ 程序直接控制方式 ” 相比,在“中断驱动方式”中,I/O 控制器会通过中断信号主动报告I/O 已完成,CPU不再需要不停地轮询。CPU 和 I/O 设备可并行工作,CPU利用率得到明显提升。

缺点:每个字在 I/O 设备与内存之间的传输,都需要经过 CPU。而频繁的中断处理会消耗较多的CPU时间。

DMA方式

DMA方式(Direct Memory Access,直接存储器存取)。

主要用于块设备的 I/O 控制,相对于 “中断驱动方式 ” 有这样几个改进:

  • 数据的传送单位是 “ 块 ”。不再是一个字、一个字的传送;
  • 数据的流向是从设备直接放入内存,或者从内存直接到设备。不再需要 CPU 作为“快递小哥”
  • 仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。

DR (Data Register,数据寄存器):暂存从设备到内存,或从内存到设备的数据。

MAR (Memory Address Register,内存地址寄存器):在输入时,MAR表示数据应放到内存中的什么位置;输出时MAR表示要输出的数据放在内存中的什么位置。

DC(Data Counter,数据计数器):表示剩余要读/写的字节数。

CR (Command Register,命令/状态寄存器):用于存放 CPU 发来的 I/O 命令,或设备的状态信息。

CPU干预的频率:仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。

数据传送的单位:每次读/写一个或多个块

每次读写的只能是连续的多个块,且这些块读入内存后在内存中也必须是连续的

数据的流向:

  • 读操作(数据输入): I/O 设备 → 内存
  • 写操作(数据输出):内存 → I/O 设备

优点:数据传输以“块”为单位,CPU 介入频率进一步降低。数据的传输不再需要先经过 CPU 再写入内存,数据传输效率进一步增加。CPU 和 I/O 设备的并行性得到提升。

缺点:CPU 每发出一条 I/O 指令,只能读/写一个或多个连续的数据块。

如果要读/写多个离散存储的数据块,或者要将数据分别写到不同的内存区域时,CPU要分别发出多条 I/O 指令,进行多次中断处理才能完成。

通道控制方式

通道:一种硬件,可以理解为是 “ 弱鸡版的 CPU ”。通常可以识别并执行一系列的通道指令

与 CPU 相比,通道可以执行的指令很单一,并且通道程序是放在主机内存中的,也就是说通道与CPU 共享内存

CPU 干预的频率极低,通道会根据 CPU 的指示执行相应的通道程序,只有完成一组数据块的读/写后才需要发出中断信号,请求 CPU 干预。

数据传送的单位:每次读/写一组数据块。

数据的流向〈在通道的控制下进行)

  • 读操作(数据输入):I/O 设备 → 内存
  • 写操作(数据输出):内存 → I/O 设备

优点:CPU、通道、I/O 设备可并行工作,资源利用率很高。

缺点:实现复杂,需要专门的通道硬件支持。

小总结

4、IO软件层次结构

用户层软件:实现与用户交互的接口,向上提供方便易用的库函数。

设备独立性软件

  • 向上层提供统一的调用接口(如read/write系统调用);
  • 设备的保护:原理类似于文件保护。设备被看做是一种特殊的文件,不同用户对各个文件的访问权限是不一样的,同理,对设备的访问权限也不一样。
  • 差错处理:设备独立性软件需要对一些设备的错误进行处理
  • 设备的分配与回收
  • 数据缓冲区管理:可以通过缓冲技术屏蔽设备之间数据交换单位大小和传输速度的差异
  • 建立逻辑设备名到物理设备名的映射关系:根据设备类型选择调用相应的驱动程序
    • 用户或用户层软件发出I/O操作相关系统调用的系统调用时,需要指明此次要操作的I/O设备的逻辑设备名
    • 设备独立性软件需要通过“逻辑设备表(LUT,Logical UnitTable)”来确定逻辑设备对应的物理设备,并找到该设备对应的设备驱动程序

设备驱动程序:主要负责对硬件设备的具体控制,将上层发出的一系列命令(如read/write)转化成特定设备“能听得懂”的一系列操作。包括设置设备寄存器,检查设备状态等

中断处理程序:进行中断处理。当I/O任务完成时,I/O控制器会发送一个中断信号,系统会根据中断信号类型找到相应的中断处理程序并执行。中断处理程序的处理流程如下:

硬件:执行 I/O 操作,有机械部分、电子部分组成。不同的I/O设备有不同的硬件特性,具体细节只有设备的厂家才知道。因此厂家需要根据设备的硬件特性设计并提供相应的驱动程序。

操作系统系统可以采用两种方式管理逻辑设备表(LUT) :

  • 第一种方式,整个系统只设置一张LUT,这就意味着所有用户不能使用相同的逻辑设备名,因此这种方式只适用于单用户操作系统。
  • 第二种方式,为每个用户设置一张LUT,各个用户使用的逻辑设备名可以重复,适用于多用户操作系统。系统会在用户登录时为其建立一个用户管理进程,而LUT就存放在用户管理进程的PCB中。

5、IO核心子系统

I/O 核心子系统要实现的功能其实就是中间三层要实现的功能。

I/O调度、设备保护、假脱机技术(SPOOLing技术)、设备分配与回收、缓冲区管理(即缓冲与高速缓存)

I/O调度&设备保护

I/O 调度:用某种算法确定一个好的顺序来处理各个 I/O 请求。

如:磁盘调度(先来先服务算法、最短寻道优先算法、SCAN算法、C-SCAN算法、LOOK算法、C-LOOK算法)。当多个磁盘I/O请求到来时,用某种调度算法确定满足l/O请求的顺序。

同理,打印机等设备也可以用先来先服务算法、优先级算法、短作业优先等算法来确定I/O调度顺序。

设备保护:

操作系统需要实现文件保护功能,不同的用户对各个文件有不同的访问权限。

在 UNIX 系统中,设备被看做是一种特殊的文件每个设备也会有对应的FCB。

当用户请求访问某个设备时,系统根据FCB中记录的信息来判断该用户是否有相应的访问权限,以此实现“设备保护”的功能。(参考 文件保护 小结)

假脱机技术 SPOOLing

脱机技术

手工操作阶段:主机直接从I/O设备获得数据,由于设备速度慢,主机速度很快。人机速度矛盾明显,主机要浪费很多时间来等待设备

批处理阶段引入了脱机输入/输出技术(用磁带完成)

引入脱机技术后,缓解了CPU与慢速I/O设备的速度矛盾。另一方面,即使CPU在忙碌,也可以提前将数据输入到磁带;即使慢速的输出设备正在忙碌,也可以提前将数据输出到磁带。

脱机:脱离主机的控制进行输入输出操作!

假脱机技术

“假脱机技术”,又称“SPOOLing 技术”,用软件的方式模拟脱机技术。SPOOLing 系统的组成如下:

  • 输入井:模拟脱机输入时的磁带,用于收容I/O设备输入的数据
  • 输出井:模拟脱机输出时的磁带,用于收容用户进程输出的数据
  • 输入进程:模拟脱机输入时的外围控制机
  • 输出进程:模拟脱机输入时的外围控制机
  • 输入缓冲区:在输入进程的控制下,“输入缓冲区”用于暂存从输入设备输入的数据,之后再转存到输入井中
  • 输出缓冲区:在输出进程的控制下,“输出缓冲区”用于暂存从输出井送来的数据,之后再传送到输出设备上
  • 内存:输入缓冲区和输出缓冲区是在内存中的缓冲区
  • 磁盘:在磁盘上开辟出两个存储区域――“输入井”和“输出井”

要实现SPOOLing 技术,必须要有多道程序技术的支持。系统会建立“输入进程”和“输出进程”。

共享打印机器原理

独占式设备:只允许各个进程串行使用的设备。一段时间内只能满足一个进程的请求。

共享设备:允许多个进程“同时”使用的设备(宏观上同时使用,微观上可能是交替使用)。可以同时满足多个进程的使用请求。

打印机是一种 " 独占式设备 ",但是可以用SPOOLing技术改造成 " 共享设备 "。

独占式设备的例子:若进程1正在使用打印机,则进程2请求使用打印机时必然阻塞等待

当多个用户进程提出输出打印的请求时,系统会答应它们的请求,但是并不是真正把打印机分配给他们而是由假脱机管理进程为每个进程做两件事:

  1. 磁盘输出井中为进程申请一个空闲缓冲区(也就是说,这个缓冲区是在磁盘上的),并将要打印的数据送入其中;
  2. 为用户进程申请一张空白的打印请求表,并将用户的打印请求填入表中(其实就是用来说明用户的打印数据存放位置等信息的),再将该表挂到假脱机文件队列上。

当打印机空闲时,输出进程会从文件队列的队头取出一张打印请求表,并根据表中的要求将要打印的数据从输出井传送到输出缓冲区,再输出到打印机进行打印。用这种方式可依次处理完全部的打印任务

虽然系统中只有一个台打印机,但每个进程提出打印请求时,系统都会为在输出井中为其分配一个存储区(相当于分配了一个逻辑设备),使每个用户进程都觉得自己在独占一台打印机,从而实现对打印机的共享。

设备的分配与回收

大纲

设备分配要考虑因素

设备的固有属性可分为三种:独占设备、共享设备、虚拟设备。

  • 独占设备:一个时段只能分配给一个进程(如打印机)
  • 共享设备:可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上交替使用。
  • 虚拟设备:采用SPOOLing技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用SPOOLing技术实现的共享打印机)

设备分配算法:

  • 先来先服务
  • 优先级高者优先
  • 短任务优先

从进程运行的安全性上考虑,设备分配有两种方式:

1、安全分配方式:为进程分配一个设备后就将进程阻塞,本次 I/O 完成后才将进程唤醒。

一个时段内每个进程只能使用一个设备

优点:破坏了 “ 请求和保持 ” 条件,不会死锁。

缺点:对于一个进程来说,CPU 和 I/O 设备只能串行工作。

2、不安全分配方式:进程发出 I/O 请求后,系统为其分配 I/O 设备,进程可继续执行,之后还可以发出新的 I/O 请求。只有某个 I/O 请求得不到满足时才将进程阻塞。

一个进程可以同时使用多个设备

优点:进程的计算任务和 I/O 任务可以并行处理,使进程迅速推进。

缺点:有可能发生死锁(死锁避免、死锁的检测和解除)。

静态分配&动态分配

静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源。破坏了“请求和保持”条件,不会发生死锁

动态分配:进程运行过程中动态申请设备资源

设备分配管理数据结构

设备、控制器、通道之间的关系

一个通道可控制多个设备控制器,每个设备控制器可控制多个设备。

设备控制表(DCT)

系统为每个设备配置一张DCT,用于记录设备情况。

控制器控制表(COCT)

每个设备控制器都会对应一张COCT。操作系统根据COCT的信息对控制器进行操作和管理。

通道控制表(CHCT)

通道控制表(CHCT):每个通道都会对应一张CHCT。操作系统根据CHCT的信息对通道进行操作和管理。

系统设备表(SDT)

记录了系统中全部设备的情况,每个设备对应一个表目。

设备分配步骤
  1. 根据进程请求的物理设备名查找SDT。(注:物理设备名是进程请求分配设备时提供的参数)
  2. 根据SDT找到DCT,若设备忙碌则将进程PCB挂到设备等待队列中,不忙碌则将设备分配给进程。
  3. 根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程。
  4. 根据COCT找到CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程。

缺点:

  • 用户编程时必须使用 “ 物理设备名 ”,底层细节对用户不透明,不方便编程。
  • 若换了一个物理设备,则程序无法运行。
  • 若进程请求的物理设备正在忙碌,则即使系统中还有同类型的设备,进程也必须阻塞等待。
设备分配步骤改进方法

建立逻辑设备名与物理设备名的映射机制,用户编程时只需提供逻辑设备名。

某用户进程第一次使用设备时使用逻辑设备名向操作系统发出请求,操作系统根据用户进程指定的设备类型(逻辑设备名)查找系统设备表,找到一个空闲设备分配给进程,并在LUT中增加相应表项。

如果之后用户进程再次通过相同的逻辑设备名请求使用设备,则操作系统通过LUT表即可知道用户进程实际要使用的是哪个物理设备了,并且也能知道该设备的驱动程序入口地址。

逻辑设备表的设置问题:

整个系统只有一张LUT:各用户所用的逻辑设备名不允许重复,适用于单用户操作系统

每个用户一张LUT:不同用户的逻辑设备名可重复,适用于多用户操作系统

缓冲区管理

大纲

什么是缓冲区

缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可以利用内存作为缓冲区。

使用硬件作为缓冲区的成本较高,容量较小, 一般仅用在对速度要求非常高的场合。

比如联想寄存器 ( 快表 ) 就是硬件作为缓冲区。

一般情况下, 更多的是利用内存作为缓冲区

缓冲区的作用:

  • 缓和CPU与I/O设备之间速度不匹配的矛盾
  • 减少对CPU的中断频率,放宽对CPU中断相应时间的限制
  • 解决数据粒度不匹配的问题
  • 提高CPU与I/O设备之间的并行性
单缓冲

假设某用户进程请求某种块设备读入若干块的数据。若采用单缓冲的策略,操作系统会在主存中为其分配一个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)。

当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出

当缓冲区为时,可以往缓冲区冲入数据,但必须把缓冲区充满以后,才能从缓冲区把数据传出。

双缓冲

假设某用户进程请求某种块设备读入若干块的数据。若采用双缓冲的策略,操作系统会在主存中为其分配两个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)。

结论:采用双缓冲策略,处理一个数据块的平均耗时为Max (T, C + M)

注:管道通信中的“管道”其实就是缓冲区。要实现数据的双向传输,必须设置两个管道

循环缓冲

将多个大小相等的缓冲区链接成一个循环队列

注:以下图示中,橙色表示已充满数据的缓冲区,绿色表示空缓冲区。

缓冲池

缓冲池由系统中共用的缓冲区组成。

这些缓冲区按使用状况可以分为:

  • 空缓冲队列
  • 装满输入数据的缓冲队列(输入队列)
  • 装满输出数据的缓冲队列(输出队列)。

根据一个缓冲区在实际运算中扮演的功能不同,又设置了四种工作缓冲区:

  • 用于收容输入数据的工作缓冲区(hin)
  • 用于提取输入数据的工作缓冲区(sin)
  • 用于收容输出数据的工作级冲区( hout)
  • 用于提取输出数据的工作缓冲区(sout)。

  1. 输入进程请求输入数据:从空缓冲队列中取出一块作为收容输入数据的工作缓冲区(hin)。冲满数据后将缓冲区挂到输入队列队尾。
  2. 计算进程想要取得一块输入数据:从输入队列中取得一块冲满输入数据的缓冲区作为“提取输入数据的工作缓冲区(sin)”。缓冲区读空后挂到空缓冲区队列。
  3. 计算进程想要将准备好的数据冲入缓冲区:从空缓冲队列中取出一块作为“收容输出数据的工作缓冲区(hout)”。数据冲满后将缓冲区挂到输出队列队尾。
  4. 输出进程请求输出数据:从输出队列中取得一块冲满输出数据的缓冲区作为“提取输出数据的工作缓冲区( sout) ”。缓冲区读空后挂到空缓冲区队列

本文标签: 操作系统 计算机 详细