操作系统的概念:操作系统(Operating System)控制和管理整个计算机系统的硬件和软件资源,合理地调度计算机的工作和资源,提供给用户和其他软件方便的接口和环境,它是计算机系统中最基本的系统软件
操作系统应具有的功能:
操作系统作为系统资源的管理者:处理机(进程)管理、存储器(内存)管理、文件管理、设备管理
操作系统作为用户与计算机硬件之间的接口:命令接口(允许用户直接使用)、程序接口(由一组系统调用组成,程序接口 = 系统调用 = 系统调用命令 = 广义指令)、图形用户界面
操作系统作为最接近硬件的层次:实现对硬件机器的扩展(裸机上安装操作系统便可以提供资源管理功能以方便用户使用)
操作系统的特征:
其中 并发和共享 是两个最基本的特征,二者互为存在条件
操作系统的发展和分类:
手工操作阶段
批处理阶段(单、多道批处理)
分时操作系统:计算机以时间片为单位轮流为各个用户 / 作业服务,各个用户可通过终端与计算机进行交互
实时操作系统(硬、软实时系统)
网络操作系统
分布式操作系统
个人计算机操作系统
其中 多道批处理系统 的出现标志着操作系统开始出现
两种指令
两种处理器状态:用程序状态字寄存器(PSW)的某个标志位标识当前处理器状态
两种程序
操作系统内核:细分为大内核与微内核。内核是计算机上配置的底层软件,是操作系统最基本、最核心的部分,需要实现时钟管理、中断管理、原语等核心功能。对系统资源进行管理时还需要实现进程管理、内存管理、设备管理、文件管理等功能
一气呵成,不可中断)中断机制的诞生:为了实现多道应用程序并发执行而引入的一种技术。中断可以使 CPU 从 用户态 -> 核心态,使操作系统获得计算机的控制权,从而开展一系列的管理工作
中断的分类:
异常、陷入、例外。信号来源于 CPU 内部,与当前执行的指令有关用户态与核心态的切换:
中断 是唯一途径PSW)标志位设置为 “用户态”系统调用概念:系统调用是操作系统提供给应用程序(编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以发出系统调用请求来获得操作系统的服务。系统调用发生在用户态,对系统调用的处理发生在核心态
系统调用按功能分类:设备管理、文件管理、进程控制、进程通信、内存管理
系统调用与库函数的区别:

内中断 从而使 CPU 进入核心态唯一 一个只能在 用户态 下执行而不能在核心态下执行的指令PCB:系统为每个运行的程序配置一个数据结构称为进程控制块(PCB),用来描述进程的各种信息(指令和数据)。其中包括了进程描述信息、进程控制和管理信息、资源分配清单、处理机相关信息等
进程与进程实体:
进程的组织方式:
进程的特征:动态性、并发性、独立性、异步性、结构性
进程的三种基本状态:运行态(Running)、就绪态(Ready)、阻塞态(Waiting/Blocking)
进程的另外两种状态:新建(New)、终止态(Terminated)
进程状态间转换过程:

“关中断指令” 和 “开中断指令” 实现发送消息 / 接收消息” 两个原语进行数据交换半双工通信 即某一时间段内只能实现单向数据传输
互斥 地访问管道write() 系统调用将被阻塞,等待中的读进程将数据读走。当读进程将数据全部取走后,管道变空,此时读进程的 read() 系统调用将被阻塞线程概念:线程是一个基本的 CPU 执行单元,也是程序执行流的最小单位。引入线程之后,进程只作为除 CPU 之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。进程是 资源分配 的基本单位,而线程是 处理机调度 的基本单位
线程的属性:
TCB)线程的实现方式:
用户级线程(User-Level Thread,ULT):“从用户视角能看到的线程”,由应用程序通过线程库实现,所有的线程管理工作都由应用程序负责,在用户态下即可完成,无需操作系统的干预

内核级线程(Kernel-Level Thread,KLT):“从操作系统内核视角能看到的线程”,又称内核支持的线程。内核级线程的管理工作由操作系统内核完成,因而内核级线程的切换必然需要在核心态下才能完成

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

一对一模型:一个用户级线程映射到一个内核级线程,即一个用户进程对应多个内核级线程

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

