文章目录
1 概论
1.1 CPU漏洞攻击
Meltdown和Spectre(熔断和幽灵)
1.2 操作系统简史
1.2.1 体系结构
-
冯诺依曼结构(普林斯顿结构)
-
哈佛结构(不考
1.2.2 系统发展
- 操作系统主要功能:进程管理、存储管理、设备管理、文件系统
-
批处理
- 把用户提交的作业成批送入计算机
- 由作业调度程序自动选择作业运行
目的:- 缩短作业之间的交接时间
- 减少处理机的空闲等待,提高系统效率
-
批处理系统
加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。
-
联机批处理系统
作业的输入输出由CPU处理
- 优点:提高了计算机的利用率
- 缺点:慢速输入输出过程,高速CPU空闲,主机“忙等”
-
脱机批处理系统
作业的输入输出脱离主机控制
- 优点:主机和卫星机可并行,充分发挥主机高速计算能力
- 缺点:慢速输入输出过程,高速CPU空闲,主机“忙等”
-
多道程序系统
同时把多个程序放入内存中(前提是内存放的下),并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
-
特点:
- 多道
- 宏观并行
- 微观串行
-
-
多道批处理系统
-
特点:多道、成批
-
优点:系统吞吐量达、资源利用率高
-
缺点:平均周转时间长、不能提供交互能力
-
-
分时系统
将CPU处理时间分割为多个时间片,将时间片分给不同程序,达到多个程序“同时”运行的效果
- 特点:多路性、交互性、独立性、及时性
-
现代操作系统基本特征
并发执行、资源共享、虚拟化管理、异步性
1.3 操作系统基本实现机制
1.3.1 异常:陷阱和中断
- 中断是异步异常,可能随时发生,与处理器正在执行的内容无关。中断主要由I/O设备、处理器时钟或定时器产生,可以被启用或禁用。 (Asynchronous Exceptions)
- 同步异常,它是某一特定指令执行的结果。在相同条件下,异常可以重现。例如内存访问错误、调试指令以及被零除。 (Synchronous exceptions)
- 系统调用也视作同步异常,或陷阱trap。
- 软件和硬件都可以产生中断,软件中断常称为陷阱trap。
- 陷阱(trap)帧:完整的线程描述表的子集,用于现场保护
- 陷阱处理程序处理少量事件,多数转交给其他的内核或执行体模块处理
2 系统引导
3 内存管理
3.1 预备知识-链接与装载
-
gcc工具
- cc1:预处理器和编译器
as:汇编器 - collect2:链接器
- cc1:预处理器和编译器
-
ELF文件部分含义
- .bss:Block Started bySymbol–存放未初始化数据
.data/.datal存放初始化数据
.rodata/.rodatal存放只读数据
.text 存放程序的执行指令
- .bss:Block Started bySymbol–存放未初始化数据
-
链接的过程
- 编译:每个.c->.o,函数在不同文件,不知道位置
链接:.o连接到一起,形成可执行文件。
重定位:未填写的地址填写
- 编译:每个.c->.o,函数在不同文件,不知道位置
-
重定位链接地址计算
-
程序入口点
_start 函数 ,
-
程序的装载与运行
-
执行过程:
-
shell调用fork()系统调用
-
创建一个子程序
-
子程序调用execve()加载program (装载)
-
-
程序的装载细节
- 一个segment在文件中的大小是小于等于其在内存中的大小。
- 如果在文件中的大小小于在内存中的大小,那么在载入内存时通过补零使其达到其在内存中应有的大小。
-
3.2 存储管理基础
3.2.1 存储器管理目标
- 研究对象:以内存为中心的存储资源
- 帕金森定律(Parkinson):无论存储器空间有多大,程序都能将其耗尽
3.2.2 存储器硬件发展
- 静态存储器(SRAM):读写速度快,生产成本高,多用于容量较小的高速缓冲存储器
- 动态存储器(DRAM):读写速度较慢,集成度高,生产成本低,多用于容量较大的主存储器。
3.2.3 存储管理的功能
-
存储管理的基本机制:抽象
基本概念:地址空间、逻辑地址、物理地址
地址空间:一个进程所能够用于访问内存的地址集合。
逻辑地址:又称虚拟地址,程序所使用的地址。
物理地址:物理内存中数据存储的地址
存储管理的基石:- 地址独立:程序发出的地址与物理地址无关
- 地址保护:一个程序不能访问另一个程序的地址空间
-
存储管理的功能
-
存储分配和回收:存储管理的主要内容。讨论其算法和相应的数据结构。
-
地址变换:可执行文件生成中的链接技术、程序加载时的重定位技术,进程运行时硬件和软件的地址变换技术和机构。
-
存储共享和保护:代码和数据共享,对地址空间的访问权限(读、写、执行)。
-
存储器扩充:涉及存储器的逻辑组织和物理组织;
-
由应用程序控制:覆盖;
-
由OS控制:交换(整个进程空间),请求调入和预调入(部分进程空间)
-
-
-
单道程序内存管理
-
特点:
-
在单道程序环境下,整个内存里只有两个程序:一个用户程序和操作系统。
-
操作系统所占的空间是固定的。
-
因此可以将用户程序永远加载到同一个地址,即用户程序永远从同一个地方开始运行。
-
用户程序的地址在运行之前可以计算。
-
-
方法:
- **静态地址翻译:**即在程序运行之前就计算出所有物理地址。
- 静态翻译工作可以由加载器实现。
-
分析:
- 地址独立?YES. 因为用户无需知道物理内存的地址。
- 地址保护?YES. 因为没有其它用户程序。
-
优点:执行过程中无需地址翻译,程序运行速度快。
-
缺点:比物理内存大的程序无法加载,因而无法运行;造成资源浪费(小程序会造成空间浪费;I/O时间长会造成计算资源浪费)。
-
-
多道程序的内存管理
-
特点:分区式分配
- 把内存分为一些大小相等或不等的分区(partition),每个应用程序占用一个或几个分区。操作系统占用其中一个分区。
- 适用于多道程序系统和分时系统,支持多个程序并发执行,但难以进行内存分区的共享。
-
方法:
- 固定(静态)式分区分配,程序适应分区。
- 可变(动态)式分区分配,分区适应程序。
-
固定式分区分析:
-
把内存划分为若干个固定大小的连续分区
- 分区大小相等:只适合于多个相同程序的并发执行(处理多个类型相同的对象)。
- 分区大小不等:多个小分区、适量的中等分区、少量的大分区。根据程序的大小,分配当前空闲的、适当大小的分区。
-
优点:易于实现,开销小。
-
缺点:内碎片造成浪费,分区总数固定,限制了并发执行的程序数目。
-
采用的数据结构:分区表--记录分区的大小和使用情况。
-
分配方式
-
单一队列
-
多队列
-
-
-
可变式分区分析:
-
可变式分区:分区的边界可以移动,即分区的大小可变。
-
优点:没有内碎片。
-
缺点:有外碎片。
-
闲置空间的管理
-
位图表示法:
- 空间开销固定:不依赖于内存中的程序数量。
- 时间开销低:操作简单,直接修改其位图值即可。
- 没有容错能力:如果一个分配单元对应的标志位为1,不能确定是否因错误变成1。
-
链表表示法:
- 空间开销:取决于程序的数量。
- 时间开销:链表扫描速度较慢,还要进行链表项的插入、删除和修改。
- 有一定容错能力:因为链表有被占空间和闲置空间的表项,可相互验证。
-
-
-
3.2.4 存储器分配方法
-
基于顺序搜索的4种分配算法
- 首次适应算法(First Fit):每个空闲区按其在存储空间中地址递增的顺序连在一起,在为作业分配存储区域时,从这个空闲区域链的始端开始查找,选择第一个足以满足请求的空白块。
- 特点:**产生碎片、时间开销大。**优先利用内存低地址部分的空闲分区。但由于低地址部分不断被划分,留下许多难以利用的很小的空闲分区(碎片或零头) ,而每次查找又都是从低地址部分开始,增加了查找可用空闲分区的开销。
- 下次适应算法(Next Fit):把存储空间中空闲区构成一个循环链表,每次为存储请求查找合适的分区时,总是从上次查找结束的地方开始,只要找到一个足够大的空闲区,就将它划分后分配出去。
- 特点:使存储空间的利用更加均衡,不致使小的空闲区集中在存储区的一端,但这会导致缺乏大的空闲分区。
- 最佳适应算法(Best Fit):为一个作业选择分区时,总是寻找其大小最接近于作业所要求的存储区域。
- 特点:产生许多小碎片。若存在与作业大小一致的空闲分区,则它必然被选中,若不存在与作业大小一致的空闲分区,则只分配比作业稍大的空闲分区,从而保留了大的空闲分区。最佳适应算法往往使剩下的空闲区非常小,从而在留下许多难以利用的小空闲区(碎片) 。
- 最坏适应算法(Worst Fit):为作业选择存储区域时,总是寻找最大的空闲区。
- 特点:无法满足后期大作业。总是挑选满足作业要求的最大的分区分配给作业。这样使分给作业后剩下的空闲分区也较大,可装下其它作业。由于最大的空闲分区总是因首先分配而划分,当有大作业到来时,其存储空间的申请往往会得不到满足。
- 首次适应算法(First Fit):每个空闲区按其在存储空间中地址递增的顺序连在一起,在为作业分配存储区域时,从这个空闲区域链的始端开始查找,选择第一个足以满足请求的空白块。
-
基于索引搜索的分配算法
基于顺序搜索的动态分区分配算法一般只是适合于较小的系统,如果系统的分区很多,空闲分区表(链)可能很大(很长) ,检索速度会比较慢。为了提高搜索空闲分区的速度,大中型系统采用了基于索引搜索的动态分区分配算法。
- 快速适应算法
- 快速适应算法,又称为分类搜索法,把空闲分区按容量大小进行分类,常用大小的空闲区设立单独的空闲区链表。系统为多个空闲链表设立一张管理索引表。
- 优点:查找效率高,仅需要根据程序的长度,寻找到能容纳它的最小空闲区链表,取下第一块进行分配即可。该算法在分配时,不会对任何分区产生分割,所以能保留大的分区,也不会产生内存碎片。
- 缺点:分区回收算法复杂(链表的插入、分区合并等),系统开销较大。在分配空闲分区时是以进程为单位,一个分区只属于一个进程,存在一定的浪费。(空间换时间)
- 伙伴系统(Linux系统)
- 固定分区方式不够灵活,当进程大小与空闲分区大小不匹配时,内存空间利用率很低。
- 动态分区方式算法复杂,回收空闲分区时需要进行分区合并等,系统开销较大。
- 伙伴系统(buddy system)是介于固定分区与可变分区之间的动态分区技术。
- 伙伴:在分配存储块时将一个大的存储块分裂成两个
- 快速适应算法
-
系统中碎片
-
内部碎片:分配给作业的存储空间内未被利用的部分。只有作业完成后才能被释放。
外部碎片:分区与分区间无法利用的分区。 -
消除外部碎片的方法:紧凑技术(Compaction)
- 通过移动作业,把多个分散的小分区拼接成一个大分区的方法称为紧凑(拼接或紧缩) 。
- 实现支撑:动态重定位:作业在内存中的位置发生了变化,这就必须对其地址加以修改或变换。
-
-
多重分区分配
多重分区分配:为了支持结构化程序设计,操作系统往往把一道作业分成若干片段(如子程序、主程序、数据组等)。这样,片段之间就不需要连续了。只要增加一些重定位寄存器,就可以有效地控制一道作业片段之间的调用。
-
分区的存储保护
存储保护是为了防止一个作业有意或无意地破坏操作系统或其它作业。常用的存储保护方法有
-
界限寄存器方法:
- 上下界寄存器方法
- 基址、限长寄存器(BR,LR) 方法
-
存储保护键方法:
- 给每个存储块分配一个单独的保护键,它相当于一把锁。
- 进入系统的每个作业也赋予一个保护键,它相当于一把钥匙。
- 当作业运行时,检查钥匙和锁是否匹配,如果不匹配,则系统发出保护性中断信号,停止作业运行。
-
3.2.5 覆盖与交换-解决大作业在小内存中运行的问题
-
覆盖:把一个程序划分为一系列功能相对独立的程序段,让执行时不要求同时装入内存的程序段组成一组(称为覆盖段),共享主存的同一个区域,这种内存扩充技术就是覆盖。
- 主要用在早期的OS 中(内存<64KB) ,可用的存储空间受限,某些大作业不能一次全部装入内存,产生了大作业与小内存的矛盾。
- 程序段先保存在磁盘上,当有关程序段的前一部分执行结束,把后续程序段调入内存,覆盖前面的程序段(内存“扩大”了) 。
- 一般要求作业各模块之间有明确的调用结构,程序员向系统指明覆盖结构,然后由os完成自动覆盖。
- 缺点:用户不透明。
-
交换:把暂时不用的某个(或某些)程序及其数据的部分或全部从主存移到辅存中去,以便腾出存储空间;接着把指定程序或数据从辅存读到相应的主存中,并将控制转给它,让其在系统上运行。
- 优点:增加并发运行的程序数目,并且给用户提供适当的响应时间;编写程序时不影响程序结构
- 缺点:对换入和换出的控制增加处理机开销;程序整个地址空间都进行传送,没有考虑执行过程中地址访问的统计特性。
-
交换技术的几个问题
- 选择原则,即将哪个进程换出内存?
- 等待I/O的进程
- 交换时机的确定,何时需发生交换?
- 只要不用就换出(很少再用)
- 只在内存空间不够或有不够的危险时换出
- 交换时需要做哪些工作?
- 保存前一个进程的运行现场:寄存器、堆栈等
- 创建新进程的运行现场
- 换入回内存时位置的确定
- 选择原则,即将哪个进程换出内存?
-
覆盖与交换技术的区别
- 覆盖可减少一个程序运行所需的空间。交换可让整个程序暂存于外存中,让出内存空间。
- 覆盖是由程序员实现的,操作系统根据程序员提供的覆盖结构来完成程序段之间的覆盖。交换技术不要求程序员给出程序段之间的覆盖结构。
- 覆盖技术主要对同一个作业或程序进行。交换主要在作业或程序间之间进行。
- 覆盖:减少一个程序本身所占用的内存
交换:减少多个程序同时占用的内存
3.3 页式内存管理
3.3.1 补充知识
- 程序、进程、作业
- 程序是静止的,是存放在磁盘上的可执行文件
- 进程是动态的。进程包括程序和程序处理对象(数据集),是一个程序的执行过程,是分配资源的基本单位。通常把进程分为系统进程和用户进程:
- 系统进程:完成操作系统功能的进程;
- 用户进程:完成用户功能的进程。
- 作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。
- 三者联系:
- 作业通常包括程序、数据和操作说明书3部分。
- 每一个进程由进程控制块PCB、程序和数据集合组成。这说明程序是进程的一部分,是进程的实体。
- 因此,一个作业可划分为若干个进程来完成,而每一个进程有其实体——程序和数据集合。
3.3.2 基本原理
- 分区式内存管理缺点
- 内存的碎片问题:无论固定分区,还是可变分区都普遍存在的问题
- 内存紧缩
- 程序需要装入连续的物理内存空间
- 小内存运行大程序:覆盖、交换
- 很难满足程序对内存空间的动态需求
- 很难找到一个绝对有效的内存分配/回收策略
- First Fit/Next Fit/Best Fit/Worst Fit
- Buddy System
- 内存利用率低
- 内存的碎片问题:无论固定分区,还是可变分区都普遍存在的问题
- 分页式存储管理的基本思想
- 基本思想:把一个逻辑地址连续的程序分散存放到若干不连续的内存区域内,并保证程序的正确执行。
- 带来的好处:既可充分利用内存空间,又可减少移动带来的开销。
- 纯分页系统
- 不具备页面对换功能的分页存储管理方式,不支持虚拟存储器功能,这种存储管理方式称为纯分页或基本分页存储管理方式。(v.s. 请求分页)
- 在调度一个作业时,必须把它的所有页一次装到主存的页框内;如果当时页框数不足,则该作业必须等待,系统再调度另外作业。
- 优点:
- 没有外碎片,每个内碎片不超过页大小
- 一个程序不必连续存放
- 便于改变程序占用空间的大小
- 缺点:
- 程序全部装入内存;有内碎片
3.3.3 基本概念:页表、地址变换、多级页表、快表
-
基本概念
- 页:把每个作业的地址空间分成一些大小相等的片,称之为**页面(Page)**或页,各页从0开始编号。
- 存储块:把物理内存的存储空间也分成与页面相同大小的片,这些片称为存储块,或称为页框(Frame),同样从0开始编号。
- 页表:为便于在内存找到进程的每个页面所对应的页框,分页系统中为每个进程配置一张页表。进程逻辑地址空间的每一页,在页表中都对应有一个页表项。
-
页面大小辨析
- 若页面较小
- 减少页内碎片和总的内存碎片,有利于提高内存利用率。
- 每个进程页面数增多,使页表长度增加,占用内存较大。
- 页面换进换出速度将降低。
- 若页面较大
- 增加页内碎片,不利于提高内存利用率。
- 每个进程页面数减少,页表长度减少,占用内存较小。
- 页面换进换出速度将提高。
-
关于页表
-
页表存放在内存中,属于进程的现场信息。
-
用途:
-
记录进程的内存分配情况
-
实现进程运行时的动态重定位。
-
-
访问一个数据需访问内存2 次(页表一次,内存一次)。
-
页表的基址及长度由页表寄存器给出。
-
-
二级页表
-
一级页表存在问题:逻辑地址大,页表大,占用存储空间(要求连续)大。
-
将页表再进行分页,离散地将各个页表页面存放在不同的物理块中,同时也再建立一个外层页表用以记录页表页面对应的物理块号。
-
正在运行的进程先把外层页表(页表的页表)调入内存,而后动态调入内层页表。只将当前所需的一些内层页表装入内存,其余部分根据需要再陆续调入。
-
练习题:
-
存在的问题: 内存访问效率降低
-
-
快表TLB
-
有效内存访问时间计算(一级页表)
TLB查询时间 ε \varepsilon ε 单次内存访问时间 τ \tau τ TLB命中率 α \alpha α
有效内存访问时间EAT=(τ+ε)α + (2τ+ε)(1-α) = 2τ + ε - τα
-
例题:
-
3.3.4 页表类型:哈希页表、反置页表
-
哈希页表(Hashed Page Table)
- 过程:将虚拟页号转换为哈希值,据此访问哈希表的表项(链表)。用虚拟页号与链表中的元素的第一个域相比较。如果匹配,那么相应的页框号(第二个域)就用来形成物理地址;如果不匹配,那么就进一步比较链表的下一个节点,以找到匹配的页号。
-
反置页表(Inverted Page Table)
- 原因:每个页表可能有很多项。这些表可能消耗大量物理内存,却仅用来跟踪物理内存是如何使用的。
- 原理:反置页表不是依据进程的逻辑页号来组织,而是依据该进程在内存中的页框号来组织(即:按页框号排列),其表项的内容是逻辑页号P及隶属进程标志符pid 。
- 反置页表的大小只与物理内存的大小相关,与逻辑空间大小和进程数无关。如: 64M主存,若页面大小为4K,则反向页表只需64KB。
- 地址变换过程:
- 用进程标志符和页号去检索反置页表。
- 如果检索完整个页表未找到与之匹配的页表项,表明此页此时尚未调入内存,对于具有请求调页功能的存储器系统产生请求调页中断,若无此功能则表示地址出错。
- 如果检索到与之匹配的表项,则表项的序号i 便是该页的物理块号,将该块号与页内地址一起构成物理地址。
- 反置页表按照物理地址排序,而查找依据虚拟地址,所以可能需要查找整个表来寻找匹配。 可以通过哈希页表限制页表条目或加入TLB改善。通过哈希表(hash table)查找可由逻辑页号得到物理页面号。虚拟地址中的逻辑页号通过哈希表指向反置页表中的表项链头(因为哈希表可能指向多个表项),得到物理页面号。
- 采用反置页表的系统很难共享内存,因为每个物理帧只对应一个虚拟页条目。
3.3.5 页共享与保护
- 页式存储管理系统提供了两种方式:
- 地址越界保护
- 在页表中设置保护位(定义操作权限:只读,读写,执行等)共享带来的问题
- 若共享数据与不共享数据划在同一页框中,则:
- 有些不共享的数据也被共享,不易保密。
- 实现数据共享的最好方法:分段存储管理。
3.4 段式内存管理
3.4.1基本原理
-
段式存储管理的主要动机
-
**方便编程:**通常一个作业是由多个程序段和数据段组成的,用户一般按逻辑关系对作业分段,并能根据名字来访问程序段和数据段。
-
**信息共享:**共享是以信息的逻辑单位为基础的。页是存储信息的物理单位,段是信息的逻辑单位。页式管理中地址空间是一维的,主程序、子程序都顺序排列,共享公用子程序比较困难,一个共享过程可能需要几十个页面。
-
**信息保护:**页式管理中,一个页面中可能装有2 个不同的子程序段的指令代码,不能通过页面共享实现一个逻辑上完整的子程序或数据块的共享。段式管理中,可以使用信息的逻辑单位进行保护。
-
**动态增长:**实际应用中,某些段(数据段)会不断增长,前面的存储管理方法均难以实现。
-
**动态链接:**动态链接在程序运行时才把主程序和要用到的目标程序(程序段)链接起来。
-
-
基本思想
- 一个段可定义为一组逻辑信息,每个作业的地址空间是由一些分段构成的(由用户根据逻辑信息的相对完整来划分),每段都有自己的名字(通常是段号),且都是一段连续的地址空间,首地址为0。
3.4.2地址变换
-
地址变换
-
段表记录了段与内存位置的对应关系。
-
段表保存在内存中。
-
段表的基址及长度由段表寄存器给出。
-
访问一个字节的数据/指令需访问内存两次(段表一次,内存一次)。
-
逻辑地址由段和段内地址组成。
-
-
地址变换过程
-
将逻辑地址中的段号S 与段表长度TL 进行比较。
-
若S>TL,表示访问越界,产生越界中断。
- 若未越界,则根据段表的始址和该段的段号,计算出该段对应段表项的位置,从中读出该段在内存的始址。
- 再检查段内地址d,是否超过该段的段长SL。
- 若超过,即d >SL,同样发出越界中断信号。
-
- 若未越界,则将该段的基址与段内地址d 相加,即可得到要访问的内存物理地址。
-
- 补充知识:可重入代码
- 可重入代码
(Reentrant Code)
又称为“纯代码”(Pure Code)
,是指在多次并发调用时能够安全运行的代码。要求:不能使用全局/静态变量;代码不能修改代码本身;不能调用其他的不可重入代码。
- 可重入代码
3.4.4与页式管理优缺点对比
-
分段管理优缺点
- 优点:
- 分段系统易于实现段的共享,对段的保护也十分简单。
- 更好地支持动态的内存需求
- 缺点:
- 处理机要为地址变换花费时间;要为段表提供附加的存储空间。
- 为满足分段的动态增长和减少外碎片,要采用内存紧凑的技术手段。
- 在辅存中管理不定长度的分段比较困难(交换)。
- 分段的最大尺寸受到主存可用空间的限制。
- 优点:
-
分页与分段的比较
-
分页的作业的地址空间是单一的线性地址空间(注:页式管理所用的一维的虚拟地址也称为线性地址),分段作业的地址空间是二维的。
-
“页”是信息的“物理”单位,大小固定。“段”是信息的逻辑单位,即它是一组有意义的信息,其长度不定。
-
分页对用户是透明的,是系统对于主存的管理。分段是用户可见的(分段可以在用户编程时确定,也可以在编译程序对源程序编译时根据信息的性质来划分)。
-
3.4.5 段页式管理
-
实现原理
-
基本思想:用分段方法来分配和管理虚拟存储器,而用分页方法来分配和管理物理存储器。
-
段页式存储管理:分段和分页原理的结合,即先将用户程序分成若干个段(段式) ,并为每一个段赋一个段名,再把每个段分成若干个页(页式) 。
-
地址结构由段号、段内页号、及页内位移三部分所组成。
-
系统中设段表和页表,均存放于内存中。读一次指令或数据须访问内存三次。为提高速度可增设高速缓存。
-
每个进程一张段表,每个段一张页表。
-
段表含段号、页表始址和页表长度;页表包含页号和页框号。
-
3.5 虚拟内存管理
- 常规存储的问题
- 常规存储管理方式的特征:
- 一次性:要求一个作业全部装入内存后方能运行。
- 驻留性:作业装入内存后一直驻留内存,直至结束。
- 可能出现的问题:
- 有的作业很大,所需内存空间大于内存总容量,使作业无法运行。
- 有大量作业要求运行,但内存容量不足以容纳下所有作业,只能让一部分先运行,其它在外存等待。
- 已有解决方法:
- 增加内存容量
- 从逻辑上扩充内存容量:覆盖、对换。
- 常规存储管理方式的特征:
3.5.1 局部性原理
-
程序的局部性
-
程序在执行时,大部分是顺序执行的指令,少部分是转移和过程调用指令。(空间局部性)
-
过程调用的嵌套深度有限,因此执行的范围不超过这组嵌套的过程。(空间局部性)
-
程序中存在很多对特定数据结构的操作,如数组操作,往往局限在较小范围内。(空间局部性)
-
程序中存在很多循环结构,它们由少量指令组成,而被多次执行。(时间局部性)
-
-
局部性原理
-
指程序在执行过程中的一段较短时间内,所执行的指令地址和指令的操作数地址分别局限于一定区域。具体表现为:
-
时间局部性:即一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一段较短时间内;
-
空间局部性:即当前指令和邻近的几条指令,当前访问的数据和邻近的数据都集中在一个较小区域内。
-
-
3.5.2 请求式分页
-
虚拟存储
-
目标**(与覆盖和交换的关系)**:
- 借鉴覆盖技术:不必把程序的所有内容都放在内存中,因而能够运行比当前的空闲内存空间还要大的程序。
- 与覆盖不同:由操作系统自动完成,对程序员是透明的。
- 借鉴交换技术:能够实现进程在内存与外存之间的交换,因而获得更多的空闲内存空间。
- 与交换不同:只将进程的部分内容(更小的粒度,如分页)在内存和外存之间进行交换。
-
原理:
- 在程序装入时,不需要将其全部读入到内存,而只需将当前需要执行的部分页或段读入到内存,就可让程序开始执行。
- 在程序执行中,如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段) ,则由处理器通知操作系统将相应的页或段调入到内存,然后继续执行程序。(请求调入功能)
- 操作系统将内存中暂时不使用的页或段调出保存在外存上,从而腾出空间存放将要装入的程序以及将要调入的页或段。(置换功能)
-
特征:
-
离散性: 物理内存分配的不连续,虚拟地址空间使用的不连续(数据段和栈段之间的空闲空间,共享段和动态链接库占用的空间)
-
多次性: *作业被分成多次调入内存运行。*正是由于多次性,虚拟存储器才具备了逻辑上扩大内存的功能。多次性是虚拟存储器最重要的特征,其它任何存储器不具备这个特征。
-
**对换性:**允许在作业运行过程中进行换进、换出。换进、换出可提高内存利用率。
-
**虚拟性:**允许程序从逻辑的角度访问存储器,而不考虑物理内存上可用的空间容量。
- 范围大,但占用容量不超过物理内存和外存交换区容量之和。
- 占用容量包括:进程地址空间中的各个段,操作系统代码。
-
虚拟性以多次性和对换性为基础,
多次性和对换性必须以离散分配为基础。
-
-
优缺点:
-
优点:
-
**小内存大程序:**可在较小的可用(物理)内存中执行较大的用户程序;
-
**支持更多并发:**可在(物理)内存中容纳更多程序并发执行;
-
不必影响编程时的程序结构(与覆盖技术比较)
-
**虚拟内存通常远大于物理内存:**提供给用户可用的虚拟内存空间通常大于物理内存
-
-
代价:虚拟存储量的扩大是以牺牲CPU 处理时间以及内外存交换时间为代价。
-
限制:虚拟内存的最大容量主要由计算机的地址结构决定。例如: 32 位机器的虚拟存储器的最大容量就是4GB。
-
-
与Cache异同:
-
相同点:
-
出发点相同:二者都是为了提高存储系统的性能价格比而构造的分层存储体系,都力图使存储系统的性能接近高速存储器,而价格和容量接近低速存储器。
-
原理相同:都是利用了程序运行时的局部性原理把最近常用的信息块从相对慢速而大容量的存储器调入相对高速而小容量的存储器。
-
-
不同点:
-
侧重点不同:cache主要解决主存与CPU的速度差异问题;虚存主要解决存储容量问题,另外还包括存储管理、主存分配和存储保护等。
-
数据通路不同:CPU与cache和主存之间均有直接访问通路,cache不命中时可直接访问主存;而虚存所依赖的辅存与CPU之间不存在直接的数据通路,当主存不命中时只能通过调页解决,CPU最终还是要访问主存。
-
透明性不同:cache的管理完全由硬件完成,对系统程序员和应用程序员均透明;而虚存管理由软件(OS)和硬件共同完成,由于软件的介入,虚存对实现存储管理的系统程序员不透明,而只对应用程序员透明(段式和段页式管理对应用程序员“半透明”)。
-
未命中时的损失不同:由于主存的存取时间是cache的存取时间的5~10倍,而主存的存取速度通常比辅存的存取速度快上千倍,故主存未命中时系统的性能损失要远大于cache未命中时的损失。
-
-
-
-
请求分页(段)系统
-
请求分页与请求分段系统的比较:
-
虚存机制要解决的关键问题:
- 地址映射问题:进程空间到虚拟存储的映射问题;
- 调入问题:决定哪些程序和数据应被调入主存,以及调入机制。
- 替换问题:决定哪些程序和数据应被调出主存。
- 更新问题:确保主存与辅存的一致性。
- 其它问题:存储保护与程序重定位等问题
-
请求分页式管理
-
基本概念:
- 进程的逻辑空间(虚拟地址空间):一个进程的逻辑空间的建立是通过链接器(Linker),将构成进程所需要的所有程序及运行所需环境,按照某种规则装配链接而形成的一种规范格式(布局),这种格式按字节从0开始编址所形成的空间也称为该进程的逻辑地址空间。由于该逻辑空间并不是物理上真实存在的,所以也称为进程的虚拟地址空间。
- 如:Hello World进程包含Hello World可执行程序、printf函数(所在的)共享库程序以及OS相关程序。
- 虚拟地址空间和虚拟存储空间:进程的虚拟地址空间即为进程在内存中存放的逻辑视图。因此,一个进程的虚拟地址空间的大小与该进程的虚拟存储空间的大小相同,都从0开始编址。
- 交换分区(交换文件):
- 一段连续的磁盘空间(按页划分的),并且对用户不可见。它的功能就是在物理内存不够的情况下,操作系统先把内存中暂时不用的数据,存到硬盘的交换空间,腾出物理内存来让别的程序运行。
- 在Linux系统中,交换分区为swap;在Windows系统中则以文件的形式存在(pagefile.sys)。
- 交换分区的大小:交换分区的大小应当与系统物理内存(M)的大小保持线性比例关系(Linux中):
- 进程的逻辑空间(虚拟地址空间):一个进程的逻辑空间的建立是通过链接器(Linker),将构成进程所需要的所有程序及运行所需环境,按照某种规则装配链接而形成的一种规范格式(布局),这种格式按字节从0开始编址所形成的空间也称为该进程的逻辑地址空间。由于该逻辑空间并不是物理上真实存在的,所以也称为进程的虚拟地址空间。
-
地址映射问题
进程空间到虚存空间的映射(进程的虚存分配)
-
在程序装入时,由装载器(Loader)完成。
-
分配是以段为单位(需页对齐)进行的。
-
事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,同时建立好虚拟内存和磁盘文件之间的映射(即存储器映射) 。实际上,并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,等到运行对应的程序时,才会通过缺页异常,来拷贝数据。
-
用户可执行文件(如Hello World可执行文件)及共享库(如HelloWorld中调用的库文件中的printf函数)都是以文件的形式存储在磁盘中,初始时其在页表中的类型为file backed,地址为相应文件的位置。
-
堆(heap)和栈(stack)在磁盘上没有对应的文件,页表中的类型为anonymous,地址为空。
-
未分配部分没有对应的页表项,只有在申请时(如使用malloc( )申请内存或用mmap( )将文件映射到用户空间)才建立相应的页表项。
-
-
请求式分页管理的页表
-
页面调入问题
-
页面面调入策略:按需调页
- 也称请求式调页(demand paging)
- 当且仅当需要某页时才将其调入内存的技术称为按需调页,被虚拟内存系统采用。
- 按需调页系统类似于使用交换的分页系统,进程驻留在外存(磁盘),进程执行时使用懒惰交换(lazy swapper)换入内存。一次装入请求的一个页面,磁盘I/O的启动频率较高,系统的开销较大。
- 按需调页需要使用外存,保存不在内存中的页,通常为快速磁盘,用于和内存交换页的部分空间称为交换空间(swap space)。
-
页面调入策略-预调页
- 当进程开始时,所有页都在磁盘上,每个页都需要通过**页错误(Page Fault,也称缺页异常)**来调入内存。预调页同时将所需要的所有页一起调入内存,从而阻止了大量的页错误。部分操作系统如Solaris 对小文件就采取预调页调度。
- 实际应用中,可以为每个进程维护一个当前**工作集合(working set,简称工作集)**中的页的列表,如果进程在暂停之后需要重启时,根据这个列表使用预调页将所有工作集合中的页一次性调入内存。
- 若程序执行的局部性较差,则预先装入的很多页面不会很快被引用,并会占用大量的内存空间,反而降低系统的效率。
-
-
缺页错误处理过程
当进程执行中需访问的页面不在物理内存中时,会引发发生缺页中断(也称缺页异常),进行所需页面换入,步骤如下:
- 陷入内核态,保存必要的信息(OS及用户进程状态相关的信息)。(现场保护)
- 查找出发生缺页中断的虚拟页面(进程地址空间中的页面)。这个虚拟页面的信息通常会保存在一个硬件寄存器中,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析该指令,通过分析找出发生页面中断的虚拟页面。(页面定位)
- 检查虚拟地址的有效性及安全保护位。如果发生保护错误,则杀死该进程。(权限检查)
- 查找一个空闲的页框(物理内存中的页面),如果没有空闲页框则需要通过页面置换算法找到一个需要换出的页框。(新页面调入 1 ^1 1)
- 如果找的页框中的内容被修改了,则需要将修改的内容保存到磁盘上¹。(注:此时需要将页框置为忙状态,以防页框被其它进程抢占掉)(旧页面写回)
- 页框“干净”后,操作系统将保持在磁盘上的页面内容复制到该页框中²。(新页面调入 2 ^2 2)
- 当磁盘中的页面内容全部装入页框后,向CPU发送一个中断。操作系统更新内存中的页表项,将虚拟页面映射的页框号更新为写入的页框,并将页框标记为正常状态。(更新页表)
- 恢复缺页中断发生前的状态,将程序指针重新指向引起缺页中断的指令。(恢复现场)
- 程序重新执行引发缺页中断的指令,进行存储访问。(继续执行)
缺页处理过程涉及了用户态和内核态之间的切换,虚拟地址和物理地址之间的转换(这个转换过程需要使用MMU和TLB)
¹,² :此时会引起一个写磁盘调用,发生上下文切换(在等待磁盘写的过程中让其它进程运行)。
-
3.5.3 页面置换
-
最优置换(OPT算法)
-
所有页置换算法中页错误率最低,但它需要引用串(即页面访问序列)的先验知识,因此无法实现。
-
算法将内存中的页P置换掉,页P满足:从现在开始到未来某刻再次需要页P,这段时间最长。也就是OPT 算法会置换掉未来最久不被使用的页。
-
OPT 算法通常用于比较性研究,衡量其他页置换算法的效果。
-
-
先进先出(First-in, First-out)
-
最简单的页置换算法,系统记录每个页被调入内存的时间,当必需置换掉某页时,选择最旧的页换出。具体实现如下:
- 新访问的页面插入FIFO队列尾部,页面在FIFO队列中顺序移动;
- 淘汰FIFO队列头部的页面;
-
性能较差。较早调入的页往往是经常被访问的页,这些页在FIFO算法下被反复调入和调出。可能出现Belady异常现象。
-
Belady异常现象 :在使用FIFO算法作为缺页置换算法时,随着分配的页框增
多,缺页率反而提高注意:虽然这种现象说明的场景是缺页置换,但在运用FIFO算法作为缓存替换算法时,同样也会遇到,即增加缓存容量,但缓存命中率反而会下降的情况。
-
-
改进的FIFO算法—Second Chance
-
每个页面会增加一个访问标志位,用于标识此数据装入内存后是否被再次访问过。
-
A是FIFO队列中最旧的页面,且其放入队列后没有被再次访问,则A被立刻淘汰;否则,如果放入队列后被访问过,则将A移到FIFO队列尾部,并且将访问标志位清除。
-
如果所有页面都被访问过,则经过一次循环后就按FIFO原则淘汰。
-
-
改进的FIFO算法— Clock
- Clock是Second Chance的改进版。通过一个环形队列,避免将数据在FIFO队列中移动。算法如下:
- 如果没有缺页错误,将所访问页的访问位置1,指针不动;
- 如果产生缺页错误
(1)如果当前页面的访问位是1:首先将当前页面的访问位置0,将指针向
前移一个位置;重复这个过程,直到找到访问位为0的页面,然后转(2)。
(2)如果当前页面的访问位是0:则替换当前页面, 并将其访问位置为1,
并将指针向前移动一个位置。
FIFO类算法对比:
- 最近最少使用LRU-Least Recently Used
- LRU算法根据数据的历史访问记录来淘汰页面,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
- LRU是局部性原理的合理近似,性能接近OPT算法。但需记录页面使用的先后关系,实现开销大。
- 一种软件的实现方法:链表法
- 设置一个链表,链表节点保存当前使用的页面的页号。
- 每当有一次内存访问(读/写),根据相应的页面号查找链表,如果找到,则把相应的节点移到链表头部;如果没有找到,创建一个新的节点,插入到链表头部。
- 链表尾部为“最久未被使用”的页面,即淘汰的目标。
- 一种硬件的实现方法:计数器
- 设置指令计数器,每个页面在被访问时读取计数器,并记录相应数值。淘汰计数值最小的页面
- 一种软件的实现方法:链表法
3.5.4 关键设计问题
-
工作集与驻留集管理
-
工作集(working set):进程运行正在使用的页面集合。
-
进程的驻留集(Resident Set):虚拟存储系统中,每个进程驻留在内存的页面集合,或进程分到的物理页框集合。
-
引入工作集的目的是依据进程在过去的一段时间内访问的页面来调整驻留集大小。
-
工作集的动态变化
- 进程开始执行后,随着不断访问新页面逐步建立较稳定的工作集。
- 当内存访问的局部性区域的位置大致稳定时,工作集大小也大致稳定;
- 局部性区域的位置改变时,工作集快速扩张和收缩过渡到下一个稳定值。
-
工作集算法
- 给定一个进程,记录其工作集
- 当需要进行页面替换时,选择不在工作集中的页面进行替换
-
-
进程驻留集管理主要解决的问题:系统应当为每个活动进程分配多少个页框。
-
影响页框分配的主要因素:
- 分配给每个活动进程的页框数越少,能够驻留内存的活动进程数就越多,进程调度程序能调度就绪进程的概率就越大,然而,这将导致进程发生缺页中断的概率较大;
- 为进程分配过多的页框,并发运行的进程数降低,影响系统资源利用率。
- 分配给每个活动进程的页框数越少,能够驻留内存的活动进程数就越多,进程调度程序能调度就绪进程的概率就越大,然而,这将导致进程发生缺页中断的概率较大;
-
-
页面分配策略
-
固定分配策略:为每个活动进程分配固定数量的页框。即每个进程的驻留集大小在运行期间固定不变。
-
可变分配策略:为每个活动进程分配的页框数在其生命周期内是可变的。即系统首先给进程分配一定数量的页框,运行期间可以增/减页框。
可根据进程的缺页率调整进程的驻留集。当缺页率很高时,驻留集太小,需增加页框;当缺页率一段时间内都保持很低时,可以在不会明显增加进程缺页率的前提下,回收其一部分页框,减小进程的驻留集。
-
-
页面置换策略
- 当发生缺页中断且无足够的内存空间时,需要置换已有的某些(个)页面。应该解决的基本问题:
- 当系统要把一个页面装入内存时,应当在什么范围内判断已经没有空闲页框供分配?
- 当指定的范围内没有空闲页框时,应当从哪里选择哪个页面移出内存?
- 局部置换策略:系统在进程自身的驻留集中判断当前是否存在空闲页框,并在其中进行置换。
- 全局置换策略:在整个内存空间内判断有无空闲页框,并允许从其它进程的驻留集中选择一个页面换出内存。
- 当发生缺页中断且无足够的内存空间时,需要置换已有的某些(个)页面。应该解决的基本问题:
-
内存块初始分配方法
-
等分法:为每个进程分配存储块的最简单的办法是平分,即若有m块、n个进程,则每个进程分m/n块(其值向下取整)。
-
比例法:分给进程的块数=(进程地址空间大小/全部进程的总地址空间)*可用块总数
-
优先权法:为加速高优先级进程的执行,可以给高优先级进程分较多内存。如使用比例分配法时,分给进程的块数不仅取决于程序的相对大小,而且也取决于优先级的高低。
-
-
抖动问题(thrashing)
-
定义:
-
随着驻留内存的进程数目增加,即进程并发程度的提高,处理器利用率先上升,然后下降。
-
这里处理器利用率下降的原因通常称为虚拟存储器发生“抖动”,也就是:每个进程的驻留集不断减小,当驻留集小于工作集后,缺页率急剧上升,频繁调页使得调页开销增大。
-
因此,OS要选择一个适当的进程数目,以在并发水平和缺页率之间达到一个平衡。
-
-
抖动的消除与预防
-
局部置换策略:如果一个进程出现抖动,它不能从另外的进程那里夺取内存块,从而不会引发其他进程出现抖动,使抖动局限于一个小的范围内。然而这种方法并未消除抖动的发生。(微观层面)
-
引入工作集算法(微观)
-
预留部分页面(微观或宏观)
-
挂起若干进程:当出现CPU利用率、而磁盘I/O非常频繁的情况时,就可能因为多道程序度太高而造成抖动。为此,可挂起一个或几个进程,以便腾出内存空间供抖动进程使用,从而消除抖动现象。(宏观)
-
-
-
负载控制
-
多道程序系统允许多个进程同时驻留内存,以提高系统吞吐量和资源利用率。
-
如果同时驻留的进程数量太多,每个进程都竞争各自需要的资源,反而会降低系统效率。将导致每个进程的驻留集太小,发生缺页中断的概率很大,系统发生抖动的可能性就会很大。
-
如果在内存中活动进程太少,则所有活动进程同时处于阻塞状态的可能性就会很大,从而降低处理机利用率。
-
-
负载控制主要解决系统应当保持多少个活动进程驻留在内存的问题,即控制多道程序系统的度。当内存中的活动进程数太少时,负载控制将增加新进程或激活一些挂起进程进入内存;反之,当内存中的进程数太多时,负载控制将暂时挂起一些进程,减少内存中的活动进程数。
-
-
系统负载的判断
-
L = S准则:通过调整多道程序的度,使发生两次缺页之间的平均时间(L)等于处理一次缺页所需要的平均时间(S)。即平均地,一次缺页处理完毕,再发生下一次缺页。此时,处理机的利用率将达到最大。
-
50%准则:当分页单元的利用率保持在50%左右时,处理机的利用率将达到最大。如果系统使用基于CLOCK置换算法的全局置换策略,可通过监视扫描指针的移动速率来调整系统负载。如果移动速率低于某个给定的阀值,则意味着近期页面失败的次数较少,此时可以增加驻留内存的活动进程数。如果超出阀值,则近期页面失败的次数较多,此时,系统应当减少驻留内存的活动进程数。
-
-
页面清除策略
- 页面清除:将由置换算法选出的页面保存到外存。页面清除策略决定系统何时把被置换页面写回外存。
- 若被选中的置换页面被修改过,则需要将该页面内容写回外存,然后装入新页内容。可见,此时写出一个页面与读入一个新页的操作是成对出现的,而且,写出操作先于读入操作。所以,当正在执行的进程发生缺页中断时,需要阻塞等待一个页面的写出和另一个页面的读入,这可能降低处理机的使用效率。
- 一种有效的页面清除策略是结合页缓冲(PageBuffering)技术。当发生缺页中断时,不必首先写出置换页,而是将被选中的置换页暂时保留在内存的一个缓冲区,在以后某个合适的时候将被置换页批量写出到外存。减少磁盘I/O的次数,提高处理机的效率。
-
页面缓冲算法(page buffering)
- 该算法是对FIFO算法的发展,通过被置换页面的缓冲,有机会找回刚被置换的页面;
- 被置换页面的选择和处理:用FIFO算法选择被置换页,把被置换的页面放入两个链表之一。即:如果页面未被修改,就将其归入到空闲页面链表的末尾,否则将其归入到已修改页面链表。
-
改善时间性能的途径
- 降低缺页率:缺页率越低,虚拟存储器的平均访问时间增加得越小;
- 提高外存的访问速度:外存和内存的访问时间比值越大,则达到同样的时间延长比例,所要求的缺页率就越低;反之,如果提高外存访问速度,则相同缺页率下,系统时间性能会提高。
- 提高高速缓存命中率。
-
写时复制技术(copy-on-write)
- 写时复制的优点:
- 传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单而效率低下,因为它拷贝的数据也许并不共享,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。
- Linux的fork()使用写时复制(copy-on-write)实现,它可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会复制,从而使各个进程都拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行。
-
内存映射文件(Mem-Mapped File)
- 基本思想:进程通过一个系统调用(mmap)将一个文件(或部分)映射到其虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是对文件进行读写。
- 在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时,页面才会被每次一页的读入,磁盘文件则被当作后备存储。
- 当进程退出或显式地解除文件映射时,所有被修改页面会写回文件。
- 采用内存映射方式,可以方便地让多个进程共享一个文件。
3.6 页目录自映射
-
多级页表
-
多级页表的动机
-
节省页表所占用的内存空间
-
动态调入页表: 只将当前需用的部分页表项调入内存,其余的需用时再调入。
-
PS:
- 多级页表结构中,指令所给出的地址除偏移之外的各部分全是各级页表的页表号或页号,而各级页表中记录的全是物理页号,指向下级页表或真正的被访问页。
- 现在需要知道让某个二级页表中有一个指向自身物理地址的页表项(因为自身也属于4GB的一部分
-
-
页目录
- 页目录定义:页表页的地址映射
- 1024个页表页逻辑上连续,物理上可以分散,其对应逻辑-物理映射关系记录在页目录中
- 页目录占1页(4KB)空间,有1024项(页目录项),每一项指向一个页表页
- 每一页目录项对应4MB内存,1024个页目录项正好对应4GB内存(整个地址空间)
- 页目录和页表一共应该占据:4KB + 4MB地址空间
-
页目录自映射
-
关键点
- 存储页表的4MB地址空间中是整个4GB虚拟地址空间中的一部分,OS设计者可规定其所在位置(4MB对齐)
- 一方面根据页目录的定义:记录这4MB(连续)地址空间到物理地址空间映射关系的,是一个4KB的页目录
- 另一方面根据页表页的定义:记录这4MB(连续)地址空间到物理地址空间映射关系的,是一个4KB的页表页(当然,它属于整个4MB页表的一部分)
- 所以,页目录和上述页表页内容相同,页目录无需额外分配单独的存储空间
-4MB + 4KB→4MB?
-
构建方法
-
给定一个页表基址$PT_{base} $,该基址需4M对齐,即:
P T b a s e = (( P T b a s e ) > > 22 ) < < 22 ; PT_{base} =(( PT_{base})>> 22)<< 22; PTbase=((PTbase)>>22)<<22;
不难看出, P T b a s e PT_{base} PTbase的低22位全为0。 -
页目录表基址 P D b a s e PD_{base} PDbase在哪里?
P D b a s e = P T b a s e ∣ ( P T b a s e ) > > 10 PD_{base} = PT_{base} |(PT_{base})>>10 PDbase=PTbase∣(PTbase)>>10 -
自映射目录表项 P D E s e l f − m a p p i n g PDE_{self-mapping} PDEself−mapping在哪里?
P D E s e l f − m a p p i n g = P T b a s e ∣ ( P T b a s e ) > > 10 ∣ ( P T b a s e ) > > 20 PDE_{self-mapping} = PT_{base} |(PT_{base})>>10| (PT_{base})>>20 PDEself−mapping=PTbase∣(PTbase)>>10∣(PTbase)>>20
-
-
是不是一定要4M对齐?
- 如果仅考虑映射关系,不是必须的。
- 采用4M对齐,可使页目录表单独地存在于一个页面(页表)中,从使用方便性的角度,是必须的。
- 采用4M对齐,还可以简化计算,各部分地址可以采取“拼接”的方式。
-
特别强调
-
只要给定4M对齐的页表基址(虚拟地址),就可以得到所有页表项对应的地址,也就包括页目录表基址和自映射页目录项在页目录表中的位置。因此页目录表基址和自映射页目录项在虚空间中是计算出来的。
-
页表主要供OS使用的,因此页表和页目录表通常放置在OS空间中(如Win的高2G空间);
-
“页目录自映射”的含义是页目录包含在页表当中,是我们采用的映射(或组织)方法的一个特征,是虚拟地址空间内的映射,与虚拟地址到物理地址的映射无关!
-
支持“页目录自映射”可节省4K(虚拟地址)空间
-
-
多级页表情况
- 整个地址空间大小: 256 T B = 2 48 B 256 TB = 2^{48} B 256TB=248B
- 页大小4KB,每个页表项占8字节
- 页表数量:共有1个一级页表,
2
9
2^9
29个二级页表,
2
18
2^{18}
218个三
级页表, 2 27 2^{27} 227个四级页表 -
2
27
2^{27}
227个四级页表映射在整个地址空间中一段连续的
2
9
G
B
2^9GB
29GB
地址空间上,起始地址是0x8000 0000 0000 - 实际上, 2 18 2^{18} 218个三级页表也是连续的,是四级页表的一个子集; 2 9 2^{9} 29个二级页表也是连续的,是三级页表的一个子集(当然也是整个四级页表的子集);1个一级页表是上述二级页表的子集(当然最终也是整个四级页表的子集)。也就是说,那个一级页表是整个 2 27 2^{27} 227个四级页表中的一个。问题是:是哪个?
-
小结
- 自映射是Windows操作系统对内存管理的一种实现机制
- 对于32位地址空间来说:
- 前10位用于指定页目录号,因此一共有1个页目录,其中包括1024个页目录项,即对应1024个页表;页目录一共占 1024 ∗ 4 = 4 K B 1024*4 = 4KB 1024∗4=4KB空间。
- 中间10位用于指定页号,因此每个页表有1024个页表项,一共有 1024 ∗ 1024 1024*1024 1024∗1024个页表项;页表一共占 1024 ∗ 1024 ∗ 4 = 4 M B 1024*1024*4 = 4MB 1024∗1024∗4=4MB空间。
- 在自映射机制下,从4GB地址空间中拿出4MB空间存放1024个页表;同时,从1024个页表中拿出一个页表用于存放页目录;这样页目录和页表一共占4MB,而这4MB空间又是4GB空间的一部分,不必单独分配4MB + 4KB来存放页目录和页表。
- 核心问题:1024个页表中哪个页表用来存放页目录?页目录中的哪个页目录项指向页目录自身?
- 要求:保证虚拟地址是线性、连续的
-
4 进程管理
4.1 进程与线程
4.1.1 进程概念的引入
-
两个基本概念:并发与并行
- 顺序执行
- 并发Concurrent:设有两个活动a1和a2,如果在某一指定的时刻t,无论a1和a2是在同一处理机上还是在不同的处理机上执行,只要a1和a2都处在各自的起点和终点之间的某一处,则称a1和a2是并发执行的。
- 并行Parallel:如果考虑两个程序,它们在同一时间度量下同时运行在不同的处理机上,则称这两个程序是并行执行的。
-
程序顺序执行时的特征
- 顺序性:按照程序结构所指定的次序(可能有分支或循环)
- 封闭性:独占全部资源,计算机的状态只由该程序的控制逻辑所决定
- 可再现性:初始条件相同则结果相同。
-
程序并发执行时的特征
- 间断性:并发程序具有“执行—暂停----执行”这种间断性的活动规律。
- 非封闭性:多个程序共享系统中的资源,这些资源的状态将由多个程序来改变,致使程序之间相互影响。
- 不可再现性:在初始条件相同的情况下,程序的执行结果依赖于执行的次序。
-
并发性的确定-Bernstein条件
-
定义:
-
R(Si):Si的读子集, 其值在Si中被引用的变量的集合
-
W(Si):Si的写子集, 其值在Si中被改变的变量的集合
-
两个进程S1和S2可并发,当且仅当下列条件同时成立:
- R(S1) ∩ W(S2) = Φ
- W(S1) ∩ R(S2) = Φ
- W(S1) ∩ W(S2) = Φ
-
-
判断程序并发执行结果是否可再现的充分条件。
-
进程的定义和特征
-
进程是程序的一次执行;
-
进程是可以和别的计算并发执行的计算;
-
进程可定义为一个数据结构,及能在其上进行操作的一个程序;
-
进程是一个程序及其数据在处理机上顺序执行时所发生的活动;
-
进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
-
-
进程的特征
- 动态性:进程是程序的一次执行过程,因创建而产生,因调度而执行,因无资源而暂停,因撤消而消亡;而程序是静态实体。
- 并发性:多个进程实体同时存在于内存中,能在一段时间内同时运行。
- 独立性:在传统OS中,进程是独立运行的基本单位
- 异步性:也叫制约性,进程之间相互制约,进程以各自独立的不可预知的速度向前推进。
- 结构特征:程序、数据、进程控制块PCB
-
一个进程应该包括
- 程序的代码;
- 程序的数据;
- PC中的值,用来指示下一条将运行的指令;
- 一组通用的寄存器的当前值,堆、栈;
- 一组系统资源(如打开的文件)
-
进程与程序的区别
- 进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件,静态和可以复制。
- 进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可长久保存。
- 进程与程序的组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)。
- 进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。
4.1.2 进程状态与控制
-
原语
- 原语:由若干条指令所组成的指令序列,来实现某个特定的操作功能
- 指令序列执行是连续的,不可分割
- 是操作系统核心组成部分
- 必须在管态(内核态)下执行,且常驻内存
- 与系统调用的区别
- 不可中断
- 原语:由若干条指令所组成的指令序列,来实现某个特定的操作功能
-
Fork()函数
- 为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
a) 在父进程中,fork返回新创建子进程的进程ID;
b) 在子进程中,fork返回0;
c) 如果出现错误,fork返回一个负值; - fpid的值为什么在父子进程中不同。其实就相当于链表,进程形成了链表,父进程的fpid指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
- 为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
-
进程状态及其演变
-
就绪状态:进程已获得除处理机外的所需资源,等待分配处理机资源;只要分配CPU就可执行。
-
执行状态:占用处理机资源;处于此状态的进程的数目小于等于CPU的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的idle进程(相当于空操作)。
-
阻塞状态:正在执行的进程,由于发生某种事件而暂时无法执行,便放弃处理机处于暂停状态。
-
转换时刻:
-
就绪–> 运行
- 时间一到,调度程序选择一个进程运行
-
运行–> 就绪
-
运行进程用完了时间片
-
运行进程被中断,因为一高优先级进程处于就绪状态
-
-
运行–> 阻塞
- 当一进程所需的资源/条件必须等待时
- OS尚未完成服务
- 对一资源的访问尚不能进行
- 初始化I/O 且必须等待结果
- 等待某一进程提供输入(IPC)
-
阻塞–> 就绪
- 当所等待的事件发生时
-
-
进程控制块PCB
-
作用:
- 进程创建、撤消;
- 进程唯一标志;
-
进程控制块是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。
-
内容
-
进程标识符:每个进程都必须有一个唯一的标识符,可以是字符串,也可以是一个数字。Linux系统中就是一个整型数。在进程创建时由系统赋予。
-
程序和数据地址:把PCB与其程序和数据联系起来。
-
当前状态:为了管理的方便,系统设计时会将相同的状态的进程组成一个队列,如就绪进程队,等待进程则要根据等待的事件组成多个等待队列,如等待打印机队列、等待磁盘I/O完成队列等等。
-
现场保护区: 当进程因某种原因不能继续占用CPU时(等待打印机),释放CPU,这时就要将CPU的各种状态信息保护起来,为将来再次得到处理机恢复CPU的各种状态,继续运行。
-
同步与同步机制: 用于实现进程间互斥、同步和通信所需的信号量等。
-
优先级: 进程的优先级反映进程的紧迫程度,通常由用户指定和系统设置。Linux系统采用用户设置和系统计算相结合的方式确定进程的优先级。
-
资源清单: 列出所拥有的除CPU外的资源记录,如拥有的I/O设备、打开的文件列表等。
-
链接字: 根据进程所处的执行状态,进程相应的PCB加入到不同队列中。PCB链接字指出该进程所在队列中下一个进程PCB的首地址。
-
其他信息: 如进程记账信息,进程占用CPU的时间等。
在linux 中每一个进程都由task_struct 数据结构来定义,task_struct就是我们通常所说的PCB。
-
-
PCB组织方式
- 线性表:不论进程的状态如何,将所有的PCB连续地存放在内存的系统区。这种方式适用于系统中进程数目不多的情况。
- 索引方式:该方式是线性表方式的改进,系统按照进程的状态分别建立就绪索引表、阻塞索引表等。
- 链接方式:系统按照进程的状态将进程的PCB组成队列,从而形成就绪队列、阻塞队列、运行队列等。
-
-
辨析:进程上下文切换vs 陷入内核
- 进程上下文切换(Process Context Switch)
- 通常由调度器执行
- 保存进程执行断点
- 切换内存映射(页表基址、flush TLB)
- 陷入/退出内核(也称为模态切换, ModeSwitch)
- CPU状态改变
- 由中断、异常、Trap指令(系统调用)引起
- 需要保存执行现场(寄存器、堆栈等)
- 系统调用涉及到进程从用户态到内核态的切换(mode switch),这个时候涉及到的切换主要是寄存器上下文的切换,和通常所说的进程上下文切换(Process Context Switch)不同,mode switch 的消耗相对要小很多。
- 进程上下文切换(Process Context Switch)
4.1.3 线程概念的引入
-
线程(thread)的提出
-
进程的不足:
- 进程只能在一个时间处理一个任务,不能同时处理多个任务。
- 如果进程在执行时阻塞,整个进程都无法继续执行。例如:在等待输入时,即使进程中有些处理不依赖于输入数据,也将无法执行。
-
需要提出一种新的实体,满足以下特性:
- 实体之间可以并发地执行;
- 实体之间共享相同的地址空间;
-
-
进程与线程区别
-
调度:现代操作系统将资源拥有者称为进程(process,task),将可执行单元称为线程(Thread)。
线程:将资源与计算分离,提高并发效率。
-
并发性
-
多进程:多个程序可以并发执行,改善资源使用率,提高系统效率
-
多线程:并发粒度更细(任务级并行)、并发性更好
线程可提高进程内的并发程度
-
-
拥有资源
- 进程的资源:虚拟地址空间、进程映像、处理机保护、文件、I/O
- 线程的资源:运行状态、上下文(如:程序计数器)、执行栈。
线程间可以共享资源
-
系统开销
- 进程
- 创建/撤销时需要分配/回收大量资源
- 进程切换时涉及运行环境的保存与设置
- 进程间的同步需要借助消息通信机制。
- 线程:由于资源共享,有效减少了创建/撤销/切换/同步等所造成的开销
线程占用资源少
- 进程
-
-
小结
-
引入线程的优势:线程很轻量,容易创建、撤销
- 有些应用要求并行实体具备共享同一个地址空间
和所有可用数据的能力 - 创建一个线程比一个进程快10-100倍
- 对于存在大量计算和大量I/O处理的应用,大幅度
提高性能 - 在多CPU/多核CPU系统中更有优势
- 有些应用要求并行实体具备共享同一个地址空间
-
进程v.s. 线程
-
4.1.4 线程的实现方式
-
用户级线程:User level threads(ULT)
- 线程在用户空间,通过library模拟的thread,不需要或仅需要极少的kernel支持
- 上下文切换比较快,因为不用更改page table等,使用起来较为轻便快速.
- 提供操控视窗系统的较好的解决方案.
- 用户级的线程库的主要功能
- 创建和销毁线程
- 线程之间传递消息和数据
- 调度线程执行
- 保存和恢复线程上下文
- 用户级线程的优缺点
- 优点
- 线程切换与内核无关
- 线程的调度由应用决定,容易进行优化
- 可运行在任何操作系统上,只需要线程库的支持
- 缺点
- 很多系统调用会引起阻塞,内核会因此而阻塞所有相关的线程。
- 内核只能将处理器分配给进程,即使有多个处理器,也无法实现一个进程中的多个线程的并行执行。
- 优点
-
内核级线程:Kernel level threads (KLT)
- 内核级线程就是kernel有好几个分身,一个分身可以处理一件事.
- 这用来处理非同步事件很有用, kernel可以对每个非同步事件产生一个分身来处理.
- 支持内核线程的操作系统内核称作多线程内核.
- 内核级线程的优缺点:
- 优点
- 内核可以在多个处理器上,调度一个进程的多个线程实现同步并行执行
- 阻塞发生在线程级别
- 内核中的一些处理可以通过多线程实现
- 缺点
- 一个进程中的线程切换需要内核参与,线程的切换涉及到两个模式的切换(进程-进程、线程-线程)
- 降低效率
- 优点
-
用户级线程和内核级线程的比较
-
内核级线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
-
用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言或用户库这一级处理的;而内核级线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
-
用户级线程执行系统调用指令时将导致其所属进程的执行被暂停,而内核级线程执行系统调用指令时,只导致该线程被暂停。
-
-
用户级线程和内核级线程的总结
-
在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核级线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
-
用户级线程的程序实体是运行在用户态下的程序,而内核级线程的程序实体则是可以运行在任何状态下的程序。
-
-
混合实现方式
- 线程在用户空间创建和管理
- 需要实现从用户空间的线程到内核空间线程(轻量级进程)的映射
-
线程模型
有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程和内核级线程的不同连接方式。
1. Many-to-One Model<img src="https://gitee.com/fuoct/myblog/raw/master/img/image-20240602005720284.png" alt="image-20240602005720284" style="zoom:20%;" />
- 将多个用户级线程映射到一个内核级线程,线程管理在用户空间完成。此模式中,用户级线程对操作系统不可见(即透明)。
- 优点:线程管理是在用户空间进行的,因而效率比较高。
- 缺点:当一个线程在使用内核服务时被阻塞,那么整个进程都会被阻塞;多个线程不能并行地运行在多处理机上。
2. One-to-one Model<img src="https://gitee.com/fuoct/myblog/raw/master/img/image-20240602005801408.png" alt="image-20240602005801408" style="zoom:20%;" />
- 将每个用户级线程映射到一个内核级线程。
- 优点:当一个线程被阻塞后,允许另一个线程继续执行,所以并发能力较强。
- 缺点:每创建一个用户级线程都需要创建一个内核级线程与其对应,这样创建线程的开销比较大,会影响到应用程序的性能。
3. Many-to-Many Model<img src="https://gitee.com/fuoct/myblog/raw/master/img/image-20240602005819497.png" alt="image-20240602005819497" style="zoom:20%;" />
- 将n 个用户级线程映射到m 个内核级线程上,要求m <= n。
- 特点:在多对一模型和一对一模型中取了个折中,克服了多对一模型的并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程,开销太大的缺点。又拥有多对一模型和一对一模型各自的优点,可谓集两者之所长。
补充:
Linux并不确切区分进程与线程,而将线程定义为“执行上下文”,它实际只是同一个进程的另外一个执行上下文而已。对于调度,仍然可以使用进程的调度程序。Linux的内核进程,使用kernel_thread创建,一般被称作线程。
有两个系统调用可用以建立新的进程:fork与clone。fork一般用以创建普通进程,而clone 可用以创建线程,kernel_thread便是通过sys_clone来创建新的内核进程。fork与clone都调用do_fork函数执行创建进程的操作。fork并不指定克隆标志,而clone可由用户指定克隆标志。克隆标志有CLONE_VM、CLONE_FS 、CLONE_FILES 、CLONE_SIGHAND 与CLONE_PID等,这些克隆标志分别对应相应的进程共享机制。而fork创建普通进程则使用SIGCHLD标志。
fork:创建普通进程
clone:创建线程
kernel_thread:创建内核进程什么情况下不适合用多线程?
大量计算的情况,同时计算过程不可拆分的情况(计算密集型)
4.2 同步与互斥
4.2.1 同步与互斥问题
-
程序的并发执行
- 进程的三个特征:
- 并发:体现在进程的执行是间断性的;进程的相对执行速度是不可测的。(间断性)
- 共享:体现在进程/线程之间的制约性(如共享打印机)(非封闭性)。
- 不确定性:进程执行的结果与其执行的相对速度有关,是不确定的(不可再现性)。
- 名词解释
- 竞争:两个或多个进程对同一共享数据同时进行访问,而最后的结果是不可预测的,它取决于各个进程对共享数据访问的相对次序。这种情形叫做竞争。
- 竞争条件:多个进程并发访问和操作同一数据且执行结果与访问的特定顺序有关(Bernstein条件)。
- 临界资源:一次仅允许一个进程访问的资源。
- 临界区:每个进程中访问临界资源的那段代码称为临界区。
- 进程的三个特征:
-
进程的同步与互斥
-
进程互斥(间接制约关系):
- 两个或两个以上的进程,不能同时进入关于同一组共享资源的临界区,否则可能发生与时间有关的错误。
- 进程互斥是进程间发生的一种间接性作用,一般是程序不希望的。
-
进程同步(直接制约关系):
- 系统中各进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性的过程称为进程同步。
- 进程同步是进程间的一种刻意安排的直接制约关系。即为完成同一个任务的各进程之间,因需要协调它们的工作而相互等待、相互交换信息所产生的制约关系。
-
同步与互斥的区别与联系:
-
互斥:某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。互斥无法限制访问者对资源的访问顺序,即访问是无序访问。
-
同步:是指在互斥的基础上,通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有对资源的写入的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
-
-
临界区应当满足的四个条件:
-
没有进程在临界区时,想进入临界区的进程可进入。
-
任何两个进程都不能同时进入临界区(MutualExclusion);
-
任何一个进程进入临界区的请求应该在有限时间内得到满足(Bounded Waiting)。
-
当一个进程运行在临界区外面时,不能妨碍其他的进程进入临界区(Progress);
死等、忙等、有限等待、让权等待 解释:
-
-
机制设计上应遵循的准则
- 空闲让进:临界资源处于空闲状态,允许进程进入临界区。临界区内仅有一个进程运行。
- 忙则等待:临界区有正在执行的进程,所有其他进程则不可以进入临界区。
- 有限等待:对要求访问临界区的进程,应保证在有限时间内进入自己的临界区,避免死等。
- 让权等待:当进程(长时间)不能进入自己的临界区时,应立即释放处理机,尽量避免忙等。
4.2.2 基于忙等待的互斥方法
-
软件方法
-
Dekker算法:第一个不需要严格轮换的互斥算法
-
Peterson算法:第一个不需要轮换的互斥算法
-
Bakery Algorithm(面包店算法):支持任意数量进程
-
-
硬件方法
-
中断屏蔽:使用“开/关中断”指令。
-
执行“关中断”指令,进入临界区操作;
退出临界区之前,执行“开中断”指令。 -
优缺点:
-
简单
-
不适用于多CPU系统;
-
往往会带来很大的性能损失:很多日常任务都是靠中
断机制触发的,比如时钟,如果屏蔽中断,会影响时
钟和系统效率; -
用户进程的使用可能很危险,例如:关中断之后,不
再打开中断,会导致整个系统无法继续运行; -
使用范围:内核进程(少量使用)。
-
-
-
使用test and set指令
-
TS(test-and-set )是一种不可中断的基本原语(指令)。它会把“1”写到某个内存位置并传回其旧值。在多进程可同时访问内存的情况下,如果一个进程正在执行TS指令,在它执行完成前,其它的进程不可以执行TS指令。
-
TestAndSet(boolean_ref lock) { boolean initial = lock; lock = true; return initial; }
-
-
自旋锁Spinlocks
-
利用test_and_set硬件原语提供互斥支持
通过对总线的锁定实现对某个内存位置的原子读与更新 -
acquire(lock) { while(test_and_set(lock) == 1) /* do nothing */; } release(lock) { lock = 0; }
-
-
-
几个算法的共性问题
-
无论是软件解法(如Peterson)还是硬件(如TSL或XCHG)解法都是正确的,它们都有一个共同特点:当一个进程想进入临界区时,先检查是否允许进入,若不允许,则该进程将原地等待,直到允许为止。
- 忙等待: 浪费CPU时间
- 优先级反转:低优先级进程先进入临界区,高优先级进程一直忙等
-
如果采用用户级线程实现多线程,是否会发生优先级反转(指同一个进程的多个线程)?为什么?
- 优先级反转发生是由于低优先级进程运行在临界区时由于高优先级进程就绪而被切换。如果高优先级进程使用忙等的策略尝试进入临界区,就会一直忙等,使得低优先级进程没有机会离开临界区。
- 如果使用用户级线程,低优先级线程不会被高优先级线程抢占(进入临界区一般需要系统调用,其他线程也同时会被阻塞),因为抢占发生在进程级别。但是对于内核级线程的实现,这个是可能发生的。
-
4.2.3 基于信号量的同步方法
-
同步实现的基本思路
-
同步中,进程经常需要等待某个条件的发生,如果使用忙等待的解决方案,势必浪费大量CPU时间。
-
解决方法:将忙等变为阻塞,可使用两条进程间的通信原语:Sleep和Wakeup
- Sleep原语将引起调用进程的阻塞,直到另外一个进程用Wakeup原语将其唤醒。很明显,wakeup原语的调用需要一个参数——被唤醒的进程ID。
-
-
信号量
-
信号量: 一个确定的二元组(s, q),其中s是一个具有非负初值的整型变量,q是一个初始状态为空的队列. 当发出P操作时:
-
s为正,则该值等于可立即执行的进程的数量;s <= 0,那么发出P操作后的进程被阻塞,│s │是被阻塞的进程数。
-
q是一个初始状态为空的队列,当有进程被阻塞时就会进入此队列。
-
-
信号量的分类
- 二元信号量和一般信号量
- 二元信号量:取值仅为“0”或“1”,主要用作实现互斥;
- 一般信号量:初值为可用物理资源的总数,用于进程间的协作同步问题。
- 强信号量和弱信号量
- 强信号量:进程从被阻塞队列释放时采取FIFO
- 不会出现“饥饿”(某个进程长时间被阻塞)
- 弱信号量:没有规定进程从阻塞队列中移除顺序
- 可能出现“饥饿”
- 强信号量:进程从被阻塞队列释放时采取FIFO
- 二元信号量和一般信号量
-
二元信号量机制
-
通常使用二元信号量的PV操作实现两个进程的互斥。
-
应用时应注意:
-
每个进程中用于实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。
-
P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
-
互斥信号量的初值一般为1。
-
P(S) :while S<=0 do skip; S:=S-1; V(S) :S:=S+1;
-
-
-
一般信号量的结构
- 信号量的数据结构,包含:
- 一个初始值为正的整数: s.count
- 一个初始为空的队列: s.queue
- 其上定义了三个原子操作
- 初始化s
- 发送信号s: semSignal(s),(V, Up)
- 接收信号s: semWait(s) ,(P,Down)
- 信号量的数据结构,包含:
-
信号量的操作
- 一个信号量可能被初始化为一个非负整数.
- semWait 操作(P操作)使信号量减1。若值为负,则执行semWait的进程被阻塞。否则进程继续执行。
- semSignal操作(V操作)使信号量加1。若值小于或等于零,则被semWait操作阻塞的进程被解除阻塞。
- 除此之外,没有任何办法可以检查或操作信号量!
-
信号量在并发中的典型应用
-
一种低级通信原语:屏障Barriers
- 又称为Rendezvous, 用于进程组的同步
-
-
“信号量集”机制
- AND型信号量集机制:将进程需要的所有共享资源一次全部分配给它;待该进程使用完后再一起释放。
- 一般“信号量集”机制:在AND型信号量集的基础上进行扩充-进程对信号量Si的测试值为ti(用于信号量的判断,即Si >= ti,表示资源数量低于ti时,便不予分配),占用值为di(用于信号量的增减,即Si = Si - di和Si = Si+di )
-
P.V操作的优缺点
- 优点: 简单,而且表达能力强(用P.V操作可解决任何同步互斥问题)
- 缺点: 不够安全;P.V操作使用不当会出现死锁;遇到复杂同步互斥问题时实现复杂
4.2.4 基于管程的同步与互斥
- 信号量及PV 操作的问题:
- 信号量机制具有一些缺点,比如说用信号量及PV 操作解决问题的时候程序编写需要很高的技巧。
- 如果没有合理地安排PV 操作的位置,就会导致一些出错的结果,例如:出现死锁等问题。
-
管程的定义和组成:管程是在程序设计语言当中引入的一种高级同步机制。
- 管程由四部分组成:
- 管程的名称;
- 局部于管程内部的共享数据结构(变量)说明;
- 对该数据结构进行操作的一组互斥执行的过程;
- 对局部于管程内部的共享数据设置初始值的语句。
- 管程要解决的问题:
- 互斥:只能有一个进程可对其内部数据结构进行相应的操作,即管程进入是互斥的。由编译器来保证(管程是一个语言机制)。
- 同步:通过设置条件变量(CV) 以及在条件变量上实施的wait 和signal 操作,它可以使一个进程或线程当条件不满足(满足)的时候在条件变量上等待(唤醒)。
- 条件变量:为了区别等待的不同原因,管程又引入了条件变量。不同的条件变量,对应不同原因的进程阻塞等待队列,初始时为空。条件变量上能作wait和signal原语操作,若条件变量名为X,则调用同步原语的形式为X.wait和X.signal。
- 条件变量与信号量的区别
- 条件变量的值不可增减,P-V操作的信号量值可增减
- wait操作一定会阻塞当前进程;但P操作只有当信号量的值小于0时才会阻塞。
- 如果没有等待的进程,signal将丢失;而V操作增加了信号量的值,不会丢失。
- 访问条件变量必须拥有管程的锁
- 条件变量的值不可增减,P-V操作的信号量值可增减
- 管程由四部分组成:
-
Hoare管程
-
两个队列
- 入口等待队列(entry queue):因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时它应当在管程的入口处等待,因而在管程的入口处应当有一个进程等待队列,称作入口等待队列。
- 紧急等待队列:如果进程P唤醒进程Q,则P等待Q继续;Q执行完又唤醒进程R,则Q等待R继续,…,如此,在管程内部,由于执行唤醒操作,可能会出现多个等待进程(已被唤醒,但由于管程的互斥进入而等待),这个管程内部的等待队列被称为紧急等待队列,其优先级高于入口等待队列。
-
Hoare管程的条件变量
- 由于管程通常是用于管理资源的,因而在管程内部,应当存在某种等待机制。当进入管程的进程因资源被占用等原因不能继续运行时使其等待。为此在管程内部可以说明和使用一种特殊类型的变量----条件变量。
- 每个条件变量表示一种等待原因,并不取具体数值--相当于每个原因对应一个队列。
-
Hoare管程的同步原语
- 同步操作原语wait和signal:针对条件变量x,**x.wait()**将自己阻塞在x队列中,**x.signal()**将x队列中的一个进程唤醒。
- x.wait():如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的使用权,执行此操作的进程排入x队列尾部(紧急等待队列与x队列的关系:紧急等待队列是由于管程的互斥进入而等待的队列,而x队列是因资源被占用而等待的队列)。
- x.signal():如果x队列为空,则相当于空操作,执行此操作的进程继续;否则唤醒第一个等待者,执行x.signal()操作的进程排入紧急等待队列的尾部。
- 同步操作原语wait和signal:针对条件变量x,**x.wait()**将自己阻塞在x队列中,**x.signal()**将x队列中的一个进程唤醒。
-
Hoare管程:信号量定义
-
每个管程必须提供一个用于互斥的信号量mutex(初值为1)。
-
进程调用管程中的任何过程时,应执行P(mutex);进程退出管程时应执行V(mutex)开放管程,以便让其他调用者进入。
-
为了使进程在等待资源期间,其他进程能进入管程,故在wait操作中也必须执行V(mutex),否则会妨碍其他进程进入管程,导致无法释放资源。
-
对每个管程,引入信号量next(初值为0),凡发出signal操作的进程应该用P(next)挂起自己,直到被释放进程退出管程或产生其他等待条件。
-
进程在退出管程的过程前,须检查是否有别的进程在信号量next上等待,若有,则用V(next)唤醒它。next-count(初值为0),用来记录在next上等待的进程个数。
-
引入信号量x-sem(初值为0),申请资源得不到满足时,执行P(x-sem)挂起。由于释放资源时,需要知道是否有别的进程在等待资源,用计数器x-count(初值为0)记录等待资源的进程数。
-
执行signal操作时,应让等待资源的诸进程中的某个进程立即恢复运行,而不让其他进程抢先进入管程,这可以用V(x-sem)来实现
-
-
4.2.5 进程通信的主要方法
-
进程间通信(Inter-Process-Comm)IPC
- 低级通信:只能传递状态和整数值(控制信息),包括进程互斥和同步所采用的信号量和管程机制。缺点:
- 传送信息量小:效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。
- 编程复杂:用户直接实现通信的细节,编程复杂,容易出错。
- 高级通信:适用于分布式系统,基于共享内存的多处理机系统,单处理机系统,能够传送任意数量的数据,可以解决进程的同步问题和通信问题,主要包括三类:管道、共享内存、消息系统。
- 低级通信:只能传递状态和整数值(控制信息),包括进程互斥和同步所采用的信号量和管程机制。缺点:
-
无名管道(Pipe)
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
- 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是单独构成一种文件系统,并且只存在于内存中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
- 有名管道(Named Pipe,又称FIFO)
-
无名管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道提出后,该限制得到了克服。
-
FIFO不同于无名管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
-
需注意的是,FIFO严格遵循先进先出(first in firstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
-
共享内存
- 共享内存是最有用的进程间通信方式,也是最快的IPC形式(因为它避免了其它形式的IPC必须执行的开销巨大的缓冲复制)。
- 两个不同进程A、B共享内存的意义是,同一块物理内存*被映射到进程A、B各自的进程地址空间。
- 当多个进程共享同一块内存区域,由于共享内存可以同时读但不能同时写**,则需要同步机制约束(互斥锁和信号量都可以)。
- 共享内存通信的效率高(因为进程可以直接读写内存)。
- 进程之间在共享内存时,保持共享区域直到通信完毕。
4.2.6 经典的进程同步与互斥问题
-
生产者-消费者问题(the producer-consumer problem)
-
描述:问题描述:若干进程通过有限的共享缓冲区交换数据。其中,“生产者”进程不断写入,而“消费者”进程不断读出;共享缓冲区共有N个;任何时刻只能有一个进程可对共享缓冲区进行操作。
-
分析:
- 两个隐含条件:
- 消费者和生产者数量不固定。
- 消费者和生产者不能同时使用缓存区。
- 行为分析:
- 生产者:生产产品,放置产品(有空缓冲区)。
- 消费者:取出产品(有产品),消费产品。
- 行为关系:
- 生产者之间:互斥(放置产品)
- 消费者之间:互斥(取出产品)
- 生产者与消费者之间:互斥(放/取产品) 同步(放置——取出)
- 两个隐含条件:
-
解决方法
-
用PV操作解决生产者/消费者问题
-
采用Sleep和Wakeup原语的方法
-
用管程解决生产者消费者问题
-
拓展:
如果缓冲区大小为无限大
- 是否还需要互斥?
- 是否还有同步?
- 影响生产者,还是消费者?
- 需要,因为缓冲区是共享资源。
- 需要。
- 对消费者没有影响,对生产者有影响(无需等待empty)。
-
-
-
读者-写者问题(the readers-writers problem)
-
问题描述:对共享资源的读写操作,任一时刻“写者”最多只允许一个,而“读者”则允许多个。即:“读-写”互斥,“写-写”互斥,“读-读”允许。
-
问题分析
-
读进程的行为:
-
系统中会有多个读进程同时访问共享数据。
-
可分为三类:第一个进入的读进程(占有资源),最后一个离开的读进程(释放资源)和其他读进程。
-
需要设置一个计数器readcount来记录读进程的数目。
-
-
写进程的行为:排他性地使用资源。
-
确定同步与互斥关系:
- 读者-读者:共享Data, 互斥访问readcount
- 读者-写者:互斥访问Data
- 写者-写者:互斥访问Data
-
确定临界资源:Data, readcount
-
-
解决
-
-
哲学家进餐问题(the dining philosophers problem)
- 描述:(由Dijkstra首先提出并解决)5个哲学家围绕一张圆桌而坐,桌子上放着5支筷子,每两个哲学家之间放一支;哲学家的动作包括思考和进餐,进餐时需要同时拿起他左边和右边的两支筷子,思考时则同时将两支筷子放回原处。如何保证哲学家们的动作有序进行?例如:
- 支持任意数量的哲学家
- 不出现所有人都无法进餐(死锁)
- 不出现有人永远拿不到筷子(饥饿)
- 保证效率,尽可能并行就餐
- 思路
- 至多只允许四个哲学家同时(尝试)进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。设置信号量room=4。(破除资源互斥)
- 对筷子进行编号,每个哲学家按编号从低到高拿起筷子。或者对哲学家编号,奇数号哲学家先拿左,再拿右;偶数号相反。(破除循环等待)
- 同时拿起两根筷子,否则不拿起。(破除保持等待)
- 描述:(由Dijkstra首先提出并解决)5个哲学家围绕一张圆桌而坐,桌子上放着5支筷子,每两个哲学家之间放一支;哲学家的动作包括思考和进餐,进餐时需要同时拿起他左边和右边的两支筷子,思考时则同时将两支筷子放回原处。如何保证哲学家们的动作有序进行?例如:
-
其他问题
-
闸机问题
-
理发师问题
-
生产者消费者扩展1,2
-
构建水分子问题
-
吸烟者问题
-
读写者-灯开关模式
4.3 调度
- 什么是CPU调度?
CPU 调度的任务是控制、协调多个进程对CPU 的竞争。也就是按照一定的策略(调度算法),从就绪队列中选择一个进程,并把CPU 的控制权交给所选中的进程。- CPU调度的场景
- N 个进程就绪,等待CPU
- M个CPU,M≥1
- OS需要决策,给哪个进程分配哪个CPU 。
4.3.1 基本概念
-
何时进行调度(When)
- 当一个新的进程被创建时,是执行新进程还是继续执行父进程?
- 当一个进程运行完毕时,选择其他进程执行;
- 当一个进程由于I/O、信号量或其他的某个原因被阻塞时;
- 当一个I/O中断发生时,表明某个I/O操作已经完成,而等待该I/O操作的进程转入就绪状态;
- 在分时系统中,当一个时钟中断发生时。
-
CPU切换过程(How)
- 在进程(上下文)中切换的步骤
- 保存处理器的上下文,包括程序计数器和其它寄存器;
- 用新状态和其它相关信息更新正在运行进程的PCB;
- 把进程移至合适的队列-就绪、阻塞;
- 选择另一个要执行的进程;
- 更新被选中进程的PCB;
- 从被选中进程中重装入CPU 上下文。
-
调度的类型
-
高级调度:又称为“宏观调度”、“作业调度”。从用户角度,一次提交若干个作业,对每个作业进行调度。时间上通常是分钟、小时或天。
-
中级调度:又称为“内外存交换”,指令和数据必须在内存里才能被CPU直接访问。从存储器资源管理的角度,将进程的部分或全部换出到外存上,将当前所需部分换入到内存。
-
低级调度:又称为“微观调度”、“进程或线程调度”。从CPU资源管理的角度,执行的单位。时间上通常是毫秒。因为执行频繁,要求在实现时达到高效率。
- 非抢占式
- 抢占式
- 时间片原则
- 优先权原则
- 短作业(进程)优先
-
-
调度性能准则
-
面向用户的调度性能准则
-
周转时间:作业从提交到完成(得到结果)所经历的时间。包括:在收容队列中等待,CPU上执行,就绪队列和阻塞队列中等待,结果输出等待--批处理系统
-
响应时间:用户输入一个请求(如击键)到系统给出首次响应(如屏幕显示)的时间--分时系统
-
-
面向系统的调度性能准则
- 吞吐量:单位时间内所完成的作业数,跟作业本身特性和调度算法都有关系--批处理系统
- 平均周转时间不是吞吐量的倒数,因为并发执行的作业在时间上可以重叠。如:在2小时内完成4个作业,吞吐量是2个作业/小时,而平均周转时间可能是1.25小时
-
4.3.2 设计调度算法要考虑的问题
- 占用CPU的方式
- 不可抢占式方式: 一旦处理器分配给一个进程,它就一直占用处理器,直到该进程自己因调用原语操作或等待I/O等原因而进入阻塞状态,或时间片用完时才让出处理器,重新进行。
- 抢占式方式:就绪队列中一旦有优先级高于当前运行进程优先级的进程存在时,便立即进行进程调度,把处理器转给优先级高的进程
4.3.3 批处理系统的调度算法
-
吞吐量、平均等待时间和平均周转时间公式
- 吞吐量= 作业数 总执行时间 \frac{作业数}{总执行时间} 总执行时间作业数,即单位时间CPU完成的作业数量
- 周转时间
(Turnover Time)
=完成时刻- 提交时刻 - 带权周转时间= 周转时间 服务时间 \frac{周转时间}{服务时间} 服务时间周转时间(执行时间)
- 平均周转时间= 作业周转时间之和 作业数 \frac{作业周转时间之和}{作业数} 作业数作业周转时间之和
- 平均带权周转时间=$ \frac{作业带权周转时间之和}{作业数}$
-
四种调度算法
- 先来先服务FCFS
-
最简单的调度算法,按先后顺序调度。
-
按照作业提交或进程变为就绪状态的先后次序,分派CPU;
-
当前作业或进程占用CPU,直到执行完或阻塞,才让出CPU(非抢占方式)。
-
在作业或进程唤醒后(如I/O完成),并不立即恢复执行,通常等到当前作业或进程让出CPU。
-
-
FCFS的特点
-
比较有利于长作业,而不利于短作业。
-
有利于CPU繁忙的作业,不利于I/O繁忙的作业。
-
-
短作业优先SJF
-
又称为“短进程优先”SPN(Shortest Process Next);这是对FCFS算法的改进,其目标是减少平均周转时间。
- 对预计执行时间短的作业(进程)优先分派处理机。通常后来的短作业不抢占正在执行的作业
-
优点:
-
比FCFS改善平均周转时间和平均带权周转时间,缩短作业的等待时间;
-
提高系统的吞吐量;
-
-
缺点:
-
对长作业非常不利,可能长时间得不到执行;
-
未能依据作业的紧迫程度来划分执行的优先级;
-
难以准确估计作业(进程)的执行时间,从而影响调度性能。
-
-
-
最短剩余时间优先SRTN
- **将短作业优先进行改进,改进为抢占式,**就得到最短剩余时间优先算法,即一个新就绪的进程比当前运行进程具有更短的完成时间,系统抢占当前进程,选择新就绪的进程执行。
- 缺点:源源不断的短任务到来,可能使长的任务长时间得不到运行,导致产生“饥饿”现象。
-
最高响应比优先HRRN
- HRRN算法实际上是FCFS算法和SJF算法的折衷既考
虑作业等待时间,又考虑作业的运行时间,既照顾
短作业又不使长作业的等待时间过长,改善了调度
性能。 - 在每次选择作业投入运行时,先计算后备作业队列
中每个作业的响应比RP,然后选择其值最大的作业
投入运行。 - RP值定义为:
- RP = (作业已等待时间+作业的服务时间)/作业的服务时间
= 1 +作业已等待时间/作业的服务时间。
- RP = (作业已等待时间+作业的服务时间)/作业的服务时间
- 响应比最高优先(HRRN)算法效果:
- 非抢占式
- 短作业容易得到较高的响应比
- 长作业等待时间足够长后,也将获得足够高的响应比
- 饥饿现象不会发生
- 缺点:每次计算各道作业的响应比会有一定的时间开销
- HRRN算法实际上是FCFS算法和SJF算法的折衷既考
4.3.4 交互式系统的调度算法
-
时间片轮转(RR:Round Robin)
-
本算法主要用于微观调度,设计目标是提高资源利用率。其基本思路是通过时间片轮转,提高进程并发性和响应时间特性,从而提高资源利用率;
-
步骤
-
将系统中所有的就绪进程按照FCFS原则,排成一个队列。
-
每次调度时将CPU分派给队首进程,让其执行一个时间片。时间片的长度从几个ms到几百ms。
-
在一个时间片结束时,发生时钟中断。
-
调度程序据此暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前的队首进程。
-
进程可以未使用完一个时间片,就让出CPU(如阻塞)。
-
-
-
多级队列(MQ:Multi-level Queue)
- 本算法引入多个就绪队列,通过各队列的区别对待,达到综合的调度目标;
- 根据作业或进程的性质或类型的不同,将就绪队再分为若干个子队列。
- 每个作业固定归入一个队列。
- 不同队列可有不同的优先级、时间片长度、调度策略等;在运行过程中还可改变进程所在队列。如:系统进程、用户交互进程、批处理进程等。
- 本算法引入多个就绪队列,通过各队列的区别对待,达到综合的调度目标;
-
多级反馈队列(MFQ: Multi-level Feedback Queue )
-
多级反馈队列算法是时间片轮转算法和优先级
算法的综合和发展。优点:- 为提高系统吞吐量和缩短平均周转时间而照顾短进程
- 为获得较好的I/O设备利用率和缩短响应时间而照顾I/O型进程
- 不必估计进程的执行时间,动态调节
-
-
几点说明
- I/O型进程:让其进入最高优先级队列,以及时响应I/O交互。通常执行一个小时间片,要求可处理完一次I/O请求的数据,然后转入到阻塞队列。
- 计算型进程:每次都执行完时间片,进入更低级队列。最终采用最大时间片来执行,减少调度次数。
- I/O次数不多,而主要是CPU处理的进程:在I/O完成后,放回I/O请求时离开的队列,以免每次都回到最高优先级队列后再逐次下降。
- 为适应一个进程在不同时间段的运行特点,I/O完成时,提高优先级;时间片用完时,降低优先级;
-
优先级反转(倒置)的解决方法
- 优先级置顶
- 进程Task C 在进入临界区后,Task C 所占用的处理机就不允许被抢占。这种情况下,Task C 具有最高优先级(Priority Ceiling) 。
- 优先级继承
- 当高优先级进程Task A 要进入临界区使用临界资源X时,如果已经有一个低优先级进程Task C 正在使用该资源,可以采用优先级继(Priority Inheritance)的方法。
- 优先级置顶
4.3.5 实时系统的调度算法
P31
- 静态表调度Static table-driven scheduling
- 单调速率调度RMS:Rate Monotonic Scheduling
- 最早截止时间优先算法EDF:Earliest Deadline First
- 补充:最低松弛度优先算法LLF(Least Laxity First)
4.3.6 多处理机调度
P42
4.4 死锁
4.4.1 死锁的概念
-
一组进程中,每个进程都无限等待组内其他进程所占有的资源,在无外力介入的条件下,将因永远分配不到资源而无法运行的现象。
- 浪费大量系统资源
- 甚至导致系统崩溃
-
死锁发生原因
- 资源竞争
- 并发执行的顺序不当
-
死锁发生的四个必要条件
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不可剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
-
活锁和饥饿
-
活锁(livelock):是指任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
- 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,即所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。避免活锁的简单方法是采用先来先服务的策略。
-
饥饿(starvation):某些进程可能由于资源分配策略的不公平导致长时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿,当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时称该进程被饿死(starve to death)
-
4.4.2 处理死锁的基本方法
-
处理死锁方法
- 不允许死锁发生
- 预防死锁(静态):防患于未然,破坏死锁的产生条件
- 避免死锁(动态):在资源分配之前进行判断
- 允许死锁发生
- 检测与解除死锁
- 无所作为:鸵鸟算法
- 不允许死锁发生
-
死锁预防
-
打破互斥条件:即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是资源本身的属性。
-
打破请求且保持的条件:可以实行资源预先分配策略。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程,否则不分配任何资源。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。
但是,这种策略也有如下缺点:
1. 在许多情况下,由于进程在执行时是动态的,不可预测的,因此不可能知道它所需要的全部资源。 2. 资源利用率低。无论资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被用到一次,但该进程在生存期间却一直占有。这显然是一种极大的资源浪费; 3. 降低进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。
- 打破不可剥夺条件:即允许进程强行从占有者那里夺取某些资源。
- 当一个进程已占有了某些资源,它又申请新的资源,当不能立即被满足时,须释放所占有的全部资源,以后再重新申请。
- 它所释放的资源可以分配给其它进程,相当于该进程占有的资源被隐蔽地强占了。
- 这种预防死锁的方法实现起来困难,会降低系统性能。
- 打破循环等待条件:实行资源有序分配策略。即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但存在以下缺点:
- 限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;
- 为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。
-
-
死锁避免
-
死锁预防是排除死锁的静态策略,它使产生死锁的四个必要条件不能同时具备,从而对进程申请资源的活动加以限制,以保证死锁不会发生。
-
死锁的避免是排除死锁的动态策略,它不限制进程有关资源的申请,而是对进程所发出的每一个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。即分配资源时判断是否会出现死锁,有则加以避免。如不会死锁,则分配资源。
-
死锁避免不那么严格限制产生死锁的四个必要条件(区别于死锁预防)
-
-
银行家算法
-
银行家算法(Dijkstra, 1965)一个银行家把他的固定资金(capital)贷给若干顾客。只要不出现一个顾客借走所有资金后还不够,银行家的资金应是安全的。银行家需一个算法保证借出去的资金在有限时间内可收回。
-
为了保证资金的安全,银行家规定:
- 当一个顾客对资金的最大需求量不超过银行家现有资金时就可接纳顾客
- 顾客可以分期贷款,但贷款总数不能超过最大需求量
- 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限时间里得到贷款
- 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金
-
假定顾客借款分成若干次进行;并在第一次借款时,能说明他的最大借款额。具体算法:
- 顾客的借款操作依次顺序进行,直到全部操作完成;
- 银行家对当前顾客的借款操作进行判断,以确定其安全性(能否支持顾客借款,直到全部归还);
- 安全时,贷款;否则,暂不贷款。
-
银行家算法特点
- 允许互斥、部分分配和不可抢占,可提高资源利用率;
- 要求事先说明最大资源要求,在现实中很困难;
-
-
检测死锁:保存资源的请求和分配信息,利用某种算法对这些信息加以检查,以判断是否存在死锁。死锁检测算法主要是检查是否有循环等待。
-
死锁解除
- 实质:如何让释放资源的进程能够继续运行
- 选择一个牺牲进程
- 重新运行或回退到某一点开始继续运行
- 回退到足以解除死锁即可
- 需注意的问题:
- 怎样保证不发生“饥饿”现象,如何保证并不总是剥夺同一进程的资源
- “最小代价”,即最经
- 实质:如何让释放资源的进程能够继续运行
4.4.3 小结
5 IO管理
5.1 I/O管理概述
-
设备的管理的目的和功能
- 外设管理目的
- 提高效率:提高I/O访问效率,匹配CPU和多种不同处理速度的外设
- 方便使用:方便用户使用,对不同类型的设备统一使用方法,协调对设备的并发使用
- 方便控制:方便OS内部对设备的控制:增加和删除设备,适应新的设备类型
- 外设管理功能
- 提供设备使用的用户接口:命令接口和编程接口。
- 设备分配和释放:使用设备前,需要分配设备和相应的通道、控制器。
- 设备的访问和控制:包括并发访问和差错处理。
- I/O缓冲和调度:目标是提高I/O访问效率。
- 外设管理目的
-
I/O管理示意(软件角度)
-
用户进程在运行过程中,提出I/O 请求, 一旦请求被操作系统接受,操作系统则负责完成该请求。
-
我们把硬件设备抽象为控制器。控制器中包含控制寄存器、状态寄存器,以及一些数据寄存器。从操作系统角度,是通过对这些寄存器进行相应的控制,达到控制设备的目的。
-
在OS中,I/O 设备管理可直接从应用程序或文件系统得到请求,并负责完成这个请求。它具体包括:
-
逻辑I/O :完成设备无关的操作,如设备分配,设备回收
数据准备等; -
设备驱动程序:负责对设备控制器进行控制(通过读写其中的寄存器)。
-
中断服务程序:设备工作结束后负责向CPU 发中断信号,中断服务程序完成相应处理。
-
-
-
I/O管理的特点
- I/O性能经常成为系统性能的瓶颈;
- 操作系统庞大复杂的主要原因之一:资源多、杂,并发,均来自I/O;
- 速度差异很大
- 控制接口复杂
- 传输单位不同
- 数据表示各异
- 错误条件多样
- 与其它功能联系紧密,特别是文件系统。
5.2 I/O硬件组成
-
设备控制器
- 控制器的功能
- 接收和识别CPU命令
- 数据交换:CPU与控制器、控制器与设备
- 设备状态的了解和报告
- 设备地址识别
- 缓冲区
- 对设备传来的数据进行差错检测
- 组成
- 控制器与CPU接口:数据寄存器、控制寄存器、状态寄存器,采用内存映射或专门的I/O指令
- 控制器与设备接口:数据信号、控制信号、状态信号
- I/O逻辑:用于实现CPU对I/O设备的控制
- 控制器的功能
-
I/O端口地址
- I/O端口地址:接口电路中每个寄存器具有唯一的地址
- 所有I/O端口地址形成I/O端口的地址空间(受OS保护)
- I/O指令形式与I/O地址是相互关联的,主要有以下形式:
- 内存映像编址(内存映像I/O模式):控制器的内存/寄存器作为物理内存空间的一部分
- I/O独立编址(I/O专用指令):Intel 体系架构in/out 指令
-
内存映射I/O的特点
-
优点:
-
不需要特殊的保护机制来阻止用户进程进行相应的I/O 操作。操作系统要避免把包含了控制寄存器的那部分地址空间放入用户的虚拟地址空间中。
-
可以引用内存的每一条指令都可以适用于引用控制寄存器。
-
-
缺点:
- 不允许对一个控制寄存器的内容进行高速缓存。
- 如果我们把设备控制寄存器进行了高速缓存,那么第一次引用的时候就把它放入了高速缓存。以后再对它的引用都是从高速缓存当中取值,而不会再去对设备进行相应的检测
-
-
I/O独立编址的特点
-
优点:
-
外设不占用内存的地址空间
-
编程时,易于区分是对内存操作还是对I/O操作
-
-
缺点:
- I/O端口操作的指令类型少,操作不灵活
-
5.3 I/O控制方式
P31
-
程序控制I/O,也称轮询或查询方式I/O,它由CPU代表进程向I/O模块发出指令, 然后进入忙等状态, 直到操作完成之后进程才能够继续执行。
-
中断驱动, 当I/O操作结束后由设备控制器主动地来通知设备驱动程序, 而不是依靠设备驱动程序不断地去轮询看看设备的状态。
-
DMA,直接存储器访问方式, 是由一个专门的控制器来完成数据从内存到设备或者是从设备到内存的传输工作。
-
通道与DMA的原理几乎是一样的,通道是一个特殊功能的处理器,它有自己的指令和程序专门负责数据输入输出的传输控制。CPU将“传输控制”的功能下放给通道后只负责“数据处理”功能。这样,通道与CPU分时使用内存,实现了CPU内部运算与I/O设备的并行工作。
5.4 I/O软件的组成
5.5 I/O缓冲管理
-
单缓冲(single buffer)
-
每当用户进程发出一个I/O请求时,操作系统便在主存中为之分配一个缓冲区,T、M,C的定义见下图。由于T和C是可以并行的,当T>C时,系统对每一块数据的处理时间为M+T,反之,为M+C,系统对每一块数据的处理时间为Max(C,T) + M。
-
-
双缓冲(double buffer)
-
两个缓冲区,CPU和外设都可以连续处理而无需等待对方。要求CPU的处理速度和外设速度相近。在双缓冲时,系统处理一块数据的时间可以粗略地认为是Max(C,T),如果C<T,可使块设备连续输入,如果C>T,则可使CPU不必等待设备输入。
-
-
环形缓冲(circular buffer)
-
若CPU和外设的处理速度差较大,双缓冲效果不够理想,因此引入了多缓冲机制,可将多个缓冲组织成循环缓冲形式。对于用作输入的循环缓冲,通常是提供给输入进程或计算进程使用,输入进程不断向空缓冲去输入数据,而计算进程则从中提取数据进行计算。
-
循环缓冲区的组成如下
-
多个缓冲区,在循环缓冲区中包括多个缓冲区,每个缓冲区的大小相同,作为输入的多缓冲区可分为三种类型,用于装输入数据的空缓冲区R、已装满数据的缓冲区G以及计算进程正在使用的工作缓冲区C。
-
多个指针,作为输入的缓冲区可设置三个指针,用于指示计算进程下一个可用缓冲区G的指针Nextg、指示输入进程下次可用的空缓冲区R的指针Nexti、以及用于指示计算进程正在使用的缓冲区C的指针Current。
-
-
-
缓冲池
P70
5.6 I/O设备管理
5.7 I/O性能问题
6 磁盘管理
6.1 磁盘历史及工作原理
-
基本概念
-
扇区(sector):盘片被分成许多扇形的区域
-
磁道(track): 盘片上以盘片中心为圆心,不同半径的同心圆。
-
柱面(cylinder):硬盘中,不同盘片相同半径的磁道所组成的圆柱。
-
每个磁盘有两个面,每个面都有一个磁头(head)。
- 距离轴心最远的柱面/磁道编号为0
- 外道数据访问速度更快
-
-
磁盘的组织
-
读一个扇区需要“柱面/磁头/扇区”三个参数
-
现代磁盘驱动器可以看做一个一维的逻辑块的数组,逻辑块是最小的传输单位
-
一维逻辑块数组按顺序映射到磁盘的扇区。
-
扇区0是最外面柱面的第一个磁头(磁道)第一个扇区。
-
该映射是先按磁道内扇区顺序,再按柱面内磁道顺序,再按从外到内的柱面顺序来排序的。
-
-
-
Flash Disk
-
Flash Disk的特征是低功耗、大容量、数据访问速度高。
- 没有高速的机械部件,可以在剧烈振动的环境中工作;零下45摄氏度到零上85摄氏度
-
Flash Disk执行一次数据读写的周期比硬盘短。
- 即使是较慢的NAND型Flash Memory作为存储载体的Flash Disk执行一次数据访问的周期大致为15-30μs,硬盘大约为5ms。
-
问题:容量、价格、寿命(P/E)、可靠性、读写不对称(读快、写慢)。
-
-
Flash Disk两种技术:NAND / NOR
6.2 磁盘的组织与调度
-
CHS模式: Cylinder, Header, Sector
- 磁头数NH(Heads):表示硬盘总共有几个磁头,也就是有几面盘片, 最多为256个 (用8 位存储)。
- 柱面数NC(Cylinders):表示硬盘每一面盘片上有几条磁道, 最大为1024 (用10 位存储)。
- 扇区数NS(Sectors):表示每一条磁道上有几个扇区,由于所有的0扇区用于存放固件以及一些硬盘的专用的文件,最大为63 (用6 位存储)。用户可见扇区从1开始。
相应的,磁头号为0 ~ 255,用H表示;柱面号为0 ~ 1023,用C表示;扇区号为1~63,用S表示。
-
8.46GB问题:CHS模式
-
第2至第4字节是该分区起始地址。其中第2字节为起始磁头号(面号);第3字节的低6位为起始扇区号,高2位则为起始柱面号的高2位;第4字节为起始柱面号的低8位。
-
柱面号最大值为2^10 = 1024(0~1023)
-
扇区号不会超过2^6 - 1 = 63(0~63)
-
磁头号不会超过2^8 = 256(0~255)
- 1024 × 256 × 63 = 16,515,072
-
这个扇区之前的所有物理扇区所包含的字节数为:
- 16,515,072 × 512Bytes= 8,455,716,864 B ≈ 8.46GB
- LBS模式
- Logic Block Address,将磁盘驱动器可以看做一个一维的逻辑块的数组,逻辑块是最小的传输单位。
- CHS与LBA的地址转换
-
磁盘空间的管理
P16
- 位图
- 空闲表法
- 空闲链表法
- 成组链接法
-
磁盘访问时间
- 寻道时间
- 指的是把磁臂(磁头)从当前位置移动到指定磁道上所经历的时间。该时间是启动磁盘的时间s与磁头移动n条磁道所花费的时间之和。
- Ts= m × n + s,m是常数
- 旋转延迟时间:旋转到指定道的时间,平均旋转一半,Tr =1/(2*r)
- 传输时间
- Tt是指把数据从磁盘读出,或向磁盘写入数据所经历的时间, Tt的大小与每次所读/写的字节数b,旋转速度r以及磁道上的字节数N有关
- Tt=b/(rN)
- 寻道时间
-
四种磁盘调度算法
-
先来先服务算法(FCFS)
- 算法思想:按访问请求到达的先后次序服务。
- 优点:简单,公平。
- 缺点:效率不高,相邻两次请求可能会造成最内到最外的柱面寻道,使磁头反复移动,增加了服务时间,对机械部件也不利。
-
最短寻道时间优先算法(SSTF)
- 算法思想:优先选择距当前磁头最近的访问请求进行服务,主要考虑寻道优先。
- 优点:改善了磁盘平均服务时间。
- 缺点:可能产生“饥饿” 现象,造成某些访问请求长期等待得不到服务。
- 调度的两个评价标准:效率+公平
-
扫描算法(SCAN)电梯调度:反复来回
- 算法思想:当有访问请求时,磁头按一个方向移动,在移动过程中对遇到的访问请求进行服务,直到磁头步进到最内(最外)侧磁道调转扫描方向。
- 优点:克服了最短寻道优先的缺点,既考虑了距离,同时又考虑了方向
- 缺点:但由于是摆动式的扫描方法,两侧磁道被访问的频率仍低于中间磁道。
-
循环扫描算法(C-SCAN):单向
-
算法思想:
-
按照所要访问的柱面位置的次序去选择访问者。
-
移动臂到达最后一个柱面后,立即带动读写磁头快速返回到0号柱面。
-
返回时不为任何的等待访问者服务。
-
返回后可再次进行扫描。
-
-
由于SCAN算法偏向于处理那些接近中间磁道的访问请求,所以使用改进型的C-SCAN算法可避免这个问题
- LOOK和C-LOOK
- 釆用SCAN算法和C-SCAN算法时磁头总是严格地遵循从盘面的一端到另一端,显然,在实际使用时还可以改进,即磁头移动只需要到达最远端的一个请求即可返回,不需要到达磁盘端点。
- 这种形式的SCAN算法和C-SCAN算法称为LOOK和C-LOOK调度。这是因为它们在朝一个给定方向移动前会查看是否有请求。
“磁臂粘着”现象:有一个或几个进程对某一磁道有较高的访问频率, 即这个(些)进程反复请求对某一磁道的I/O操作,从而垄断了整个磁盘设备。
-
6.3 提高磁盘I/O性能
P29
6.4 廉价冗余磁盘阵列
-
RAID
- 一种把多块独立的硬盘(物理硬盘)按照不同方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据冗余的技术
- 优点
- 成本低,功耗小,传输速率高
- RAID比起传统的大直径磁盘驱动器来,在同样的容量下,价格要低许多。
- RAID让很多磁盘驱动器并行传输数据,比单个磁盘驱动器提高几倍、几十倍甚至上百倍的速率。有效缓解了快速的CPU与慢速的磁盘之间的矛盾
- 可提供容错功能
- 普通磁盘驱动器只能通过CRC(循环冗余校验)码提供简单的容错,RAID建立在每个磁盘驱动器的硬件容错功能之上,可提供更高的可靠性。
- 成本低,功耗小,传输速率高
- 数据分段并行交叉存取
- 把一个文件的数据分成多个条带(striping)写到多个硬盘,每个条带的大小可以按需调整。
-
RAID分级
P33
7 文件系统
7.1 文件系统基本概念
7.2 文件系统实现方法
-
文件控制块
- 基本信息
- 文件名:字符串,通常在不同系统中允许不同的最大长度,可修改
- 物理位置;
- 文件逻辑结构:有/无结构(记录文件,流式文件)
- 文件物理结构:(如顺序,索引等)
- 访问控制信息
- 文件所有者(属主):通常是创建文件的用户,或者改变已有文件的属主;
- 访问权限(控制各用户可使用的访问方式):如读、写、执行、删除等;
- 使用信息
- 创建时间,上一次修改时间,当前使用信息等。
- 基本信息
-
文件逻辑结构和物理结构
- 文件逻辑结构(文件组织)
- 提高检索效率;便于修改;降低文件存储代价。
- 文件物理结构
- 文件在存储介质上的存放方式,表示了一个文件在文件存储介质上的位置、链接和编目的方法。
- 主要解决两个问题:
- 假设一个文件被划分成N块,这N块在磁盘上是怎么存放的?
- 其地址(块号或簇号)是怎样记录的?
- 主要结构:连续结构、索引结构、串联结构
- 文件逻辑结构(文件组织)
-
物理结构的三种结构
-
连续结构
-
-
优点:
- 结构简单,实现容易,不需要额外的空间开销
- 顺序存取和随机存取的效率高
-
缺点:
-
文件长度一经固定便不易改变,容易出现磁盘碎片;
-
不利于文件的动态增加和修改;
-
-
适合于变化不大的文件
-
-
-
串联结构
-
什么是串联结构
-
串联文件结构是按顺序由串联的块组成的,即文件的信息按存储介质的物理特性存于若干块中。
-
每个物理块的最末一个字(或第一个字)作为链接字,它指向后继块的物理地址。链首指针存放在该文件目录中。文件的结尾块的指针为“∧”,表示文件至本块结束。
-
对于记录式文件,一块中可包含一个逻辑记录或多个逻辑记录,也可以若干物理块包含一个逻辑记录。
-
优点:
- **空间利用率高;**能较好的利用外存空间;
- 文件动态扩充和修改容易;
- 顺序存取效率高;
类似于存储管理中的页式
-
缺点:
- 随机存取效率太低,如果访问文件的最后的内容,实际上是要访问整个文件。
- 可靠性问题,如指针出错;
- 链接指针占用一定的空间。
-
-
-
索引结构
- 什么是索引结构
- 一个文件的信息存放在若干个不连续物理块中
- 系统为每个文件建立一个专用数据结构:索引表,并将这些物理块的块号存放在该索引中。
- 索引表就是磁盘块地址数组,其中第i个条目指向文件的第i块。
- 索引文件:系统为每个文件建立逻辑块号与物理块号的对照表,称为文件的索引表。文件由数据文件和索引表构成。这种文件称为索引文件。
- 什么是索引结构
-