王道考研---操作系统原理笔记
- 第一章---操作系统概述
- 第二章---进程
- 2.1.1. 进程定义, 组成, 组织方式, 特征
- 2.1.2. 进程的状态和转换
- 2.1.3. 进程控制
- 2.1.4. 进程通信
- 2.1.5. 线程与多线程
- 2.2.1. 处理机调度的概念层次
- *2.2.2. 进程调度的时机, 切换过程, 方式
- 2.2.3. 调度算法的评价指标
- 2.2.4. 批处理调度算法: FCFS, SJF, HRRN
- 2.2.5. 交互式系统调度算法: 时间片轮转(RR), 优先级, 多级反馈队列
- 2.3.1. 进程同步, 互斥
- 2.3.2. 进程互斥的软件实现方法
- 2.3.3. 进程互斥的硬件实现方法
- 2.3.4. 信号量机制
- 2.3.5. 信号量实现进程互斥, 同步, 前驱关系
- 2.3.6. --- 2.3.10. 信号量解决同步互斥问题示例
- 2.3.11. 管程
- 2.4.1. 死锁的概念
- 2.4.2 死锁处理策略---预防死锁
- 2.4.3. 死锁处理策略---避免死锁
- 2.4.4. 死锁处理策略---检测与解除
- 第三章---内存管理
- 第四章---文件管理
- 第五章---I/O设备管理
第一章---操作系统概述
*1.1. 操作系统概念, 功能, 目标
操作系统(Operating System, OS)是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配
;以提供给用户和其他软件方便的接口和环境;它是计算机系统中最基本的系统软件。
- 脱机命令 = 批处理式命令
- 联机命令 = 交互式命令
- 内部命令:由系统定义的,常驻内存的处理程序集合
操作系统层次结构
- 用户可以与操作系统直接命令交互, 也可以通过操作系统提供的程序接口与操作系统进行交互
1.2. 操作系统四个特征: 并发, 共享, 虚拟, 异步
- 并发性: 指计算机系统中同时存在着多个运行着的程序
- 并发: 指两个或多个事件在同一时间间隔内发生。这些事件宏观上是同时发生的,但微观上是交替发生的
- 并行: 指两个或多个事件在同一时刻同时发生
- 共享性: 指系统中的资源可供内存中多个并发执行的进程共同使用
- 虚拟性:
- 空分复用(虚拟存储器)
4G内存电脑拥有,同时运行超过4G运行空间的程序
- 时分复用(虚拟处理器)
CPU时间片轮转
- 空分复用(虚拟存储器)
- 异步性: 在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。
并发与共享相辅相存, 前者缺一不可, 否则虚拟性与异步性显得毫无意义.
1.3. 操作系统发展与分类
手工操作
- 缺点:用户独占全机, 人机速度矛盾致资源利用率低
单批道处理
引入脱机输入/输出技术, 并由 监督程序 负责控制作业的输入输出
- 优点:
缓解了一定程度的人机速度矛盾, 资源利用率有所提升 - 缺点:
内存中仅能 有一道程序运行,只有 该程序运行结束之后才 能调入下一道程序。CPU有大量的时间是在 空闲等待I/O完成。资源 利用率依然很低。
分时操作系统
计算机以时间片为单位轮流为各个用户/作业服务,各个用户可通过终端与计算机进行交互
- 优点:
用户请求可以被即时响应,解决了人机交互问题。允许多个用户同时使用一台计算机,并且用户对计算机的操作相互独立,感受不到别人的存在 - 缺点:
不能优先处理一些紧急任务。操作系统对各个用户/作业都是完全公平的,循环地为每个用户/ 作业服务一个时间片,不区分任务的紧急性 - 特征:
- 多路性,独立性,交互性,及时性
实时操作系统
在实时操作系统的控制下,计算机系统接收到外部信号后及时进行处理,
并且要在严格的时限内处理完事件。实时操作系统的主要特点是及时性和可靠性
- 优点:
能够优先响应一些紧急任务,某些紧急任务不需时间片排 - 分类:
- 硬实时系统, 必须在绝对严格规定时间内完成
- 软实时系统, 接受偶尔违反时间规定
除了以上, 还有网络操作系统, 分布式操作系统, 分布式操作系统的主要特点是 分布性和并行性, 任何工作都可以分布在这些计算机上, 由它们并行, 协同完成这个任务
1.4. OS的运行机制和体系结构
指令与操作系统状态
指令: 处理器能识别, 执行的最基本命令
- 特权指令: 不允许用户使用
- 非特权指令
两种处理器状态, 用程序状态寄存器 (PSW) 标记, 0 用户, 1 核心
- 用户态(目态): CPU 只能执行非特权指令
- 核心态(管态): 特权指令, 非特权指令都可以执行
两种程序
- 内核程序: 系统的管理, 两种指令都可以执行, 在核心态运行
- 应用程序: 只能运行非特权指令, 在用户态执行
操作系统内核
内核是计算机上配置的底层软件, 是操作系统最基本, 最核心的部分;
实现操作系统内核功能的那些程序就是内核程序
内核功能
-
接近硬件
- 时钟管理, 中断处理, 原语(设备驱动, CPU 切换)
- 原语最接近硬件的部分, 这种程序的运行具有原子性, 运行短, 调用频繁
- 时钟管理, 中断处理, 原语(设备驱动, CPU 切换)
-
接近上层
- 进程管理, 存储器管理, 设备管理等... (不同操作系统划分不同)
操作系统体系结构
分为 大内核与微内核.
- 大内核: 将操作系统的主要功能模块都作为系统内核, 运行在核心态
- 优点: 高性能
- 缺点: 内核代码庞大, 结构混乱, 难维护
- 微内核: 只把最基本的功能保留在内核
- 优点: 内核功能少, 结构清晰, 好维护
- 缺点: 需要频繁在内核态和用户态间切换, 性能低
1.5. 中断和异常
引入中断机制, 实现了多道程序并发执行
本质: 发生中断就意味着操作系统需要介入,展开管理工作
- 当中断发生时,CPU立即进入核心态
- 当中断发生后,当前运行的进程暂停运行,并由操作系统内核对中断进行处理
- 对于不同的中断信号,会进行不同的处理
问题: 用户态, 核心态之间是怎么切换的?
- 切换是通过中断实现的, 并且是唯一途径, 改变 程序状态字(PSW) 即可
中断分类
- 内中断 (异常, 例外, 陷入), 来自 CPU内部与当前执行指令有关
- 陷阱, 陷入 (trap)
系统调用时使用的访管指令
- 故障 (fault)
- 硬件故障 --- 缺页
- 终止 (abort)
- 软件故障 --- 除0, 不可恢复的致命错误
- 陷阱, 陷入 (trap)
- 外中断 (中断), 来自 CPU 外部 , 与当前执行的指令无关
- 外设请求 --- I/O操作完成发出信号
- 人工干预 --- 用户强行终止进程, kill
外中断的处理
- 每执行完指令, CPU要检查有无外部中断
- 保存上下文
- 根据中断类型转入相应的中断处理程序
- 恢复原进程的CPU环境并退出中断返回原进程继续往下执行
1.6. 系统调用
操作系统给用户的程序接口, 就是由一组系统调用程序组成,
应用程序通过系统调用请求操作系统的服务。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序
中,凡是与资源有关的操作(如存储分配、V/o操作、文件管理等),都必须通过系统调用的方式向操作系统提
出服务请求,由操作系统代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。
系统调用相关处理在 核心态 处理
应用程序既可以直接进行系统调用, 也可以调用库函数进行系统调用
调用流程
- 传递系统调用参数
- 执行陷入指令(用户态)
- 陷入指令在用户态执行, 执行陷入指令后立即引发内中断, 从而 CPU 进入核心态
- 发出请求在用户态, 相应处理在核心态
- 是唯一一个只能在用户态执行的指令, 核心态不可执行
- 执行系统调用相应服务程序(核心态)
- 返回用户程序
第二章---进程
2.1.1. 进程定义, 组成, 组织方式, 特征
进程定义
程序: 一个指令序列
程序段、数据段、PCB三部分组成了进程实体(进程映像), 一般情況下,我们把*进程实体就简称为进程,
例如,所谓创建进程,实质上是创建进程实体中的PCB;而撤销进程,实质上是撤销进程实体中的PCB。
注意:PCB是进程存在的唯一标志!
不同角度看待进程:(强调进程的动态性)
- 进程是程序的一次执行过程。
- 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
- 进程是具有独立功能的程序在数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位
引入进程实体的概念后,可把进程定义为:
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
除非特别区分, 可以认为进程就是进程实体
进程组成
- PCB (存放操作系统管理进程的数据)
- 进程描述信息
- PID, 唯一不重复, 进程标识符
- UID, 用户标识符
- 进程控制和管理信息
- 当前状态
- 进程优先级
- 资源分配清单
- 程序段 ptr
- 数据段 ptr
- 键盘, 鼠标等
- 处理机相关信息
- 各种寄存器的值, 记录进程当前运行情况, 如PC值执行到哪一个了
- 进程描述信息
- 程序段: 存放需要执行的代码
- 数据段: 存放程序运行过程中处理的各种数据
进程组织
- 链接方式
- 按照进程状态将 PCB 分成多个队列
- 操作系统有指向各队列的指针
- 索引方式
- 根据进程状态的不同, 建立几张索引表
- 操作系统持有指向各个索引表的指针
进程的特征
- 动态性: 进程是程序的一次执行过程,是动态地产生、变化和消亡的
- 并发性: 内存中有多个进程实体,各进程可井发执行
- 独立性: 进程是能独立运行、独立获得资源、独立接受调度的基本单位
- 进程是资源分配、接受调度的基本, 独立单位
- 异步性: 各进程按各自独立的、不可预知的速度向前推进,操作系统要提供"进程同步机制"来解决异步问题
- 结构性: 每个进程都会配置一个PCB。结构上看,进程由PCB、程序段、数据段组成
2.1.2. 进程的状态和转换
进程状态
- 创建态 (New), 进程正在被创建,操作系统为进程分配资源、初始化PCB
- 运行态 (Running), 占有CPU并在CPU上执行
- 就绪态 (Ready), 已经具备运行条件, 没有空闲CPU 还没有运行
- 阻塞态 (Wating/Blocking), 因等待某一时间暂时不能运行
- 终止态 (Terminated), 进程正在从系统中撤销,操作系统会回收进程拥有的资源、撒销PCB
进程转换
2.1.3. 进程控制
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销己有进程、实现进程状态转换等功能。
问题: 如何保证 PSW 的修改不被抢占?
回答: 用原语实现进程控制。原语的特点是执行期问不允许中断,只能一气呵成,这种不可被中断的操作即原子操作,原语采用“关中断指令”和“开中断指令”实现
// ...
关中断指令
原语 code 1
原语 code 2
开中断指令
// ...,
显然,关/开中断指令的权限非常大,必然是只允许在核心态下执行的特权指令
进程控制原语核心:
- 更新PCB中的信息(如修改进程状态标志、将运行环境保存到PCB、从PCB恢复运行环境)
- 所有的进程控制原语一定都会修改进程状态标志
- 剥夺当前运行进程的CPU使用权必然需要保存其运行环境
- 某进程开始运行前必然要恢复期运行环境
- 将PCB插入合适的队列
- 分配/回收资源
进程创建
- 创建原语
- 申请空白PCB
- 为新进程分配所需资源
- 初始化PCB
- 将PCB插入就绪队列
- 引起进程创建的时间
- 用户登录 --- 分时系统中,用户登录成功,系统会建立为其建立一个新的进程
- 作业调度 --- 多道批处理系统中,有新的作业放入内存时,会为其建立一个新的进程
- 提供服务 --- 用户向操作系统提出某些请求时,会新建一个进程处理该请求
- 应用请求 --- 由用户进程主动请求创建一个子进程
进程终止
- 撤销原语
- 从PCB集合中找到终止进程的PCB
- 若进程正在运行,立即剥夺CPU,将CPU分配给其他进程
- 终止其所有子进程
- 将该进程拥有的所有资源归还给父迸程或操作系统
- 删除PCB
- 引起进程终止的事件
- 正常结束
- 异常结束
- 外泉干预
进程的阻塞和唤醒
进程的阻塞和唤醒原语应当成对使用
- 进程的阻塞
- 阻塞原语
- 找到要阻塞的进程对应的PCB
- 保护进程运行现场,将PCB状态信息设置为“阻塞态",暂时停止进程运行
- 将PCB插入相应事件的等待队列
- 引起进程阻塞的事件
- 需要等待系统分配某种资源
- 需要等待相互合作的其他进程完成工作
- 阻塞原语
- 迸程的唤醒
- 唤醒原语
- 在事件等待队列中找到PCB
- 将PCB从等待队列移除,设置进程为就绪态
- 将PCB插入就绪队列,等待被调度
- 引起进程唤醒的事件
- 等待的事件发生
- 唤醒原语
进程的切换
- 切换原语
- 将运行环境信息存入PCB
- PCB移入相应队列
- 选择另一个进程执行,并更新其PCB
- 根据PCB恢复新进程所需的运行环境
- 引起进程切换的事件
- 当前进程时间片到
- 有更高优先级的进程到达
- 当前迸程主动阻塞
- 当前进程终止
2.1.4. 进程通信
顾名思义,进程通信就是指进程之间的信息交换。
进程是分配系统资源的单位 (包括内存地址空间),因此各进程拥有的内存地址空间相互独立。
为了保证安全,一个进程不能直接访问另一个进程的地址空间。但是进程之间的信息交换又是必须实现的。为了保证进程间的安全通信,操作系统提供了一些方法。
共享存储
两个进程对共享空间的访问必须是互斥的(互斥访问通过操作系统提供的工具实现)
- 基于数据结构
- 比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
- 基于存储区
- 在内存中画出一块共享存储区,数据的形式、存放位置都由进程控制而不是操作系统。相比之下,这种共享方式速度更快,是一种高级通信方式。
管道通信
“管道”是指用于连接读写进程的一个共享文件,又名pipe文件。其实就是在内存中开辟个大小固定的缓冲区
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
- 各进程要互斥地访问管道
- 数据以宇符流的形式与入管道
- 当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。
- 当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
- 数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。
消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。
-
消息结构
- 消息头, 包括:发送进程ID、接受进程ID、消息类型、消息长度等格式化信息
- 消息体
-
传递方式
- 直接传递, 消息直接挂到接收进程的消息缓冲队列上
- 间接传递, 消息要先发送到中间实体(信箱)中,因此也称“信箱通信方式”。Eg:计网中的电子邮件系统
2.1.5. 线程与多线程
可以把线程理解为“轻量级进程”
线程基础
线程是一个基本的CPU执行单元,也是程序执行流的最小单位。
引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
引入线程后,进程只作为除CPU之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)
即, 进程是资源分配的基本单位, 线程是调度的基本单位
- 每个线程有一个线程ID, 线程控制块 (TCB)
- 同一线程的切换, 不需要切换进程环境
- 线程几乎不拥有系统资源
- 同一线程内进程共享进程的资源, 并且通信无需系统干预
线程实现
- 用户级线程 (User-Level Thread, ULT)
- 用户级线程由应用程序通过线程库实现。
- 所有的线程管理工作都由应用程序负责(包括线程切换)
- 用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预。
- 在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。(用户级线程对用户不透明,对操作系统透明)
- 内核级线程 (Kernel-Level Thread, KLT, 内核支持的线程)
- 内核级线程的管理工作由操作系统内核完成。
- 线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
在同时支持 ULT 和 KLT 的 OS 中, 可以用 \(n\) 个 ULT 映射到 \(m\) 个 KLT 上, \(n \geq m\)
重点: 只有内核级线程才是处理机分配的单位
多线程模型
- 多对一模型:
- 多个用户及线程映射到一个内核级线程。每个用户进程只对应一个内核级线程。
- 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
- 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行
- 一对一模型:
- 一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。
- 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
- 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
- 多对多模型:
- \(n\) 用户及线程映射到 \(m\) 个内核级线程 \((n\geq m)\)。每个用户进程对应 \(m\) 个内核级线程。
- 克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
2.2.1. 处理机调度的概念层次
调度的三个层次
高级调度(作业调度)
只关心如何调入, 结束后自动调出
- 由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序
- 按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业给他们分配内存等必要资源,
并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。 - 高级调度是辅存(外存)与内存之问的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。
高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。
中级调度(内存调度)
- 就是要决定将哪个处于挂起状态的进程重新调入内存
一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高。 - 引入了虚拟存储技术之后,可将暂时不能运行的进程调至外存等待。等它重新具备了运行条件且内存又稍有空闲时,再重新调入内存
- 这么做的目的是为了提高内存利用率和系统吞吐量。
- 暂时调到外存等待的进程状态为挂起状态。值得注意的是,PCB并不会,
PCB并不会一起调到外存,而是会常驻内存。- PCB中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的PCB
来保持对各个进程的监控、管理。被挂起的进程PCB会被放到的挂起队列中。
- PCB中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的PCB
低级调度(进程调度)
- 其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它
- 讲程调度是操作系统中 最基本的一种调度, 在一般的操作系统中都必须配置进程调度。
- 进程调度的频率很高,一般几十毫秒一次。
七状态模型及三种调度的联系对比
*2.2.2. 进程调度的时机, 切换过程, 方式
进程调度时机
- 主动放弃
- 进程正常终止
- 运行过程中发生异常而终止
- 进程主动请求阻塞 (i/o)
- 被动放弃
- 分给进程的时间片用完
- 有更紧急的事要处理 (I/O)
- 有更高优先级的进程进入队列
- 不能进行
- 处理中断时
- 进程在操作系统内核程序临界区中
- 在原子操作中
临界资源: 一个时间段只允许一个进程使用, 进程需要互斥访问
临界区: 访问临界资源的那段代码
内核程序临界区一般用来访问某种内核数据结构 , 比如进程的就绪队列
进程在访问普通临界区时可以进行进程调度与切换
进程调度方式
-
非剥夺调度方式,又称非抢占方式。
- 即,只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。
-
剥夺调度方式,又称抢占方式。
- 当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程,
时间片
- 当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程,
进程切换与过程
狭义的进程调度”与“进程切换”的区别:
- 狭义的进程调度指的是从就绪队列中选中一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,
也可能是另一个进程,后一种情况就需要进程切换) - 进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程
广义的进程调度包含了选择一个进程和进程切换两个步骤
进程切换的过程主要完成了:
- 对原来运行进程各种数据的保存
- 对新的进程各种数据的恢复
程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)
注意:进程切换是有代价的,因此如果过于频繁的进行进程调度、切换,必然会使整个系统的效率降低,使系统大部分时间都花在了进程切换上,而真正用于执行进程的时间减少
2.2.3. 调度算法的评价指标
CPU利用率
CPU 利用率 = \(\frac{忙碌时间}{总时间}\)
系统吞吐量
单位时间内完成作业的数量
系统吞吐量 = \(\frac{总共完成多少作业}{总共花了多少时间}\)
周转时间
指从作业被提交给系统开始, 到作业完成为止这段时间间隔
包括四个部分
- 作业在外存后备队列上等待作业调度(高级调度)的时间、
- 进程在就绪队列上等待进程调度(低级调度)的时间、
- 进程在CPU上执行的时间、
- 进程等待I/O操作完成的时间。
后三项在一个作业的整个处理过程中,可能发生多次。
作业周转时间 = \(作业完成时间-作业提交时间\)
平均周转时间 = \(\frac{各作业周转时间之和}{作业数}\)
带权周转时间 = \(\frac{作业周转时间}{作业实际运行时间}\)
平均带权周转时间 = \(\frac{各作业带权周转时间之和}{作业数}\)
等待时间
对于进程, 就是在处理机上等待时间总和
对于作业, 还要计算上作业在外存等待队列上等待的时间
响应时间
从用户提交请求到首次产生响应所产生的时间
2.2.4. 批处理调度算法: FCFS, SJF, HRRN
学习技巧:
- 算法思想
- 算法规则
- 用于作业调度还是进程调度?
- 抢占式? 非抢占式?
- 优点和缺点
- 是否导致饥饿(某进程/作业长期得不到服务)
先来先服务 (FCFS)
First Come First Serve
- 思想: 先来先得
- 规则: 按照到达先后顺序服务
- 作业/进程调度: 都能使用, 作业调度考虑谁先到后背队列, 进程调度考虑谁先到就绪队列
- 抢占: 非抢占式
- 优点: 公平, 实现简单
- 缺点: 对长作业有利, 短作业不利
- 饥饿: 不会导致饥饿
等待时间 = 周转时间 - 运行时间 - I/O 操作时间
短作业优先 (SJF), 最短剩余时间优先 (SRTN)
SJF, Shortest Job First
- 思想: 追求最少的平均等待时间, 平均周转时间, 平均带权周转时间
- 规则: 最短的作业/进程优先得到服务(服务时间最短)
- 作业/进程调度: 两者都可以, 用于进程时称为 "短进程优先 (SPF, Shortest Process First)" 算法
- 抢占: 默认非抢占式, 也有抢占式---最短剩余时间优先算法 (SRTN, Shortest Remaining Time Next)
- 优点: "最短" 平均等待时间, 周转时间
- 缺点: 对短作业有利, 长作业不利
- 饥饿: 会发生饥饿
最短剩余时间优先算法(SRTN)
每当有进程加入就绪队列改变时就需要调度,如果新到达的进程剩余时间比当前运行的进程剩余时间更短,
则由新进程抢占处理机,当前运行进程重新回到就绪队列。另外,当一个进程完成时也需要调度
SJF, 默认非抢占式
高响应比优先算法 (HRRN)
Highest Response Ratio Next
- 思想: 总和考虑等待时间和要求服务时间
- 规则: 每次调度时计算各个作业/进程的响应比, 选择最高的来服务
- 响应比 = \(\frac{等待时间+要求服务时间}{要求服务时间}\)
- 作业/进程调度: 都能使用
- 抢占: 非抢占式, 当前运行作业/进程主动放弃处理机才进行调度
- 优点: 综合考虑, 结合了 SJF 和 FCFS 的优点
- 缺点: 无
- 饥饿: 不会导致饥饿
高响应比优先算法:
- 非抢占式的调度算法,只有当前运行的进程主动放弃 CPU 时(正常/异常完成,或主动阻塞),才需要进
行调度,调度时计算所有就绪进程的响应比,选响应比最高的进程上处理机。
三种算法总结, 均不适合交互式系统!
2.2.5. 交互式系统调度算法: 时间片轮转(RR), 优先级, 多级反馈队列
学习技巧:
- 算法思想
- 算法规则
- 用于作业调度还是进程调度?
- 抢占式? 非抢占式?
- 优点和缺点
- 是否导致饥饿(某进程/作业长期得不到服务)
时间片轮转 (RR)
Round-Robin
- 思想: 公平轮流服务, 每个进程在一定时间间隔内都得到响应
- 规则: 按照各进程到就绪队列顺序, 轮流让各进程执行一个 时间片 (如100ms), 时间片结束未执行完成将被剥夺处理机, 放到队尾
- 作业/进程调度: 进程调度, 作业放入内存建立了进程才能被分配时间片
- 抢占: 抢占式, 时钟装置产生时钟中断进行时间片轮转
- 优点: 公平,响应快, 适用分时操作系统
- 缺点: 高频率进程切换, 不区分任务紧急程度
- 饥饿: 不会导致饥饿
同一时刻两个进程 A 和 B, A 刚下处理机, B 刚进入队列, 默认 B 先轮转时间片
如果时间片太大, 退化为FCFS, 会增大进程响应时间
如果时间片太小, 进程切换频繁, 切换进程会花费大量时间
一般来说, 设计时间片要让切换进程的开销占比不超过 1%
优先级调度算法
- 思想: 根据任务紧急程度来调度
- 规则: 每个任务有自己的优先级, 调度时选择优先级最高的
- 作业/进程调度: 都可以, 甚至还能用于 I/O 调度
- 抢占: 抢占式, 非抢占式均可, 抢占式时就绪队列发生变化就可能要调度, 否则主动放弃处理机时调度
- 优点: 优先级区分紧急程度, 适用实时操作系统
- 缺点: 一直有高优先级进入会有饥饿
- 饥饿: 导致饥饿
同优先级, 选择先进来的
优先级可以动态改变:
- 静态优先级: 进程创建后, 优先级一直不变
- 动态优先级: 创建进程有个初始值, 之后根据情况动态调整
通常策略
- 通常系统进程优先级 高于 用户进程
- 前台进程优先级 高于 后台进程
- 操作系统更偏好I/O型进程(I/O繁忙型进程), 相对的是 计算型进程(CPU繁忙进程), 两者可以并行, 选择更早进行 I/O 可以更优
动态策略
- 如果某进程在就绪队列中等待了很长时间,则可以适当提升其优先级
- 如果某进程占用处理机运行了很长时间,则可适当降低其优先级
- 如果发现一个进程频繁地进行I/O操作,则可适当提升其优先级
多级反馈队列调度
- 思想: 对其他算法的折中考虑
- 规则:
- 设置多级就绪队列, 优先级从高到低, 时间片从小到大
- 新进程到达时先进入第1级队列, 按FCFS原则排队等待时间片, 如果用完时间片还未结束, 进入下一级队列队尾, 如果已经在最低级队列, 则重新放回队尾
- 只有第k级队列为空, 才会为 k+1 级队列分配时间片
- 作业/进程调度: 用于进程调度
- 抢占: 抢占式, 抢占式的算法。在k级队列的进程运行过程中,若更上级的队列(1~k-1级)中进入了一个新进程,则由于新进程处于优先级更高的队列中,因此新进程会抢占处理机,原来运行的进程放回 k 级队列队尾。
- 优点: 结合了前面所有调度算法的优点, 并且对于 CPU密集型进程, I/O密集型进程可以调整偏好程度 (如: 将因 I/O 阻塞的进程重新放回原队列, 这样可以保证 I/O 进程保持较高优先级)
- 缺点: 无明显缺点
- 饥饿: 会导致饥饿
2.3.1. 进程同步, 互斥
同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某
些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互
合作。
我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)
属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源
对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源
时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,
另一个进程才能去访问临界资源。
进程互斥原则
- 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区;
- 忙则等待。当己有进程进入临界区时,其他试图进入临界区的进程必须等待;
- 有限等待对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿):
- 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。
2.3.2. 进程互斥的软件实现方法
单标志法
思想: 两个进程在访问完临界区后会把使用临界区的权限转交给另一进程. 也就是说每个进程进入临界区的权限只能被另一进程赋予
// P0:
{
while (turn != 0);
critical section;
turn = 1;
remainder section;
}
// P1:
{
while (turn != 1);
critical section;
turn = 1;
remainder section;
}
访问顺序一定是 01010101...
, 当临界区空闲, 0 没有访问临界区时, turn == 0
, 1 需要访问但无法进入.
不满足空闲让进的互斥原则
双标志先检查法
先检查, 后上锁
bool flag[2]; // flag[i] = true 表示 i 进程想要进入临界区
flag[0] = false;
flag[1] = false;
// P0:
{
while (flag[1]);
flag[0] = true;
critical section;
flag[0] = false;
remainder section;
}
// P1:
{
while (flag[0]);
flag[1] = true;
critical section;
flag[1] = false;
remainder section;
}
若 0, 1 两个进程并发执行, P0 结束while循环还未复制 flag[0] 时, 发生进程切换, P1 也可以进入临界区, 不满足忙则等待 原则.
双标志后检查法
先上锁, 后检查
bool flag[2]; // flag[i] = true 表示 i 进程想要进入临界区
flag[0] = false;
flag[1] = false;
// P0:
{
flag[0] = true;
while (flag[1]);
critical section;
flag[0] = false;
remainder section;
}
// P1:
{
flag[1] = true;
while (flag[0]);
critical section;
flag[1] = false;
remainder section;
}
P0 先执行flag[0] = true
后, 发生进程切换, P[1]执行 flag[1]
, 最后两个进程都不能进入临界区
不满足空闲让进和有限等待互斥原则
Peterson 算法
主动先让对方进入临界区
bool flag[2];
int turn = 0;
// P0
void funcp0(){
flag[0] = true;
turn = 1;
while (flag[1] && turn == 1) ;
critical section;
flag[0] = false;
remainder section;
}
// P1
void funcp1(){
flag[1] = true;
turn = 0;
while (flag[0] && turn == 0) ;
critical section;
flag[1] = false;
remainder section;
}
进程发生忙等待, 不满足 让权等待 互斥原则
2.3.3. 进程互斥的硬件实现方法
中断屏蔽方法
使用 开/关中断指令
- 优点:简单、高效
- 缺点:不适用于多处理机, 只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)
TestAndSet 指令
TS, TSL指令
将 上锁 和 检查 操作变成了一气呵成的原子操作
用硬件实现
bool TestAndSet (bool* lock) {
bool old;
old = *lock; // 获得lock 旧值
*lock = true;
return old;
}
int main(){
// TSL logical
while (TestAndSet);
临界区代码
lock = false;
剩余区代码
}
- 优点: 实现简单, 适用多处理机环境
- 缺点: 不满足让权等待 , lock = true, 另一个进程会忙等
Swap 指令
bool old = true;
while (old == true)
swap(&lock, &old);
临界区代码段...
lock = false;
剩余区代码段...
- 优点: 实现简单, 适用多处理机环境
- 缺点: 不满足让权等待 , lock = true, 另一个进程会忙等
2.3.4. 信号量机制
信号量表示系统中某种资源的数量
一对原语: wait(S) 和 signal(S), 叫做 P(S), V(S) 操作
整型信号量
整数型变量, 表示某种资源的数量
int S = 1;
void wait(int S) {
while (S <= 0);
S --;
}
void signal(int S) {
S ++;
}
// P0
{
wait(S);
使用资源...
signal(S);
}
- 缺点: 会发生忙等, 不满足让权等待 互斥原则
记录型信号量
typedef struct {
int value; // 剩余资源数
struct process *L; // 等待队列
} semaphore;
void wait (semaphore S) { // 原语操作
S.value --;
if (S.value < 0)
block (S.L); // 资源数不够, 把进程挂到S的等待队列中, 进入阻塞态
}
void signal(semaphore S) { // 原语操作
S.value ++;
if (S.value <= 0) {
wakeup(S.L); // 释放资源后, 还要进程在等待, 就将等待队列中阻塞的进程唤起
}
}
- 优点: 没有申请到资源的进程会被挂起进入阻塞态, 所以不会发生忙等(让权等待满足), 满足所有的互斥原则
2.3.5. 信号量实现进程互斥, 同步, 前驱关系
将临界区资源视为一种特殊资源, 设置互斥信号量 mutex, 初值为 \(1\)
注意:
- 对不同的临界资源需要设置不同的互斥信号量
- P, V 操作必须成对出现
进程同步
- 设置同步信号量S, 初始为0
- 在"前操作"之后执行V(S)
- 在"后操作"之前执行P(S)
Semaphore S = 0;
void P1(){
code 1;
code 2;
V(S);
code 3;
}
void P2(){
P(S);
code 4;
code 5;
code 6;
}
进程前驱关系
2.3.6. --- 2.3.10. 信号量解决同步互斥问题示例
生产者-消费者问题
- 改变互斥信号量mutex 在 同步信号量操作之间
semaphore mutex = 1; // 互斥
semaphore empty = n; // 同步
semaphore full = 0; // 同步
void put() {
while (1) {
P(empty);
P(mutex);
把产品放入缓冲区;
V(mutex);
v(full);
}
}
void get() {
while (1) {
P(full);
P(mutex);
取出产品
V(mutex);
V(empty);
// 使用产品
}
}
多生产者-消费者问题
问题描述
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放
橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。
只有盘子空时,爸爸或妈妈才
可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。
分析:
- 父亲, 母亲: 生产者1, 2
- 儿子, 女儿: 消费者1, 2
- 公用一个临界区资源, 有 2 类信号量
semaphore mutex = 1; // 可以省去, 缓冲区大于1 必须设置, 等于 1 可能不用设置
semaphore apple = 0;
semaphore orange = 0;
semaphore plate = 1;
- 从 "事件的角度" 来考虑问题, 把多个进程的结果引起的相同情况结合起来
吸烟者问题
题目描述
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷
起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草
第
二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌
子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供
并给供应者进程
应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
读者写者问题
有读者和写者两组并发进程,
共享一个文件,当两个或两个以上的读进程同时访问共享数据时不产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致
数据不一致的错误。因此要求:
- 允许多个读者可以同时对文件执行读操作;
- 只允许一个写者往文件中写信息;
- 任一写者在完成写操作之前不允许其他读者或写者工作;
- 写者执行写操作前,应让己有的读者和写者全部退出。
semaphore rw = 1; // 读写锁
int count = 0; // 记录正在访问的读者数量
semaphore mutex = 1; // 保证对 count 的互斥访问
semaphore w = 1; // 实现读写公平
void write(){
while (1) {
P(w);
P(rw);
写文件...
V(rw);
V(w);
}
}
void read() {
while (1) {
P(w);
P (mutex);
if (count == 0)
P(rw);
count ++;
V(mutex);
V(w);
读文件...
P(mutex);
count --;
if (count == 0)
V(rw);
V(mutex);
}
}
- 实现一气呵成, 加一个 互斥信号量
哲学家进餐问题
一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学
家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时
才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子己在他人手上,则需等待。饥饿的哲
学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
关键在于解决 死锁, 每个进程需要持有两个或两种以上的临界资源
方法:
- 设定信号量代表拿筷子的人数, 最多让 \(4\) 名哲学家同时拿筷子
- 奇数号先拿左边, 偶数号先拿右边
semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore mutex = 1;
void P() {
while (1) {
P(mutex);
P(chopstick[i]); // 拿左
P(chopstick[(i + 1) % 5]); // 拿右
V(mutex);
吃饭....
V(chopstick[i]);
V(chopstick[(i + 1) % 5]);
}
}
2.3.11. 管程
信号量: 编程困难, 容易出错
管程是一种特殊的软件模块,有这些部分组成:
- 局部于管程的共享数据结构说明:
- 对该数据结构进行操作的一组过程;
- 对局部于管程的共享数据设置初始值的语句:
- 管程有一个名字。
管程的基本特征:
- 局部于管程的数据只能被局部于管程的过程所访问:
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据;
- 每次仅允许一个进程在管程内执行某个内部过程。
其实类似 OOP
monitor ProducerConsumer{
condition full, empty;
int count = 0;
void insert(Item item) {
if (count == N)
wait(full);
count ++;
insert_item(item);
if (count == 1)
signal(empty);
}
Item remove() {
if (count == 0)
wait(empty);
count --;
if (count == N - 1)
signal(full);
return remove_item();
}
}
end monitor;
void produce() {
while (1) {
item = 生产一个产品;
ProducerConsumer.insert(item);
}
}
void consume() {
while (1) {
item = ProducerConsumer.remove();
消费产品 item;
}
}
- 管程中设置条件变量和等待/唤醒操作,以解决同步问题
- 编译器负责互斥进入管程的过程
2.4.1. 死锁的概念
死锁定义
- 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
- 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先 (SPF)算法
中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”
而发生长进程 - 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug 导致的,有时是
程序员故意设计的。
死锁产生的必要条件
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
- 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。
像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源) - 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出子新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己己有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中的
每一个进程己获得的资源同时被下一个进程所请求。
注意!发生死锁时一定有循环等待
- 但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件), 如果同类资源数大于 \(1\),则即使有循环等待,
也未必发生死锁。 - 但如果系统中每类资源都只有 \(1\) 个,那循环等待就是死锁的充分必要条件了。
问: 什么时候回发生死锁?
- 对系统资源的竞争。各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资
源(CPU)的竞争是不会引起死锁的。 - 进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程P1、
P2分别申请并占有了资源R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,
两者会因为申请的资源被对方占有而阻塞,从而发生死锁。 - 信号量的使用不当也会造成死锁。如生产者-消费者问题中,如果实现互斥的p操作在实现同步的
p操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资
源) - 总之,对不可剥夺资源的不合理分配,可能导致死锁。
死锁的处理策略
- 预防死锁。破坏死锁产生的四个必要条件中的一个或几个。
- 避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
- 死锁的检测和解除。允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。
2.4.2 死锁处理策略---预防死锁
破坏互斥
SPOOLing技术
把独占设备在逻辑上改造成共享设备, 将输出传输到输出进程, 进程端视作完成了输出(打印机)
- 缺点:适用范围小
破坏不剥夺条件
方案一: 进程请求新的资源得不到满足时, 它必须释放所保持的所有资源, 待以后需要时重新申请。
方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。
,这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)
该策略的缺点:
- 实现起来比较复杂
- 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态
的资源,如CPU。 - 反复地申请和释放资源会增加系统开销,降低系统吞吐量
- 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重
新申请。如果一直发生这样的情况,就会导致进程饥饿。
破坏请求和保持条件
静态分配: 进程在运行前一次性申请自己所需要的所有资源, 没申请到就不运行。
实现简单,但也有缺点:
- 有些资源可能需要用很短的时间, 因此如果进程运行期间一直保持, 造成了严重的资源浪费,资源利用率低。
- 另外,该策略也有可能导致某些进程饥饿。
破坏循环等待条件
顺序资源分配:首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。
原理分析:
- 一个进程只有己占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持
有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。
该策略的缺点:
- 不方便增加新的设备,因为可能需要重新分配所有的编号。
- 进程实际使用资源的顺序可能和 编号递增顺序不一致,会导致资源浪费。
- 必须按规定次序申请资源,用户编程麻烦。
2.4.3. 死锁处理策略---避免死锁
不允许死锁发生
安全序列
安全序列:就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个
安全序列,系统就是安全状态。当然,安全序列可能有多个。
如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后
可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了某些资源,那系统也有可能回到安全状态。不过分配时总是考虑最坏。
如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)
因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源
分配请求。这也是“银行家算法”的核心思想。
银行家算法
安全性算法
银行家算法
数据结构
- 长度为 \(m\) 的一维数组 Avalible 表示还有多少可用的资源
- \(n * m\) 矩阵 Max 表示各进程对资源的最大需求数
- \(n * m\) 矩阵 Allocation 表示已经给各进程分配了多少资源
- \(Max - Allocation = Need\) 矩阵表示各进程最多还需要多少资源
- 用长度为 \(m\) 的一维数组 Request 表示进程此次申请的各种资源数
步骤
- 检查此次申请是否超过声明的最大需求数
- 检查此时系统剩余可用资源是否满足要求
- 试探分配,更改各数据结构
- 用安全性算法检查此次分配是否会导致系统进入不安全状态
2.4.4. 死锁处理策略---检测与解除
检测死锁
建立资源分配图
如果图中的边都可以被消除,则称这个图是可完全简化的
检测算法:
- 找到一个既不是孤点也不阻塞的点, 消除其所有边
- 消除后释放的资源可以让某些阻塞进程被唤醒,然后继续消除这些进程的边。
死锁定理
- 如果某时刻系统的资源分配图是不可完全简化的,那么此时系统发生死锁
死锁解除
注意: 并不是所有进程都是死锁状态,用检测算法化简资源分配图后,还连着边的进程就是死锁进程。
主要方法:
- 资源剥夺法。挂起(放外存)某些进程,抢占资源重新分配给其他进程。后续注意饥饿
- 撤销进程法(终止进程)。付出代价可能很大,前功尽弃,但实现简单
- 进程回退法, 让死锁进程回退到足以避免死锁的地步,需要系统记录进程历史信息。
问: 如何决定“对谁动手” ?
答:
- 进程优先级来看(越低先牺牲)
- 已执行多长时间(越短先牺牲)
- 还要多久能结束(越久先牺牲)
- 进程已经使用了多少资源(更多先牺牲)
- 进程是交互式还是批处理式(优先批处理)
第三章---内存管理
3.1.1. 内存基础知识
内存基础
存放数据的硬件,程序执行前要放在内存中被CPU处理
存储方式
- 内存地址从 \(0\) 开始, 每个地址对应一个存储单元
- 按字节编址,每个存储单元大小为 1 字节,
1B
, \(8\) 个bit
- 按字编址,在字长为16位计算机中,每个存储单元大小为1个字,每个字大小为 \(16\) 个
bit
单位转换
\(2^{10} = 1K\)
\(2^{20} = 1M\)
\(2^{30} = 1G\)
- 变量在数据段中, 指令在程序段中
- 相对地址 <-> 逻辑地址
- 绝对地址 <-> 物理地址
程序运行:
- 编译代码: 生成目标模块
- 链接模块: 形成完整的逻辑地址
- 装入内存: 形成完整的物理地址
链接方式
- 静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
- 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
- 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
装入方式
绝对装入
只适用于单道程序环境
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。
装入程序按照装入模块中的地址,将程序和数据装入内存。
静态重定位
又称可重定位装入。编译、链接后的装入模块的地址都是从 0 开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对地址进行 “重定位”,将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)
静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。
作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。
动态重定位
又称动态运行时装入。编译、链接后的装入模块的地址都是从o开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行
时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持
重定位寄存器: 存放装入模块存放的起始地址(基地址)
采用动态重定位时允许程序在内存中发生移动, 并且可以动态申请空间
3.1.2 内存管理概念
- 操作系统负责内存空间的分配与回收
- 提供某种技术从逻辑上对内存空间进行扩充
- 提供地址转换功能,负责程序的逻辑地址与物理地址的转换
- 提供内存保护,保证各进程在各自顾存储空间内运行,互不干扰
- 在CPU设置一对上、下限寄存器,判断寻址合法
- 采用重定位寄存器(基址寄存器)和界址寄存器(限长寄存器) 进行越界检查 , 界址寄存器中存放进程的最大逻辑地址
3.1.3. 覆盖与交换
覆盖技术
在同一个程序或进程中进行
思想:将程序分为多个段。常用的段常驻内存,否则需要时调入。
内存中分为一个固定去和若干个覆盖区。
需要常驻内存的段放在固定区,调入后不再调出,除非运行结束
- 缺点:必须由程序员声明覆盖结构,对用户不透明,增加了编程负担
交换技术
在不同进程(作业)之间进行
系统将内存中某些进程暂时换出外存,吧外存中某些具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)
3.1.4. 连续分配管理方式
为用户进程分配的一定是要一个连续的内存空间
- 内部碎片:分配给某进程的内存区域中,某些部分没有用上
- 外部碎片:是指内存中的某些空闲分区由于太小而难以利用
使用 紧凑(Compacation) 技术解决外部碎片
单一分配
内存被分为系统区和用户区
内存中只有一道用户程序
- 优点:
- 实现简单;
- 无外部碎片;
- 可以采用覆盖技术扩充内存;
- 不一定需要内存保护;
- 缺点:
- 只能用于单用户,单任务操作系统
- 有内部碎片
- 存储器利用效率极低
固定分区分配
将用户空间划分为若干个固定大小的分区,在每个分区只装入一道作业
操作系统需要建立一个数据结构---分区说明表,来实现各个分区的分配与回
。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的
大小、起始地址、状态(是否已分配)。
-
分区大小相等
- 缺乏灵活性
- 使用与用一台计算机控制多个相同对象
-
分区大小不等
- 增加了灵活性,可以满足不同大小的进程需求
-
优点:实现简单,无外部碎片
-
缺点:
- 用户程序太大,需要覆盖技术来解决但又会降低性能
- 会产生内部碎片,内存利用率低
动态分区分配
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数
目是可变的。(eg:假设某计算机内存大小为 64MB,系统区 8MB,用户区共 56 MB..)
数据结构:
- 空闲分区表
- 空闲分区链
注意内存回收时的各种情况,对分区表的修改情况
- 无内部碎片,有外部碎片
3.1.5. 动态分区分配算法
首次适应算法
- 算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区
- 如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区
表),找到大小能满足要求的第一个空闲分区。
最佳适应算法
- 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,
即,优先使用更小的空闲区 - 如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
- 缺点:每次选最小的分区分配,会留下越来越多,很小的,难以利用的内存块。因此会产生很多的外部碎片
最坏适应算法
Largest Fit
- 算法思想:为了解決最佳适应算法的问题一一即留下太多难以利用的小碎片,可以在每次分配时
优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。 - 如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区
表),找到大小能满足要求的第一个空闲分区。 - 缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了
邻近适应算法
- 算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查
找结束的位置开始检索,就能解决上述问题。 - 如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表),每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区.
3.1.6. 基本分页存储管理概念
非连续分配方式
分页地址转换
将内存空间分为一个个大小相等的分区(比如:每个分区4KB),每个分区就是一个 “页框”,或称“页帧”
“内存块”、“物理块” 。每个页框有一个编号,即 “页框号”,“内存块号”,“页帧号”、“物理块号”,页框号从 \(0\) 开始
将用户进程的地址空间也分为与页框大小相等的一个个区域称为 “页”或“页面”。每个页面也有一个编号,即“页号”
页号也是从0开始。(注:进程的最后一个页面可能没有一个页框那么大。因此,页框不能太大,否则可能产生过大的内部碎片)
操作系统以页框为单位为各个进程分配内存空间。进程的每个
页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。
各个页面不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。
- 要算出逻辑地址对应的页号
- 要知道该页号对应页面在内存中的起始地址
- 要算出逻辑地址在页面内的“偏移量”
- 物理地址=页面始址 +页内偏移量
结论:如果每个页面大小为 \(2^kB\),用二进制数表示逻辑地址,则末尾 \(K\) 位即为页内偏移量,其余部
分就是页号
因此,如果让每个页面的大小为 \(2\) 的整数幂,计算机就可以很方便地得出一个逻辑地址对应的页号
和页内偏移量。
页表
- 一个进程对应一张页表
- 进程的每一页对应一个页表项
- 每个页表项由“页号”和“块号” 组成
- 页表记录进程页面和实际存放的内存块之间的对应关系
- 每个页表项的长度是相同的,页号是“隐含”的
3.1.7. 基本地址变换机构
基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。
通常会在系统中设置一个页表基地址寄存器(PTBR),存放页表在内存中的起始地址F 和页表长度M.
进程末执行时,页表的始址 和 页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中
3.1.8. 具有快表的地址变换机构
- 时间局部性: 如果执行了程序中的某条指令,那么不久后这条指令很
有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环) - 空间局部性: 一旦程序访问了某个存储单元,在不久之后,其附近的
存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
快表,又称联想寄存器 (TLB),是一种访问速度比内存快很多的高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,内存中的页表常称为慢表。以加速地址变换的过程
计算时间:
访问一次快表消耗 1 us, 内存 100 us, TLB命中率为 90%, 平均耗时?
(1 + 100) * 0.9 + (1 + 100 + 100) * 0.1 = 111 us
- 还要注意是否是同时查询快表和慢表
3.1.9. 两级页表
可将长长的页表进行分组,使每个内存块刚好可以放入一个分组(比如上个例子中,页面大小 4KB,
每个页表项 4B,每个页面可存放1K 个页表项,因此每 1K 个连续的页表项为一组,每组刚好占一个内
存块,再讲各组离散地放到各个内存块中)
另外,要为离散分配的页表再建立一张页表,称为页目录表,或称外层页表,或称顶层页表
- 采用多级页表,则各级页表的大小不能超过一个页面大小
- \(n\) 级页表在访问逻辑地址时访问次数为 \(n+1\) 次,不考虑 TLB
计算页表层数步骤:
- 获得系统逻辑地址位数 tot,得到页内偏移地址位数 b
- 页号位数 page_number = tot - b
- 每个页表最多有多少个页表项 item_per_page = 页面大小(长度) / 页表项大小
- 答案 = ceil(page_number / item_per_page)
3.1.10. 基本分段存储管理方式
段页对比:
-
分段比分页更容易实现信息的共享与保护
-
不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。改的代码是不能共享的 (比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)
3.1.11. 段页式管理方式
逻辑地址结构: (段号, 页号, 页内偏移量)
每个段对应段表项,各表项长度相同段表组成: (段号(隐含)、页表长度、页表存放地址)
每个页对应一个页表项,页表组成:(页号(隐含)、页面存放的内存块号)
3.2.1. 虚拟内存基本概念
建立在离散分配的基础上
- 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
- 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
- 在操作系统的管理下,,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存
传统内存管理方式:
- 一次性:作业必须一次性装入内存后才能运行
- 驻留性:作业被装入内存,就会一直驻留在内存中
虚拟内存:
- 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存.
- 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换
入、换出。 - 虛拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
3.2.2. 请求分页管理方式
请求分页存储管理与基本分页存储管理的主要区别:
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然
后继续执行程序。 - 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
请求页表新增增加 \(4\) 个字段:
- 状态位:是否已经调入内存
- 访问字段:记录最近访问过几次或者上次访问的时间,供置换算法使用
- 修改位:调入内存后是否被修改
- 外存地址:页面在外存中的存放位置
缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然
后由操作系统的缺页中断处理程序处理中断。
此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
-
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
-
如果内存中没有空闲块,,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断与当前指令有关, 属于内中断
地址变换机构
新增步骤:
- 查到页表项判断是否调入内存
- 页面置换(需要调入页面,但没有空闲内存块)
- 需要修改请求页表中新增的表项
3.2.3. 页面置换算法
最佳置换算法 (OPT)
Optimal
思想: 每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面。
缺页中断次数 \(\geq\) 页面置换次数
- 缺点:最佳置换算法是无法实现的
先进先出置换算法 (FIFO)
First In First Out
思想:每次选择淘汰的页面是最早进入内存的页面
Belady 异常---当为进程分配的物理块数增大时,缺页次数不减反增。
只有FIFO算法发生Belady异常, 实现简单,但不适应,算法性能差
最近最久未使用置换算法(LRU)
Least Recently Used
思想:每次淘汰的页面是最近最久未使用的页面
实现:用访问字段记录该页面上次被访问以来所经历的时间t, 每次要淘汰一个页面选择 \(t\) 最大的
做题时,若淘汰页面,逆向检查此时内存中的几个页面号。逆向扫描过程中最后一个出现的页号就是要淘汰的页面
- 缺点: 算法性能好, 但是实现困难,开销大
时钟置换算法 (CLOCK, 最近未用算法,NRU)
NRU, Not Recently Used
朴素版实现: 访问字段记录有无被访问 0/1, 将页面通过指针链接为循环链表
- 第一次扫描,找到页面若字段为 0,直接换出,字段为 1 则置 0
- 第二次扫描,即换出第一个页面
改进型实现:增加一个字段修改位,(访问位,修改位)
- 第一次扫描:从当前位置开始扫描到第一个 (0, 0) 的帧用于置换,不修改标志位
- 第二次扫描:查找第一个 (0, 1) 的帧用于置换,扫描过的帧访问位设0
- 第三次扫描:查找第一个 (0, 0) 用于置换,不修改标志位
- 第四次扫描:查找第一个 (0, 1) 的帧用于置换
优先级:
- 最近没访问且没修改
- 最近没访问,但修改
- 最近访问过,但没修改
- 最近访问过,且修改
3.2.4. 页面分配策略
置换策略
驻留集: 指请求分页存储管理中给进程分配的物理块的集合
- 驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少
- 驻留集太大,又会导致多道程序并发度下降,资源利用率降低。所以应该选择一个合适的驻留集大小。
驻留集大小 \(\leq\) 进程总大小
固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变
可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变
局部置换:发生缺页时只能选进程自己的物理块进行置换。
全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块罝换
到外存,再分配给缺页进程。
固定分配不存在全局置换(驻留集大小发生改变)
置换策略:
- 可变分配全局置换:只要缺页就给新物理块,减少物理块的情况是空闲物理块被用完,其他进程需要新物理块从该物理块中调走
- 可变分配局部置换,要根据缺页频率来动态增加或减少进程的物理块
调页策略
何时调入?
-
预调页策略:根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如
果果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入,由程序员指出应该先调入哪些部分 -
请求调页策略:进程在运行期间发现缺页时才将所缺页面调入内存。由这种策略调入的页面一定会
被访问到,但由于每次只能调入一页,而每次调页都要磁盘I/O操作,因此I/O开销较大。
何处调入?
从对换区读取,(读写速度更快,采用连续分配)
- 系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保
证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。 - 系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不
会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的
部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。 - UNIX 方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调
入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入
抖动(颠簸)现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称
为抖动,或颠簸。
产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程
的物理块不够)
工作集:在某段时间间隔(窗口尺寸)内,进程实际访问页面的集合
工作集大小可能会小于窗口尺寸(局部性很好,差距越大)
第四章---文件管理
4.1.1. 初始文件管理
文件的属性
- 文件名:同目录无重名文件
- 标识符:对用户无可读性,操作系统用于区分文件的内部名称
- 类型:指明文件的类型
- 位置:文件存放的路径(用户使用)、在外存中的地址(操作系统使用,用户不可见)
- 大小:文件大小
- 创建信息、上次修改时间、文件所有者信息
- 保护信息:对文件进行保护的访问控制信息
文件内部组织
- 无结构文件:由一系列二进制或字符流组成
- 有结构文件:若干记录,每个记录有若干数据项
文件之间组织
- 文件树的形式,每个内部节点是目录,叶子节点是文件
文件如何存在外存
- 外存被分为 块/磁盘快/物理块, 以块为单位读取,即便一个文件远小于一个块大小,依然独自占一个块
操作系统向上提供的功能
- 创建文件 (create 系统调用)
- 删除文件 (delete 系统调用)
- 读文件 (read 系统调用)
- 写文件 (write 系统调用)
- 打开文件 (open 系统调用)
- 关闭文件 (close 系统调用)
其他需要操作系统实现的文件管理功能
- 文件共享:多个用户可以共享一个文件
- 文件保护:如何保证不同的用户对文件有不同的操作权限
4.1.2. 文件的逻辑结构
逻辑结构,从用户角度来看
物理结构,从操作系统角度来看
无结构文件:二进制流或字符流组成,又称“流式文件”。
有结构文件:由一组相似的记录组成,又称“记录式文件”。每条记录由若干数据项组成,每条记录由一个数据项作为关键字,分为定长和不定长记录
顺序文件:文件中的记录一个接一个顺序排列,记录可以是定长或不定长,物理上存储可以是顺序存储,链式存储
有两种结构:
- 串结构: 记录之间的顺序与关键字无关(如按存入时间排列)
- 顺序结构: 记录之间的顺序按关键字排列
能否随机存取?
顺序文件: 默认物理上顺序存储的文件,缺点是增加,删除记录困难(串结构简单)
- 链式存储只能依次查找
- 顺序存储
- 可变长记录,只能依次查找
- 定长记录,可以实现随机存储。
- 串结构,无法快速找到关键字对应记录
- 顺序结构,可以快速找到对应关键字的记录(如折半查找)
索引文件: 本身是定长记录的顺序文件, 主要用于对信息处理的及时性要求比较高的场合 (索引号,长度, 指针)
- 可以用不同的数据项建立索引文件
- 关键字顺序排列,可以实现快速查找
索引顺序文件:在索引文件的基础上,不是一个记录对应一个索引表项,而是一组记录对应一个索引表项 (分块的思想)索引项组成 (键,地址)
多级索引顺序文件:高级索引顺序文件索引项为低一级的索引顺序文件,索引项组成 (键,地址)
4.1.3. 文件目录
- 组织结构清晰,便于查找
- 编程时方便使用文件路径找到文件
文件控制块
目录本身是有结构文件,由一条条记录组成,每个记录都是放在该目录的一个文件
FCB(File Control Block) 的有序集合是 “文件目录”。一个FCB就是一个文件目录项
FCB 中包含了文件的基本信息(文件名、物理地址、逻辑结构、物理结构等),存取控制信息,使用信息。
需要对目录进行哪些操作?
- 搜索:当用户要使用一个文件时,系统要根据文件名搜索目录,找到该文件对应的目录项
- 创建文件:创建一个新文件时,需要再所属目录中新增一个目录项
- 删除文件:当删除一个文件时需要在目录中删除相应的目录项
- 显示目录:用户可以请求显示目录的内容,如显示该目录中的所有文件及相应属性
- 修改目录:某些文件属性保存在目录中,因此这些属性变化时需要修改相应的目录项(如:文
件重命名)
单级目录结构
- 可以实现按名存取,不允许文件重名
- 不适用于多用户系统
两级目录结构
主文件目录 (MFD, Master File Directory), 用户文件目录 (UFD, User File Directory),不同用户目录的文件可以重名
多级目录结构 (树形目录结构)
用户访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。
-
从根目录出发的路径称为绝对路径
-
从当前目录出发的路径称为相对路径
-
缺点:不便于实现文件的共享
无环图目录结构
在属性目录基础上添加有向边,形成 DAG, 实现文件共享
- 为每个节点设置共享计数器,提出删除节点时,共享计数器减 1,只有共享计数器减为 0 时,才会删除结点
- 共享文件被修改,所有用户都可以看到变化
索引节点(FCB的改进)
查找文件只关心文件名字(因为不同),去除冗余信息
减少了目录项的存储磁盘块数量,平均启动磁盘时间也减少(每次磁盘I/O读入一块到内存)
外存中的索引结点被称为“磁盘索引结点”,内存中的索引结点被称为内存索引节点(额外增加一些信息,“文件是否被修改、此时有几个进程正在访问文件)
4.1.4-4.1.5. 文件的物理结构 (文件分配方式)
在很多操作系统中,磁盘块的大小与内存块、页面的大小相同
外存管理中,文件的逻辑地址也被分为了一个一个的文件块
逻辑地址形式 (逻辑块号,块内地址)
连续分配
每个文件在磁盘上占有一组连续的块
- 转换时,只关心 逻辑块号<->物理块号
- 验证逻辑块号是否小于物理块长度
- \(物理块号=起始块号+逻辑块号\)
- 优点:
- 支持顺序访问和直接访问(随机访问)
- 读取某个磁盘块,需要移动磁块,连续分配文件在顺序读写的速度最快
- 缺点:
- 物理上不方便对文件拓展
- 空间利用率低,产生难以利用的磁盘碎片,可以通过紧凑解决
链接分配
采取离散分配的方式,分为隐式链接和显示链接
隐式链接
FCB: (文件名,...,起始块号,结束块号),
类比链表的指针遍历,访问逻辑为 \(i\) 号逻辑块,需要 \(i+1\) 次磁盘 I/O.
- 优点:
- 拓展方便,不存在磁盘碎片
- 缺点:
- 只支持顺序访问,不支持随机访问,查找效率低
- 指向下一盘块的指针也要耗费少量空间
显式链接
链接文件各物理块的指针显示存放在一张表中,即文件分配表,FAT, File Allocation Table
- 顺序存储表中信息, 表项: (物理块号,下一块), 类似数组实现邻接表
- 一个磁盘仅需要一张 FAT。开机时 FAT 被读入内存并常驻
- 优点:
- 采用链式分配 (显式链接)方式的文件,支持顺序访问,也支持随机访问(想访问i号逻辑块时,并不需要依次访问之前的0~i-1号逻辑块)
- 块号转换的过程不需要访问磁盘,因此相比于隐式链接来说,访问速度快很多。
- 显然,显式链接也不会产生外部碎片,也可以很方便地对文件进行拓展。
- 缺点:
- FAT 需要占用一定内存空间
链接方式默认隐式链接
索引分配
注意: FCB 在内存里时读取不需要 I/O,初始放在外存(属于目录项,存在磁盘里)
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文
件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表一一建立逻辑页面到物理页之间
的映射关系)。索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块。
- 目录中需要记录文件的索引快是几号磁盘块。 (文件名,...,索引块号), 一个索引项指向物理块
- 可以支持随机访问,文件拓展很容易实现
需要索引项太多?
连接方案
连接方案:将多个索引块链接起来存放,每个索引快用空间存指向下一索引块的位置, FCB中记录 (文件名,顶级索引块)
- 文件太大要顺序访问所有索引快
多层索引
多层索引,类似多级页表。FCB中记录 (文件名,索引块)。
- 磁盘块大小为 1 KB, 一个索引表项占 4 B, 则一个磁盘块只能存放 256 个索引项
- 两层索引文件最大长度:256*256*1KB = 65,536 KB = 64MB
- 文件过小但依然需要多层索引。。。
混合索引
多种索引分配方式的结合。
例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)
计算文件最大长度核心: 索引表大小不能超过磁盘块大小
4.1.6. 文件存储空间管理
存储空间划分与初始化
Windows 中,一个文件分为多个文件卷,每个文件卷分为 目录区(存放 FCB, 用于磁盘存空间管理的信息) 和 文件区(存放数据)
空闲表法 (适用于 连续分配方式)
记录 (第一个空闲盘,块号), 为文件分配连续的存储空间, 与内存管理的相似
回收时注意表项合并
空闲链表法
空闲盘块链
- 盘块为单位,存有指向下一块指针
- 系统保存 链头、链尾指针
- 分配与回收都是一个一个操作
空闲盘区链
- 盘区为单位,第一个盘块存有盘区长度和下一个盘区的指针
- 系统保存 链头、链尾指针。
- 分配时也有最佳适应算法等, 大小没有符合要求时,可以将不同盘区分配给一个文件
- 回收时,要注意前后合并的问题,离散分配、连续分配都适用,效率高
位示图法
成组链接法
设立一个磁盘块为 超级块
4.1.6. 文件的基本操作
创建文件 (Create)
参数:
- 外存空间大小
- 文件路径
- 文件名
做的事:
- 在外存中找到文件所需的空间
- 根据文件存放路径信息找到目录文件,在目录中创建该文件对应的目录项, 包含文件名,外存中存放的位置
删除文件(Delete)
参数:
- 路径
- 文件名
做的事:
- 根据文件路径找到对应目录文件,从目录中找到文件名对应的目录项
- 根据目录项记录的外存存放位置、文件大小等信息,回收文件占用的磁盘块
- 从目录表中删除文件对应的目录项
打开文件 (Open)
参数:
- 路径
- 文件名
- 操作类型 (r, rw...)
做的事:
- 从路径找到目录文件,目录中找到文件名对应目录项,检查是否有权限
- 将目录项复制到内存中“打开文件表”中。返回对应表项索引号(文件描述符)。之后用户使用打开文件表的编号来指明要操作的文件
两种打开文件表: 进程的和系统的。
关闭文件(Close)
做的事:
- 将进程打开文件表对应表项删除
- 回收分配给文件的资源
- 打开文件表的打开计数器 count - 1, 若等于 0 删除表项
读文件 (Read)
真正从外存读入内存
- 读的文件(操作系统看来是打开表的编号)
- 将读指针所指的外存中,将用户指定大小的数据读入用户指定的内存区域中)
写文件 (Write)
- 指明文件
- 修改大小
- 外存数据在内存中位置
- 将数据从内存写入外存
4.1.7. 文件共享
硬链接
基于索引结点
索引结点,对FCB进行瘦身(存放文件名, 指针)
索引结点中设置一个链接计数变量 count,用于表示链接到本索引结点上的用户目录项数
- 若 count = 2,说明此时有两个用户目录项链接到该索引结点上,或者说是有两个用户在共享此文件。
若某个用户决定“删除〞该文件,则只是要把用户目录中与该文件对应的目录项删除,且索引结点的
count值减 1。 - 若 count > 0,说明还有别的用户要使用该文件,暂时不能把文件数据删除,否则会导致指针悬空。
- 当 count = 0 时,系统负责删除文件
软链接
基于符号链接
索引节点指向 Link 类型的文件(源文件被删除依然存在link文件)。类似 快捷方式
4.1.8. 文件保护
口令保护
口令存放在 FCB 或 索引节点中
- 优点:保存口令的空间开销不多,验证口令的时间开销也很小。
- 缺点:正确的“口令” 存放在系统内部,不够安全。
加密保护
使用某个“密码”对文件进行加密,在访问文件时需要提供正确的“密码”才能对文件进行正确的解密。
- 优点:保密性强,不需要在系统中存储“密码”
- 缺点:编码/译码,或者说加密/解密要花费一定时间。
访问控制表
在每个文件的FCB(或索引结点)中增加一个访问控制列表 (Access-Control List, ACL),该表中记录了各个用户可以对该文件执行哪些操作。
4.1.9. 文件系统的层次结构
4.2.1. 磁盘的结构
- 固定头磁盘:每个磁道 1 个磁头
- 移动头磁盘:每个盘面 1 个磁头
4.2.2. 磁盘调度算法
一次磁盘读写时间
- 寻找时间(寻道) \(T_S\)
- 启动磁头臂, 花费 \(s\)
- 移动磁头,每跨越一个磁道耗时 \(m\), 总共跨越 \(n\) 条磁道
- \(T_S=s+m*n\)
- 操作系统可以控制
- 延迟时间 \(T_R\),
- 磁盘转速为 \(r\)。
- 延迟时间 \(T_R=\frac{1}{2*r}\)
- 传输时间 \(T_t\),
- 转速为 \(r\), 读写字节数为 \(b\)。每个磁道上字节数为 \(N\)
- 传输时间 \(T_t=\frac{b}{r*N}\)
先来先服务算法 (FCFS)
- 优点:公平
- 缺点:磁道分散,性能差
最短寻找时间优先 (SSTF)
- 思想:选择距离当前磁头最近的磁道访问
- 优点:性能较好,平均寻道时间短
- 缺点:可能产生“饥饿”现象
扫描算法 (SCAN)
题目不说明,SCAN就是LOOK算法
LOOK 调度
题目不说明,SCAN就是LOOK算法
在SCAN的基础上,如果在磁头移动方向上己经没有别的请求,就可以立即改变磁头移动方向
循环扫描算法 (C-SCAN)
C-LOOK
与 LOOK 相对 SCAN 的改进同理
4.2.3. 减少磁盘延迟的方法
- 采用交替编号
- 原理:读完一个扇区需要一段时间才能继续读
- 磁盘结构设计 (柱面,盘面,扇区)
- 错位命名
4.2.4. 磁盘的管理
- 磁盘初始化
- 引导块
- 坏块的管理
第五章---I/O设备管理
5.1.1. I/O设备分类与概念
5.1.2. I/O 控制器
5.1.3. I/O 控制方式
程序直接控制
- 完成一次读写, 关键:轮询
- CPU干预频率,在等待 I/O 完成的过程中CPU需要不断轮询检查
- 每次读写一个字
- 流向
- 读: I/O->CPU->内存
- 写: 内存->CPU->I/O
- 每个读写都要CPU参与
- 优点:实现简单。读写指令后,加上实现循环检查即可
- 缺点:CPU 和 I/O 设备只能串行工作,CPU需要一直轮询检查,长期处于“忙等”状态,CPU利用率低
中断驱动方式
关键: 中断
其他与上相同
- 优点: CPU 和 I/O 设备并行工作
- 缺点: 频繁中断消耗过多CPU时间
DMA 方式
Direct Memory Access
- 数据传送单位是“块”
- 数据直接在设备与内存间传送,不需要CPU介入
- 仅在一个或多个数据块开始和结束时,才需要CPU介入
通道控制
5.1.4. I/O 软件层次结构
用户层软件
- 实现了与用户交互的接口,用户使用提供的 库函数
- 将用户请求翻译成格式化的 I/O 请求,并通过系统调用请求操作系统内核的服务
设备独立性软件
又称**设备无关性软件 **
- 向上提供统一的调用几口(read/write)
- 设备的保护(类似文件保护, 设备被看成一种特殊的文件)
- 差错处理,对设备错误进行处理
- 设备的分配与回收(临界资源)
- 数据缓冲区管理(缓冲技术屏蔽设备间数据交换单位大小和数据传输的差异)
- 建立逻辑设备名到物理设备名的映射关系;根据设备类型选择调用相应的驱动程序
- (通过逻辑设备表(LUT, Logical Unit Table) 确定逻辑设备对应的物理设备,并找到对应的设备驱动程序)
- 第一种方式,整个系统设置一张 LUT, 只适用于单用户操作系统
- 第二种方式,每个用户设置一张 LUT, 存在用户进程对应的 PCB 中
- (通过逻辑设备表(LUT, Logical Unit Table) 确定逻辑设备对应的物理设备,并找到对应的设备驱动程序)
设备驱动程序
负责对硬件设备的具体控制,将上层发出一系列命令(read、write)转化成设备听得懂一系列操作。包括设置设备寄存器;检查设备状态
不同I/O设备具有不同的硬件特性,厂家需要根据设备的硬件特性设计并提供相应的驱动程序
驱动程序一般以一个进程的形式存在
中断处理程序
当I/O任务完成时,I/O控制器会发送一个中断信号,系统会根据中断信号类型找到相应的中断处理程序并执行。中断处理程
序的处理流程如下:
5.1.5. I/O 核心子系统
I/O 系统
即 设备独立性软件、设备驱动程序、中断处理程序
- 用户层软件:假脱机技术(SPOOLing技术)
- 设备独立性软件:I/O调度(如磁盘调度)、设备保护、设备分配与回收、缓冲区管理
5.1.6. 假脱机技术 (SPOOLing)
脱机,脱离主机的控制进行输入/输出
需要多道程序技术的支持
共享打印机原理:
- 独占式设备:只允许各进程串行使用
- 共享设备:允许多个进程“同时”使用的设备
5.1.7. 设备的分配与回收
设备固有属性: 独占设备、共享设备、虚拟设备(独占->共享)。
5.1.8. 缓冲区管理
用内存作为缓冲区