高级调度(作业调度):外存与内存之间的调度
中级调度(内存调度):决定将哪个处于 挂起状态 的进程重新调入内存
引入虚拟存储技术之后,可以将暂时不能运行的进程调至外存等待,等它重新具备了运行条件且内存空闲时重新调入内存,这么做的目的是为了提高内存利用率和系统吞吐量。值得注意的是,进程的 PCB 并不会一起调到外存,而是会常驻内存。PCB 中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的 PCB 来保持对各个进程的监控和管理
挂起状态:暂时调到外存等待的进程状态称为 挂起状态(suspend)。“挂起” 和 “阻塞” 的区别:挂起态将进程映像调到外存,阻塞态下进程映像仍在内存中
进程调度):主要任务是按照某种方法或策略从就绪队列中选取一个进程,并将处理器分配给它。进程调度是操作系统中最基本的一种调度,频率很高,一般几十毫秒调度一次三种处理机调度层次的联系和对比:
| 主要任务 | 发生时机 | 频率 | 状态影响 | |
|---|---|---|---|---|
| 高级调度(作业调度) | 从后备队列中选择作业将其调入内存并创建进程 | 外存->内存 | 最低 | 无->创建态->就绪态 |
| 中级调度(内存调度) | 从挂起队列中选择合适的进程将其调回内存 | 外存->内存 | 中等 | 挂起态->就绪态 |
| 低级调度(进程调度) | 从就绪队列中选择一个进程为其分配处理机 | 内存->CPU | 最高 | 就绪态->运行态 |

进程调度的方式:非剥夺调度方式(非抢占)和剥夺调度方式(抢占)
不能进行进程调度与切换的情况:
内核程序临界区 中:
CPU 利用率:指 CPU “忙碌” 时间占总时间的比例
系统吞吐量:单位时间内完成的作业数量
周转时间:指从作业被提交给系统开始,到作业完成为止的这段时间间隔,由以下各部分时间组成:高级调度时间 + 低级调度时间 + CPU 上执行时间 + 等待 I/O 完成的时间
先来先服务(FCFS,First Come First Service):非抢占式调度算法,不会导致饥饿现象(某进程/作业长期得不到服务)
短作业优先(SJF,Shortest Job First):存在抢占式和非抢占式会,产生饥饿现象
最短剩余时间优先):每当有 进程加入 就绪队列或当前 进程已完成 时就需要调度,如果新到达的进程的剩余时间比当前运行的进程的剩余时间更短,则由新进程抢占处理器,当前进程重新回到就绪队列高响应比优先(HRRN,Highest Response Ratio Next):非抢占式的调度算法,会产生饥饿现象
时间片轮转(Round-Robin):抢占式调度算法,不会产生饥饿问题
1%优先级:抢占式调度算法,会导致饥饿问题(高优先级的进程/作业源源不断到来)
多级反馈队列:抢占式调度算法,会导致饥饿问题
第 1 级 队列,按 FCFS 原则排队等待被分配时间片,若用完时间片但进程还未结束,则进程进入下一级队列队尾(若已经是最下级队列,则重新放入该队列队尾)第 k 级 队列为空时,才会为 k+1 级队头的进程分配时间片工作次序 而产生的制约关系boolean flag[];,数组中各个元素用来标记各进程想进入临界区的意愿,比如 “flag[0] = true” 意味着 0 号进程想要进入临界区。细分为双标志先检查法和双标志后检查法:区别在于前者 “检查后上锁”,后者 “上锁后检查”
Gary L. Peterson 想到了一种方法,如果双方都争着想进入临界区,那可以让进程尝试 “孔融让梨”,主动让对方先使用临界区
中断屏蔽方法:利用 “开/关中断指令” 实现,即在某进程开始访问临界区到结束访问为止都不允许被中断,也即不能发生进程切换
TestAndSet(TS)或 TestAndSetLock(TSL)指令:使用硬件实现,执行的过程不允许被中断,只能一气呵成(原子操作)
Swap 指令:也称 Exchange(XCHG) 指令。使用硬件的方式实现,执行过程不允许被中断,只能一气呵成。优缺点同 TSL 方式一致
定义:信号量其实就是一个变量(可以是一个整数或是更复杂的记录型变量),可以用一个信号量来标识系统中某种资源的数量
一对原语:wait(W) 和 signal(S) 常简称为 P、V 操作
记录型信号量:
// 信号量定义
typedef struct {
int value; // 剩余资源数
struct process *L; // 等待队列
} semaphore;
// wite 原语申请资源,P 操作
void wait (semaphore S) {
S.value--;
if (S.value < 0) {
// 当前线程阻塞并挂到 S 等待队列队尾
block(S.L);
}
}
// singal 原语释放资源,V 操作
void signal(semaphore S) {
S.value++;
// singal 释放资源后 S.value 仍小于 0,说明仍然存在等待资源的进程在阻塞,则唤醒它
if (S.value <= 0) {
// 存在可用资源且等待队列中存在阻塞线程,唤醒队头线程
wakeup(S.L);
}
}
信号量机制实现互斥:
1信号量实现进程同步:
0信号量机制实现前驱关系:
0生产者、消费者共享一个初始为空、大小为 n 的缓冲区
只有缓冲区未满时,生产者才能将产品放入缓冲区,否则阻塞等待
只有缓冲区非空时,消费者才能从缓冲区消费产品,否则阻塞等待
缓冲区是临界资源,各进程必须互斥地访问
信号量定义:
semaphore mutex = 1; // 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0; // 同步信号量,表示非空缓冲区的数量
生产者:
producer() {
while (1) {
生产一个产品;
P(empty); // 消耗一个空闲缓冲区
P(mutex); // 互斥访问缓冲区
将产品放入缓冲区;
V(mutex); // 释放缓冲区
V(full); // 增加一个产品
}
}
消费者:
consumer() {
while (1) {
P(full); // 消耗一个非空缓冲区
P(mutext); // 互斥访问缓冲区
从缓冲区取走一个产品;
V(mutex); // 释放缓冲区
V(empty); // 增加一个空闲缓冲区
使用产品;
}
}
注:实现互斥的 P 操作一定要放在实现同步的 P 操作之后,否则会产生死锁
桌子上有一个盘子,每次只能向其中放入一个水果
爸爸专向盘子中放入苹果,女儿专等着吃盘子中的苹果
妈妈专向盘子中放入橘子,儿子专等着吃盘子中的橘子
只有盘子为空时,爸爸或妈妈才能向盘子中放入一个水果
只有盘子中存在自己需要的水果时,儿子或女儿才能从盘子中取走自己需要的水果

信号量定义:
semaphore mutext = 1; // 互斥信号量,互斥地访问盘子
semaphore plate = 1; // 同步信号量,盘子可放的水果个数
semaphore apple = 0; // 同步信号量,盘子中的苹果个数
semaphore orange = 0; // 同步信号量,盘子中的橘子个数
爸爸:
dad() {
while (1) {
准备一个苹果;
P(plate);
P(mutex);
将苹果放入盘子;
V(mutex);
V(apple);
}
}
妈妈:
mom() {
while (1) {
准备一个橘子;
P(plate);
P(mutex);
将橘子放入盘子;
V(mutex);
V(orange);
}
}
女儿:
daughter() {
while (1) {
P(apple);
P(mutex);
从盘子中取出苹果;
V(mutex);
V(plate);
吃掉苹果;
}
}
儿子:
son() {
while (1) {
P(orange);
P(mutex);
从盘子中取走橘子;
V(mutex);
V(plate);
吃掉橘子;
}
}
注:实现互斥的 P 操作一定要放在实现同步的 P 操作之后,否则会产生死锁
假设一个系统有三个抽烟者进程和一个供应者进程
香烟由三种材料组成:烟草、纸、胶水,每个抽烟者不停地卷烟并抽掉
三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水
供应者每次将两种材料放在桌上,拥有剩余那种材料的抽烟者拿走桌上的材料后卷起香烟抽掉,并通知供应者完成了
供应者接着便会在桌上放入另外两种材料,如此往复从而实现让三个抽烟者轮流抽烟
信号量定义:
semaphore offer1 = 0; // 同步信号量,桌上材料组合一的数量
semaphore offer2 = 0; // 同步信号量,桌上材料组合二的数量
semaphore offer3 = 0; // 同步信号量,桌上材料组合三的数量
semaphore finish = 0; // 抽烟是否完成
int i = 0; // 用于实现抽烟者轮流抽烟
供应者:
provider() {
while (1) {
if (i == 0) {
将组合一放在桌上;
V(offer1);
} else if (i == 1) {
将组合二放在桌上;
V(offer2);
} else if (i == 2) {
将组合三放在桌上;
V(offer3);
}
i = (i + 1) % 3;
P(finish); // 等待来自吸烟者的完成信号
}
}
吸烟者:
smoker1() {
while (1) {
P(offer1);
从桌上拿走组合一;
卷烟;
抽掉;
V(finish);
}
}
smoker2() {
while (1) {
P(offer2);
从桌上拿走组合二;
卷烟;
抽掉;
V(finish);
}
}
smoker3() {
while (1) {
P(offer3);
从桌上拿走组合三;
卷烟;
抽掉;
V(finish);
}
}
注:缓冲区大小为 1,同一时刻 4 个同步信号量中至多有一个值为 1,不会被 P 操作阻塞,因而无需设置专门的互斥信号量实现对缓冲区的互斥访问
有读者和写者两组并发进程共享一个文件
当两个及以上读进程同时访问共享数据时不会导致数据不一致的问题
当某个写进程和其它进程同时访问共享数据则可能导致数据不一致的错误。因此要求:
- 允许多个读者同时对文件执行读操作
- 同一时刻只允许一个写者往文件中写入信息
- 任一写者在完成写操作之前不允许其他读者或写者工作
- 写者执行写操作前,应让已有的读者和写者全部退出
信号量定义:
semaphore rw = 1; // 用于实现对文件的互斥访问,表示当前是否有进程在访问共享文件
int count = 0; // 用于记录当前有几个进程在访问共享文件
semaphore mutext = 1; // 用于保证对 count 变量的互斥访问
semaphore w = 1; // 用于实现 “写优先”,保证写进程不会饿死
写者:
writer() {
while (1) {
P(w);
P(rw); // 写之前加锁
写文件;
V(rw); // 写之后解锁
V(w);
}
}
读者:
reader() {
while (1) {
P(w);
P(mutex); // 各都进程间互斥访问 count
if (count == 0) {
P(rw); // 第一个读进程负责 “加锁”
}
count++;
V(mutex);
V(w);
读文件;
P(mutex);
count--;
if (count == 0) {
V(rw); // 最后一个读进程负责 “解锁”
}
V(mutex);
}
}
一张圆桌上坐着 5 位哲学家,每两个哲学家之间摆一根筷子,桌子中间是一碗米饭
哲学家们倾注毕生的经历用于思考和进餐,哲学家在思考时,并不影响其他人
只有当哲学家饥饿时,才试图拿起左、右两个筷子(一根一根地拿起),如果筷子已经在他人手中则等待
饥饿的哲学家只有成功拿到两根筷子时才可以开始进餐,进餐完毕后继续思考
如何避免死锁现象的发生呢?
// 信号量定义
semaphore chopsticks[5] = {1, 1, 1, 1, 1};
semaphore mutex = 1;
Pi() {
while (1) {
P(mutex);
P(chopsticks[i]); // 拿左
P(chopsticks[(i + 1) % 5]); // 拿右
V(mutex);
吃饭;
V(chopsticks[i]); // 放左
V(chopsticks[(i + 1) % 5]); // 放右
思考;
}
}
为什么要引入管程:解决信号量机制编程麻烦、易出错的问题
管程是一种特殊的软件模块(类比 类),由以下部分组成:
类字段)类方法)构造器、代码块)类名)管程的基本特征:
管程的补充说明:
编译器 实现的设置条件变量 及 等待/唤醒 操作以解决同步问题死锁、饥饿与死循环:
产生死锁必须满足的四个条件:
死锁的发生时机:
死锁的处理:
SPOOLing 技术):把只能互斥使用的资源改造为允许共享使用
静态分配方法,进程在运行前一次性申请完它所需的全部资源
顺序资源分配法,给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(编号相同的资源)一次性申请完
安全序列:所谓安全序列,是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就处于安全状态,否则系统就进入了不安全状态
使用银行家算法避免死锁:
银行家算法数据结构:
public class Banker {
// 各种可用资源数量
int[] available = new int[m];
// 各进程对资源的最大需求数
int[][] max = new int[n][m];
// 已经给个进程分配的资源
int[][] allocatI/On = new int[n][m];
// 各进程最多还需要的资源数(max - allocatI/On)
int[][] need = new int[n][m];
// 进程此次申请的各种资源数
int[] request = new int[m];
}
银行家算法步骤:
安全性算法 检查此次分配是否会导致系统进入不安全状态安全性算法:检查当前剩余可用资源是否能满足某个进程的最大需求,如果可以就把该进程加入安全序列并将其持有的资源全部回收。不断重复上述过程,看最终是否能让所有进程都进入安全序列
死锁的检测:
用某种数据结构(资源分配图)来保存资源的请求和分配信息
提供一种算法,利用上述信息来检测系统是否已进入死锁状态(依次消除与不阻塞进程相连的边,直到无边可消)
资源分配图:包含两种节点和两种边
- 进程节点(P1、P2):对应一个进程
- 资源节点(R1、R2):对应一类资源,包含多个资源
- 进程节点 -> 资源节点:表示进程想要申请几个资源(每条边代表一个)
- 资源节点 -> 进程节点:表示已经为进程分配了几个资源(每条边代表一个)

死锁的解除:
程序的编译运行步骤:
- 编译:由编译程序将用户源代码编译成若干个
目标模块(高级语言翻译为机器语言)- 链接:由链接程序将编译后的一组
目标模块以及所需库函数链接在一起,形成一个完整的装入模块- 装载:由装入程序将装入模块装入内存运行

三种链接方式:静态链接、装入时动态链接、运行时动态链接
模块装入的三种方式:即使用三种不同的方法完成逻辑地址到物理地址的转换
重定位寄存器 的支持内存管理需要实现的功能:
内存空间的分配与回收
内存空间的扩充:操作系统需要提供某种技术从逻辑上对内存空间进行扩充
地址转换:操作系统需要提供地址转换功能,负责将程序的逻辑地址转换为物理地址(绝对装入、静态重定位、动态重定位)
存储保护:操作系统需要提供内存保护功能,保证个进程在各自存储空间内运行,互不干扰。具体实现方式分以下两种:
基址寄存器)和界地址寄存器(限长寄存器)进行越界检查。基址寄存器存放的是进程的 起始物理地址,限长寄存器存放的是进程的 最大逻辑地址
覆盖技术:用来解决 “程序大小超过物理内存总和” 的问题,思想是将程序分为多个段,常用的段常驻内存,不常用的段在需要时调入内存。内存中分为一个 “固定区” 和若干个 “覆盖区”
交换技术:内存空间紧张时,系统将内存中某些进程暂时换出外存(进程挂起),把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)

在具有交换功能的操作系统中,通常将磁盘空间划分为 文件区 和 对换区 两部分
离散分配 方式连续分配 方式单一连续分配:内存被分为 系统区 和 用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据
固定分区分配:将整个用户空间划分为多干个的分区,每个分区中只能装入一道作业。操作系统需要建立一个数据结构(分区说明表)来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态
动态分区分配:也称可变分区分配。不需要预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要,因此系统分区的大小和数目是可变的。操作系统需要采用 空闲分区表 或 空闲分区链 的数据结构来记录分区的使用情况
动态分区分配算法:
首次适应算法(First Fit):空闲分区以 地址递增 的次序排列,每次分配内存都从低地址开始查找
邻近适应算法(Next Fit):空闲分区以 地址递增 的次序排列(可排成循环链表),每次分配从上次结束的位置开始查找
最佳适应算法(Best Fit):空闲分区按 容量递增 的次序链接。每次分配内存时顺序查找空闲分区表(或空闲分区链)
最坏适应算法(Worst Fit):空闲分区按 容量递减 次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表)
| 算法 | 思想 | 分区排列顺序 | 优点 | 缺点 |
| :------- | :------------------------------------------- | :----------- | :----------------- | :----------------------- |
| 首次适应 | 从头到尾查找合适的分区 | 地址递增 | 性能好、算法开销小 | |
| 邻近适应 | 由最佳适应演变而来,每次从上次结束的位置查找 | 地址递增 | 算法开销小 | 高地址的大分区被用完 |
| 最佳适应 | 优先使用更小的分区以保留更多大的分区 | 容量递增 | 保留更多的大分区 | 外部碎片多、算法开销大 |
| 最坏适应 | 优先使用更大的分区以产生太多小的外部碎片 | 容量递减 | 减少了外部碎片 | 不利于大进程、算法开销大 |
页框:也称页帧、内存块、物理块。将 内存空间 分为一个个大小相等的分区(4KB/分区),每个分区就是一个 “页框”。每个页框有一个 “页框号”,也称页帧号、内存块号、物理块号。页框号由低地址向高地址从 0 开始编号
页:也称页面。将 用户进程的地址空间` 分为与页框大小相等的一个个区域即为页。每个页有一个 “页号”,页号从 0 开始编号。操作系统以页框为单位为各个进程分配内存空间,进程的每个页面分别放入一个页框中。换言之,进程的页面与内存空间的页框有着一一对应的关系
基本分页存储管理如何实现逻辑地址到物理地址的转换:
如果让每个页面的大小为 2 的整数幂,计算机可以很方便地得出一个逻辑地址对应页号和页内偏移量。每个页面大小为 2kB,用二进制数表示逻辑地址:则末尾 k 位即为页内偏移量,其余部分为页号
如果有 K 位表示 “页内偏移量”,则说明系统中一个页面的大小是 2K 个内存单元
如果有 M 位标识 “页号”,则说明系统中一个进程最多允许有 2M 个页面

页表:操作系统需要为每个进程建立一张页表用于记录进程页面与实际存放的内存块之间的对应关系

通常会在系统中设置一个 页表寄存器(PTR)存放页表在内存中的起始地址 F 和页面长度 M。进程未执行时,页面的始址和页面长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把他们放到页表寄存器中
设页面大小为 L,逻辑地址 A 到物理地址 E 的变换过程如下;
P >= M 则抛出越界中断,否则继续执行页表起始地址F + 页号P * 页表项长度,根据页表项地址取出内存块号 b
内存块号b * 页面大小L + 页内偏移量W
两个局部性原理说明:
时间局部性原理:如果执行了程序中的某条指令,那么不久后这条指令很可能会被再次执行;如果某个数据被访问过,那么不久后该数据很可能会被再次访问
空间局部性原理:一旦程序访问了某个存储单元,在不久之后其附近的单元也很有可能被访问
快表:又称为 联想寄存器(TLB),是一种访问速度比内存快很多的 高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,内存中的页表常称为慢表。具有快表的地址变换机制如下:
单级页表存在的问题:
页表必须连续存放,当页表很大时,需要占用许多个连续的页框
没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面就可运行
分级页表:将长长的页表进行分组,使每个内存块刚好可以放入一个分组。分组后需要为离散分配的页表项分组再建立一张页表,称为页目录表,或称外层页表、顶层页表

两级页表结构如何实现逻辑地址到物理地址的转换:
按照地址结构将逻辑地址拆分为三部分【一级页号,二级页号,页内偏移量】
从 PCB 中读出页目录表起始地址,再根据一级页号查页目录表,找到二级页表在内存中的存放位置
根据二级页号查页表,找到最终想访问的内存块号
结合页内偏移量得到物理地址
例如:将逻辑地址(0000000000,0000000001,111111111111)转化为物理地址(使用十进制表示)
0# 页表存放在内存块号为 3 的内存块中,从中取出二级页表1 知最终想访问的内存块号为 44 * 4096(4KB/块) + 1023 = 17407
多级页表分级主要注意的小细节:
多级页表中,各级页表的大小不能超过一个页面(4KB)。若两级页表不够,可以分更多级
多级页表的访存次数:N 级页表访问一个逻辑地址需要 N+1 次访存(无快表机制)
进程的地址空间:按照程序自身的逻辑关系划分为若干段,每段都有一个段名(低级语言中程序员使用段名来编程),每段从 0 开始编址
内存分配规则:以段为单位进行分配,每个段在内存中占据 连续 空间,各段之间可以不相邻
分段系统的逻辑地址结构由 段号(段名)和 段内地址(段内偏移量)所组成
段表:
基址“)和段的长度
分段与分页存储管理的对比:
页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,具体细节对用户不可见;段是信息的逻辑单位。分段的主要目的是为了更好地满足用户需求。一个端通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。分段比分页更容易实现信息的共享和保护页的大小固定且由操作系统决定;段的长度不固定,取决于用户编写的程序分页的用户进程地址空间是一维的,程序只需给出一个记忆符即可表示一个地址;分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址| 优点 | 缺点 | |
|---|---|---|
| 分页 | 内存空间利用率高,不会产生外部碎片,只有少量的内部碎片 | 不方便按照逻辑模块实现信息的共享和保护 |
| 分段 | 很方便按照逻辑模块实现信息的共享和保护 | 如果段长很大,为其分配很大的连续空间会很不方便,段式管理会产生外部碎片 |
段页式管理:将程序按照逻辑模块分段,对各段进行分页(如 4KB/页),再将内存空间分为大小相同的页框(内存块),新建进程时将各页面分别装入各内存块中

段页式管理的地址转换:
传统存储管理(连续分配与非连续分配)存在的问题:
虚拟内存的三个主要特征:
虚拟内存的容量:
最大容量 是由计算机的地址结构(CPU 寻址范围)确定的实际容量 = min (内存和外存容量之和,CPU 寻址范围)虚拟内存的实现:请求分页存储管理、请求分段存储管理、请求段页式存储管理。操作系统需要提供请求调页和页面置换功能
缺页中断:是因为当前执行的指令想要访问的目标页面未调入内存而产生的中断,因此属于内中断
最佳置换算法(OPT,Optimal):每次选择淘汰的页面将是以后永不使用,或者在很长时间内不再被访问的页面,这样可以保证最低的缺页率
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面,将调入内存的页面按序排成队列,置换队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块
最近最久未使用置换算法(LRU,Least Recently Used):每次淘汰最近最久未使用的页面,在请求分页管理页表项中用访问字段记录该页面自上次被访问以来所经历的时间
时钟置换算法:又称 CLOCK 算法或最近未用算法(NRU,Not Recently Used)
简单 CLOCK 算法:为每个页面设置一个 访问位,再将内存中的页面通过链接指针链接成一个循环队列。当某页被访问时,访问位设置为 1。当需要一个淘汰页面时,检查页面的访问位,如果是 0 则将该页换出;如果是 1 则不换出,但需将访问位设置为 0,继续检查下一个页面。简单的 CLOCK 算法选择淘汰一个页面最多会经历两轮扫描(所有页面访问位均为 1)
改进型时钟置换算法:除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面是否被修改过。在其它条件相同时,应优先淘汰没有被修改过的页面(无需 I/O 写入外存)。对页面引入 访问位 和 修改位 并将所有可能被置换的页面排成一个循环队列。
- 第一轮:扫描第一个(0,0)页面用于替换(未访问,未修改)
- 第二轮:扫描第一个(0,1)页面用于替换,将扫描过的页面访问位设置为 0
- 第三轮:扫描第一个(0,0)页面用于替换
- 第四轮:扫描第一个(0,1)页面用于替换
| 局部置换 | 全局置换 | |
|---|---|---|
| 固定分配 | ✓ | - |
| 可变分配 | ✓ | ✓ |
页面调入策略:
从何处调入页面;
抖动(颠簸)现象:刚刚换出的页面马上又要调入内存,刚刚调入的页面马上又要换出外存。主要原因是分配给进程的内存块不足
驻留集与工作集:
无结构文件:文件内部的数据就是一系列二进制流或字符流,又称 “流式文件”
有结构文件:由一组相似的记录组成,又称 “记录式文件”,每条记录由若干个数据项组成

文件控制块(FCB):FCB 中包含了文件的基本信息、存取控制信息、使用信息等。一个 FCB 就是一个文件目录项,FCB 的有序集合称为 “文件目录”
文件的目录结构:
单级目录结构:整个系统只建立一张目录表,每个文件占一个目录项。单级目录结构要求文件不能重名
两级目录结构:分为主文件目录(Master File Directory)和用户文件目录(User File Directory)。允许不同用户的文件重名,实现了文件访问权限控制。两级目录结构用户不能对文件进行分类管理
树形目录结构:不便于实现文件的共享
无环图目录结构:在树形目录结构的基础上,增加一些指向同一节点的有向边,使整个目录成为一个有向无环图,可以很方便地实现多个用户间的文件共享。需为共享节点设置一个共享计数器,计数器为 0 时才真正删除该节点
索引结点:除文件名之外的所有信息都放到索引结点中,每个文件对应一个索引结点。目录项中只包含文件名、索引结点指针,因而每个目录项的长度大幅减小。由于目录项长度减小,每个磁盘块可以存放更多个目录项,因此检索文件时磁盘 I/O 次数就减少了很多
类似于内存分页,磁盘中的存储单元也会被划分为一个个 “块/磁盘块/物理块”。许多操作系统中将磁盘块的大小设为内存块(页帧)、页面(页)的大小

连续分配:要求每个文件在磁盘上占有一组连续的块,并且文件目录的 FCB 中需记录文件的起始块号和块长度

链接分配:
隐式链接分配方式:文件目录的 FCB 中记录文件存放的起始块号号结束块号,每个磁盘块中都会保存指向下一盘块的指针

显式链接分配方式:把用于连接文件各物理块的指针显式地存放在一张表中,即文件分配表(FAT,File AllocatI/On Table)。一个磁盘只会建立一张文件分配表,开机时文件分配表载入内存后常驻内存

索引分配:文件离散地分配在各磁盘块中,系统会为每个文件建立一张 索引表,索引表中记录了文件的各个逻辑块对应的物理块。索引表存放的磁盘块称为索引块,文件数据存放的磁盘块称为数据块

文件太大,索引表项 过多的解决方案:
0 ~ i-1 号索引块,磁盘 I/O 次数过多,查找效率低K 层索引结构,且顶级索引表为调入内存,则访问一个数据块只需要 K+1 次磁盘 I/O 操作

磁盘空间的划分与初始化:将物理磁盘划分一个个的文件卷,又称逻辑卷或逻辑盘(C 盘、D 盘)。文件卷包含了目录区和文件区两部分:
对空闲磁盘块的管理:
空闲表法:记录第一个空闲盘的块号和空闲盘块数,适用于连续分配方式
空闲链表法:
位示图法:每个二进制位对应一个盘块。使用 0 代表盘块空闲,1 代表盘块已分配。位示图一般用连续的 “字” 来表示,字 中的每一位对应一个盘块

成组链接法:文件卷的目录区中专门用一个磁盘块作为 “超级块”,当系统启动时需要将超级块读入内存,并且要保证内存与外存中的超级块数据一致。超级块中记录了下一组空闲盘块的数量以及空闲块号,依据此空闲盘块号可找到下一个 “超级块”,依次类推
打开计数器 count–,若 count == 0 则删除对应表项文件共享的两种方式:
文件保护的三种方式:
Access-Control List,ACL),该表中记录了各个用户可以对文件执行的操作
磁盘的分类:
磁盘结构:
柱面号,盘面号,扇区号)。为什么磁盘的物理地址是(柱面号,盘面号,扇区号)而不是(盘面号,柱面号,扇区号):读取地址连续的磁盘块时,采用前者可以减少磁头更换磁道消耗的时间
先来先服务(FCFS)
最短寻找优先(SSTF):优先处理与当前磁头最近的磁道,保证每次寻道时间最短

扫描算法(SCAN):只有磁头移动到最外侧磁道时才能往内移动,移动到最内侧磁道时才能往外移动(电梯算法)

LOOK 调度算法:在 SCAN 算法的基础上,如果在磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向

循环扫描算法(C-SCAN):只有磁头朝某个特定方向移动时才处理磁道访问请求,返回时直接快速移动至起始端而不处理任何请求

C-LOOK 调度算法:在 C-SCAN 算法的基础上,如果磁头移动的方向上已经没有任何磁道访问请求,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可

一次磁盘 I/O 操作所需时间: T = TS + 1/2r + b/(rN)
减少磁盘延迟时间的方法:磁头读入一个扇区数据后需要一小段时间进行处理,如果逻辑上相邻的扇区在物理上也相邻,则读入几个连续的逻辑扇区可能需要很长的 “延迟时间”(需要盘片持续旋转)
磁盘的初始化:
引导块:计算机开机时需要进行一系列初始化工作,这些初始化工作是通过执行初始化程序(自举程序)完成的。通常在只读存储器(ROM)中存放很小的 “自举程序”,完整的自举程序放在系统磁盘的 引导块 上,引导块位于磁盘的固定位置。开机时计算机先运行 ROM 中的 “自举程序”,通过执行该程序就可以找到引导块,并将完整的 “自举程序” 读入内存,完成系统初始化

I/O 控制器概念:CPU 无法直接控制 I/O 设备的机械部件,因此 I/O 设备还要有一个电子部件作为 CPU 与 I/O 设备机械部件之间的 ”中介“,用于实现 CPU 对设备的控制。这个电子部件就是 I/O 控制器,又称设备控制器
I/O 控制器的功能:
I/O 控制器的组成:

对 I/O 设备寄存器的编址方式:
程序直接控制方式:CPU 首先向 I/O 控制器发出读指令,在 I/O 控制器的控制下 I/O 设备启动, I/O 控制器中状态寄存器的值设为 1(未就绪);之后 CPU 轮询检查控制器的状态;输入设备将准备好的数据传给 I/O 控制器,并报告自身状态;控制器将输入数据放到数据寄存器中,并将状态寄存器的值修改为 0(已就绪);CPU 轮询发现设备已就绪,将 I/O 控制器中数据寄存器中的内容读入自己的寄存器,而后再放入内存;若还要继续读入数据,则 CPU 继续发出读指令
中断驱动方式:由于 I/O 设备速度很慢,因此在 CPU 发出读/写命令后,可将等待 I/O 的进程阻塞,切换运行别的进程。当 I/O 完成后,控制器会向 CPU 发出一个中断信号,CPU 在每一条指令完成后均会检测是否存在中断信号,若存在则会保存当前进程的运行环境,转而执行中断处理程序处理该中断。CPU 从 I/O 控制器的数据寄存器中读一个字的数据传送到 CPU 寄存器,而后再写入内存。接着 CPU 恢复等待 I/O 进程的运行环境,然后执行该进程
直接存取控制方式:Direct Memory Access(DMA),主要用于块设备的 I/O 控制。数据传送的单位是 “块”,数据流向是从 I/O 控制器的数据寄存器直接流向内存,不再需要 CPU 的中介处理,仅在传送数据块的开始和结束时才需要 CPU 干预

通道控制方式:通道是一种硬件(“弱鸡版” CPU),可以识别并执行一系列的通道指令

LUT,Logical Unit Table)” 来确定逻辑设备对应的物理设备,并找到该设备对应的设备驱动程序
假脱机技术:又称 “SPOOLing 技术”,是用软件的方式模拟脱机技术。SPOOLing 技术可以把一台物理设备虚拟成逻辑上的多台设备,可将独占式设备改造成共享设备。由以下几部分组成:

共享打印机原理分析:当多个用户进程提出打印的请求时,系统会答应它们的请求,但是并没有真正意义上把打印机分配给他们,而是由假脱机管理进程为每个进程服务:
设备分配时应考虑的因素:
设备分配的两种方式:
设备分配管理中的数据结构:
设备控制表(Device Control Table):设备类型、设备标识符、设备状态、指向控制器表的指针、重复执行次数或时间、设备队列的队首指针
控制器控制表(COCT):控制器标识符、控制器状态、指向通道表的指针、控制器队列的队首指针、控制器队列的队首指针
通道控制表(CHCT):通道标识符、通道状态、与通道连接的控制器表首址、通道队列的队首指针、通道队列的队尾指针
系统设备表(SDT):记录了系统中全部设备的情况,每个设备对应一个表目(设备类型、设备标识符、设备控制表、驱动程序入口)

设备分配的步骤:
只有设备、控制器、通道三者都分配成功时才算本次设备分配成功,之后便可启动 I/O 设备进行数据传输。“设备、控制器、通道” 之间的关系:一个通道可控制多个设备控制器,每个设备控制器可控制多个设备
缓冲区的作用:
缓冲区管理的策略:
缓冲池:由系统中共用的缓冲区组成。这些缓冲区按使用状态可分为:
根据一个缓冲区在实际运算过程中扮演的功能不同,又设置了四种工作缓冲区: