操作系统整理

This commit is contained in:
yinkanglong_lab
2021-03-29 22:59:56 +08:00
parent 13f2b65e9e
commit 83527d1a86
79 changed files with 7356 additions and 710 deletions

2
Office/excel/说明.md Normal file
View File

@@ -0,0 +1,2 @@
1. 多级列表表示文字层级
2. 图形,可以表示递进关系

2
Office/word/说明.md Normal file
View File

@@ -0,0 +1,2 @@
1. 多级列表表示文字层级
2. 图形,可以表示递进关系

View File

@@ -22,9 +22,15 @@
- [ ] 标准库(网络编程、多,周)
- 数据结构
- [ ] 《大话数据结构》
- 算法
- [ ] 《数据结构与算法分析》
- [ ] 《算法图解》
- 算法
- [x] 学习、复习枚举法
- [ ] 学习、复习分治法(把相关思想下的问题和算法补充完整)
- [ ] 学习、复习动态规划
- [ ] 学习、复习贪心
- [ ] 学习、复习回溯剪枝
- [ ] 学习、复习分支限界
- 知识复习——基础(一周)
- [ ] 计算机网络
- [ ] 数据库

View File

@@ -1,11 +1,7 @@
# 计划
- [x] 对刷题的内容进行总结。首先进行这个总结。(一半了)
- [x] 实现字符串分割和字符串格式化的方法C++库string篇
- [ ] 学习、复习分治法(把相关思想下的问题和算法补充完整)
- [ ] 学习、复习动态规划
- [ ] 学习、复习贪心
- [ ] 学习、复习回溯剪枝
- [ ] 学习、复习分支限界
- [x] 学习、复习枚举法
- [x] 学习C++容器。对容器的构造函数进行总结。
- [x] 学习C++容器。对每个容器的基础操作进行总结。
- [x] 学习C++模板算法。对算法的第三个参数进行总结。

View File

@@ -1,8 +1,8 @@
## 计划
- [x] pytorch动态数据分类
- [ ] sklearn静态数据分类
- [ ] pytorch静态数据分类
- [x] sklearn静态数据分类
- [x] pytorch静态数据分类
## 收获

View File

@@ -5,14 +5,19 @@
- [x] 字符串分割与格式化方法总结
- [x] 位运算与补码总结
- [x] 大数运算
- [ ] n个骰子的点数
- [ ] 约瑟夫问题
- [ ] 手写所有排序
- [x] n个骰子的点数
- [x] 约瑟夫问题
- [x] 手写所有排序
## 收获
* LeetCode的笔记终于整理完了。对各类算法题有了初步的认识。接下来数据结构与算法。主要整理各个算法思想下的典型问题。结合本科时候的笔记。彻底整理完成小尾巴。
* 基础知识的复习要开始了。感觉已经满了很多。不仅仅有基础知识。
* 还有自己的项目经历的整理。应该整理一份三到四个项目经历的说明文档。用到了那些技术。你在其中主要做了什么事情。
* 联邦学习和恶意软件的进度。每天都要推进一些。至少应该在本周把基础框架和应用方法整理完成。
* 相当于现在同时上:数据结构与算法、计算机网络、数据库、操作系统、机器学习。五门超级大的课程。确实很有挑战。加油吧。少年。清明节能回家吗?堪忧啊。生活总是这么让人头秃。。。

View File

@@ -0,0 +1,8 @@
## 计划
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [x] 数据结构预算法——蛮力法整理完成
- [ ] 基础知识与项目经历——操作系统整理开始part1
- [ ] 联邦学习与恶意软件——pysyft学习完成
## 收获

View File

@@ -0,0 +1,10 @@
## 计划
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构预算法——分治法整理完成
- [ ] 基础知识与项目经历——操作系统整理完成part2
- [ ] 联邦学习与恶意软件——TensorFlow学习开始part1
## 收获

View File

@@ -0,0 +1,9 @@
## 计划
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构预算法——动态规划和贪心整理完成
- [ ] 基础知识与项目经历——数据库整理完成
- [ ] 联邦学习与恶意软件——TensorFlow学习完成part2
## 收获

View File

@@ -0,0 +1,7 @@
## 计划
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构预算法——回溯剪枝和分支限界整理完成
- [ ] 基础知识与项目经历——计算机网络整理完成
- [ ] 联邦学习与恶意软件——TensorFlow federated学习开始part1

View File

@@ -0,0 +1,7 @@
## 计划
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构预算法——图算法和随机化算法整理完成
- [ ] 基础知识与项目经历——项目经历整理完成
- [ ] 联邦学习与恶意软件——TensorFlow federated学习完成part2

62
操作系统/0 概述.md Normal file
View File

@@ -0,0 +1,62 @@
# 计算机操作系统 概述
> 计算机操作系统的组成
## 1 操作系统引论
1. 操作系统的目标和作用
2. 操作系统的发展过程
3. 操作系统的基本特性
4. 操作系统的主要功能
5. OS结构设计
## 2 进程管理
1. 进程的基本概念
2. 进程控制
3. 进程同步
4. 进程通信
5. 线程
6. 管程
## 3 处理机管理(处理机的调度与死锁)
1. 处理机调度的层次
2. 调度队列模型和调度准则
3. 调度算法
4. 实时调度
5. 产生死锁
6. 预防死锁
7. 死锁的检测与解除
## 4 存储器管理
1. 存储器的层次结构
2. 程序的装载和链接
3. 连续分配方式
4. 分页存储管理方式
5. 分段存储管理方式
6. 虚拟存储器的基本概念
7. 请求分页存储管理的方式
8. 页面置换算法
9. 请求分段存储管理的方式
## 5 设备管理
1. IO系统
2. IO控制方式
3. 缓冲管理
4. IO软件
5. 设备分配
6. 磁盘存储器管理
## 6 文件管理
1. 文件和文件系统
2. 文件的逻辑结构
3. 外存分配方式
4. 目录管理
5. 文件存储空间管理
6. 文件共享与文件保护
7. 数据一致性控制

View File

@@ -0,0 +1,219 @@
# 计算机组成
> 目录
> 1. 操作系统的目标和作用
> 2. 操作系统的发展过程
> 3. 操作系统的基本特性
> 4. 操作系统的主要功能
> 5. OS结构设计
## 1 操作系统的目标和作用
### 定义
* 操作系统Operating SystemOS是计算机系统最基础的系统软件管理软硬件资源、控制程序执行改善人机界面合理组织计算机工作流程为用户使用计算机提供良好运行环境。
### 目标
### 作用
### 分类
1. 操作控制方式
1. 多道批处理操作系统,脱机控制方式
2. 分时操作系统,交互式控制方式
3. 实时操作系统
2. 应用领域
1. 服务器操作系统、并行操作系统
2. 网络操作系统、分布式操作系统
3. 个人机操作系统、手机操作系统
4. 嵌入式操作系统、传感器操作系统
### 资源分类
1. 硬件资源
1. 处理器资源:哪个程序占有处理器运行
2. 内存资源:程序/数据在内存中如何分布
3. 设备管理:如何分配、去配和使用设备
2. 信息资源
1. 数据、程序
2. 信息资源管理:如何访问文件信息
3. 信号量资源:如何管理进程之间的通信
### 资源使用
3. 资源使用原则:屏蔽资源使用的底层细节
1. 驱动程序:最底层的、直接控制和监视各类硬件资源。职责是隐藏底层硬件的具体细节,并向其他部分提供一个抽象的、通用的接口
4. 资源共享方式
1. 独占使用方式
2. 并发使用方式
5. 资源分配策略
1. 静态分配方式
2. 动态分配方式
3. 资源抢占方式
## 2 基本特征
### 2.1 并发
* 并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。
* 并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。
* 操作系统通过引入进程和线程,使得程序能够并发运行。
### 2.2 共享
* 共享是指系统中的资源可以被多个并发进程共同使用。
* 有两种共享方式:互斥共享和同时共享。
* 互斥共享的资源称为临界资源,例如打印机等,在同一时刻只允许一个进程访问,需要用同步机制来实现互斥访问。
### 2.3 虚拟
* 虚拟技术把一个物理实体转换为多个逻辑实体。
* 主要有两种虚拟技术:时(时间)分复用技术和空(空间)分复用技术。
* 多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。
* 虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
### 2.4 异步
* 异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。
## 3 基本功能
### 进程管理
* 进程控制、进程同步、进程通信、死锁处理、处理机调度等。
### 内存管理
* 内存分配、地址映射、内存保护与共享、虚拟内存等。
### 文件管理
* 文件存储空间的管理、目录管理、文件读写管理和保护等。
### 设备管理
* 完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。
* 主要包括缓冲管理、设备分配、设备处理、虛拟设备等。
## 4 系统调用
### 定义
![](image/2021-03-29-22-38-00.png)
1. 如果一个进程在用户态需要使用内核态的功能,就进行系统调用从而陷入内核,由操作系统代为完成。
1. 操作系统实现的完成某种特定功能的过程
2. 为所有运行程序提供访问操作系统的接口
2. 系统调用实现
1. 编写系统调用处理程序
2. 设计一张系统调用入口地址表,每个入口地址指向一个系统调用的处理程序,并包含系统调用自带参数的个数
3. 陷入处理机制需开辟现场保护区,以保存发生系统调用时的处理器现场
3. Linux 的系统调用主要有以下这些:
| Task | Commands |
| :---: | --- |
| 进程控制 | fork(); exit(); wait(); |
| 进程通信 | pipe(); shmget(); mmap(); |
| 文件操作 | open(); read(); write(); |
| 设备操作 | ioctl(); read(); write(); |
| 信息维护 | getpid(); alarm(); sleep(); |
| 安全 | chmod(); umask(); chown(); |
## 5 宏内核和微内核
### 宏内核
* 宏内核是将操作系统功能作为一个紧密结合的整体放到内核。由于各模块共享信息,因此有很高的性能。
### 微内核
* 由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。
* 在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。
* 因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。
![](image/2021-03-29-22-38-38.png)
## 6 中断分类
### 外中断
* 由 CPU 执行指令以外的事件引起,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
### 内中断
1. 异常中断。由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。
2. 陷入中断。在用户程序中使用**系统调用**。
## 7 硬件资源
### 中央处理器
1. 中央处理器CPU是计算机的运算核心Core和控制单元Control Unit主要包括
1. 运算逻辑部件:一个或多个运算器
2. 寄存器部件包括通用寄存器、控制与状态寄存器以及高速缓冲存储器Cache
3. 控制部件:实现各部件间联系的数据、控制及状态的内部总线;负责对指令译码、发出为完成每条指令所要执行操作的控制信号、实现数据传输等功能的部件
2. 处理器与寄存器
1. 运算单元
2. 控制单元
3. 内部总线
4. PC/IR/Flag程序计数器/指令暂存器/标志寄存器)
5. MAR/MDR内存地址寄存器MAR/内存数据寄存器MDR
6. 寄存器
7. Cache
8. IOAR/IODR
### 主存储器
1. 存储器的组织层次(由上到下,容量更大、速度更慢、价格更低,单位都是字节)
1. L0寄存器
2. L1L1 CacheSRAM静态随机存储器
3. L2L2 CacheSRAM
4. L3L3 CacheSRAM
5. L4主存DRAM动态随机存储器
6. L5SSD本地固态硬盘
7. L6本地外存储器本地硬盘
8. L7远程外存储器分布式文件系统、Web服务器
### 外围设备
1. 类型
1. 输入设备
2. 输出设备
3. 存储设备
4. 网络通信设备
2. 控制方式
1. 轮询方式CPU忙式控制+数据交换
2. 中断方式CPU启动/中断+数据交换
3. DMA方式CPU启动/中断DMA数据交换
### 总线
1. 定义及其组成
1. 总线Bus是计算机各种功能部件之间传送信息的公共通信干线它是CPU、内存、输入输出设备传递信息的公用通道
2. 计算机的各个部件通过总线相连接,外围设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统
3. 按照所传输的信息种类,总线包括一组控制线,一组数据线和一组地址线
2. 类型
1. 内部总线用于CPU芯片内部连接各元件
2. 系统总线用于连接CPU、存储器和各种I/O模块等主要部件
3. 通信总线:用于计算机系统之间的通信
## 8 软件资源
1. 系统软件:操作系统、实用程序、语言处理程序、数据库管理系统
1. 操作系统实施对各种软硬件资源的管理控制
2. 实用程序为方便用户所设,如文本编辑等
3. 语言处理程序把汇编语言/高级语言编写的程序,翻译成可执行的机器语言程序
2. 支撑软件的有接口软件、工具软件、环境数据库
3. 支持用户使用计算机的环境,提供开发工具也可以认为是系统软件的一部分
4. 应用软件是用户按其需要自行编写的专用程序

130
操作系统/2 中断.md Normal file
View File

@@ -0,0 +1,130 @@
# 中断
## 1 定义
### 广义和狭义
1. 广义程序执行过程中遇到急需处理的事件时暂时中止CPU上现行程序的运行转去执行相应的事件处理程序待处理完成后再返回原程序被中断处或调度其他程序执行的过程。
2. 操作系统是“中断驱动”的,中断是激活操作系统的唯一方式。
3. 狭义处理器之外的中断事件与当前运行指令无关的中断事件。I/O中断、时钟中断、外部信号中断。
### 异常
1. 当前运行指令引起的中断事件。地址异常、算术异常、处理器硬件故障。
2. 系统异常执行陷入指令而触发系统调用引起的中断事件。请求设备、请求I/O、创建进程。
## 2 由处理器、内存储器、总线等硬件故障引起
### 处理原则:
1. 保护现场
2. 停止设备
3. 停止CPU
4. 向操作员报告
5. 等待人工干预
### 由处理器执行机器指令引起。
1. 除数为0操作数溢出等算术异常简单处理报告用户由用户编写中断续元程序处理
2. 非法程序、用户态使用特权指令、地址越界、非法存取等指令异常:终止程序
3. 终止进程指令:终止进程
4. 虚拟地址异常:调整内存后重新执行指令
### 处理器执行陷入指令请求OS服务引起的在操作系统中它一般又被称作系统调用
1. 请求分配外设、请求I/O等等
2. 处理流程陷入OS保护现场根据功能号查入口地址跳转具体处理程序
### 来源于外围设备报告I/O状态的中断
1. I/O完成调整进程状态释放等待进程
2. I/O出错等待人工干预
3. I/O异常等待人工干预
### 由外围设备发出的信号引起的中断事件
1. 时钟中断、间断时钟中断:记时与时间片处理
2. 设备报到与结束中断:调整设备表
3. 键盘/鼠标信号中断:根据信号作出相应反应
4. 关机/重启动中断写回文件停止设备与CPU
## 3 过程
### 发现中断源,提出中断请求
1. 发现中断寄存器中记录的中断
2. 决定这些中断是否应该屏蔽
3. 当有多个要响应的中断源时,根据规定的优先级选择一个
### 中断当前程序的执行
1. 保存当前程序的PSW/PC到核心栈
### 转向操作系统的中断处理程序
## 4 中断的处理
### 中断处理程序
1. 操作系统处理中断事件的控制程序,主要任务是处理中断事件和恢复正常操作
### 中断处理过程
1. 保护未被硬件保护的处理器状态
2. 通过分被中断进程的PSW中断码字段识别中断源
3. 分别处理发生的中断事件
4. 恢复正常操作
1. 对于某些中断,在处理完毕后,直接返回刚刚被中断的进程
2. 对于其他一些中断,需要中断当前进程的运行,调整进程队列,启动进程调度,选择下一个执行的进程并恢复其执行
### 系统处理流程
1. 运行进程
2. 在硬件中发现中断源置中断码以备分析交换新旧PSW
3. 在操作系统中,保护未被硬件保护的现场信息,分析中断源,分别处理各类中断事件
1. 调整进程队列,进程调度,恢复处理器现场信息
2. 恢复处理器现场信息
4. 在硬件中恢复PSW
5. 恢复进程
## 5 中断屏蔽
1. 当计算机检测到中断时,中断装置通过中断屏蔽位决定是否响应已发送的中断
2. 有选择的响应中断
## 6 中断优先级
1. 当计算机同时检测到多个中断时,中断装置响应中断的顺序
2. 有优先度的响应中断
3. 一种可能的处理次序
1. 处理机硬件故障中断事件、自愿性中断事件、程序性中断事件、时钟中断等外部中断事件、输入输出中断事件、重启动和关机中断事件
4. 不同类型的操作系统有不同的中断优先级
## 7 中断的嵌套处理
1. 当计算机响应中断后,在中断处理过程中,可以再响应其他中断
2. 操作系统是性能攸关程序系统,且中断响应处理有硬件要求,考虑系统效率和实现代价问题,中断的嵌套处理应限制在一定层数内,如三层
3. 中断的嵌套处理改变中断处理次序,先响应的有可能后处理
## 8 多中断的响应与处理
### 决定中断处理次序的因素
1. 中断屏蔽可以使中断装置不响应某些中断
2. 中断优先级决定了中断装置响应中断的次序
3. 中断可以嵌套处理,但嵌套的层数应有限制
4. 中断的嵌套处理改变了中断处理的次序
### 案例一
1. X、Y两个中断同时发生
2. 先响应X
3. 因Y被屏蔽继续处理X
4. 再响应并处理Y
### 案例二
1. X、Y两个中断同时发生
2. 根据中断优先级先响应X
3. 因未屏蔽Y再响应并处理Y
4. Y处理完成后再处理X

View File

@@ -0,0 +1,836 @@
# 进程管理
## 2 进程管理
1. 进程的基本概念
2. 进程控制
3. 进程同步
4. 进程通信
5. 线程
6. 管程
## 1 进程与线程
### 进程
* 进程是资源分配的基本单位。进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。
* 下图显示了 4 个程序创建了 4 个进程,这 4 个进程可以并发地执行。
![](image/2021-03-29-22-52-14.png)
### 线程
* 线程是独立调度的基本单位。一个进程中可以有多个线程,它们共享进程资源。
* QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。
![](image/2021-03-29-22-53-11.png)
### 区别
1. 拥有资源。进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
2. 调度。线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
3. 系统开销。由于创建或撤销进程时系统都要为之分配或回收资源如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
4. 通信方面。线程间可以通过直接读写同一进程中的数据进行通信但是进程通信需要借助IPC。
## 2 进程状态的切换
![](image/2021-03-29-22-54-26.png)
- 就绪状态ready等待被调度
- 运行状态running
- 阻塞状态waiting等待资源
### 应该注意以下内容
- 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
## 3 进程调度算法
* 不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。
### 批处理系统
* 批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。
1. 先来先服务 first-come first-serverdFCFS
* 非抢占式的调度算法,按照请求的顺序进行调度。有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
1. 短作业优先 shortest job firstSJF
* 非抢占式的调度算法,按估计运行时间最短的顺序进行调度。长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
3. 最短剩余时间优先 shortest remaining time nextSRTN
* 最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
### 交互式系统
* 交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。
1. 时间片轮转
* 将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
* 时间片轮转算法的效率和时间片的大小有很大关系:
- 因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
- 而如果时间片过长,那么实时性就不能得到保证。
![](image/2021-03-29-22-56-33.png)
2. 优先级调度
* 为每个进程分配一个优先级,按优先级进行调度。为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
3. 多级反馈队列
* 一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
* 每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
* 可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。
![](image/2021-03-29-22-57-07.png)
### 实时系统
* 实时系统要求一个请求在一个确定时间内得到响应。分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。
## 4 进程同步
### 临界区
* 对临界资源进行访问的那段代码称为临界区。为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。
```html
// entry section
// critical section;
// exit section
```
### 同步与互斥
- 同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系。
- 互斥:多个进程在同一时刻只有一个进程能进入临界区。
### 3. 信号量
* 信号量Semaphore是一个整型变量可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
- **down** : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0进程睡眠等待信号量大于 0
- **up** :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。
down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。
如果信号量的取值只能为 0 或者 1那么就成为了 **互斥量Mutex** 0 表示临界区已经加锁1 表示临界区解锁。
```c
typedef int semaphore;
semaphore mutex = 1;
void P1() {
down(&mutex);
// 临界区
up(&mutex);
}
void P2() {
down(&mutex);
// 临界区
up(&mutex);
}
```
\<font size=3\> **使用信号量实现生产者-消费者问题** \</font\> \</br\>
问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。
因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。
为了同步生产者和消费者的行为需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计这里需要使用两个信号量empty 记录空缓冲区的数量full 记录满缓冲区的数量。其中empty 信号量是在生产者进程中使用,当 empty 不为 0 时生产者才可以放入物品full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。
注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0此时生产者睡眠。消费者不能进入临界区因为生产者对缓冲区加锁了消费者就无法执行 up(empty) 操作empty 永远都为 0导致生产者永远等待下不会释放锁消费者因此也会永远等待下去。
```c
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer() {
while(TRUE) {
int item = produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&mutex);
up(&full);
}
}
void consumer() {
while(TRUE) {
down(&full);
down(&mutex);
int item = remove_item();
consume_item(item);
up(&mutex);
up(&empty);
}
}
```
### 4. 管程
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。
```pascal
monitor ProducerConsumer
integer i;
condition c;
procedure insert();
begin
// ...
end;
procedure remove();
begin
// ...
end;
end monitor;
```
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了 **条件变量** 以及相关的操作:**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。
<font size=3> **使用管程实现生产者-消费者问题** </font><br>
```pascal
// 管程
monitor ProducerConsumer
condition full, empty;
integer count := 0;
condition c;
procedure insert(item: integer);
begin
if count = N then wait(full);
insert_item(item);
count := count + 1;
if count = 1 then signal(empty);
end;
function remove: integer;
begin
if count = 0 then wait(empty);
remove = remove_item;
count := count - 1;
if count = N -1 then signal(full);
end;
end monitor;
// 生产者客户端
procedure producer
begin
while true do
begin
item = produce_item;
ProducerConsumer.insert(item);
end
end;
// 消费者客户端
procedure consumer
begin
while true do
begin
item = ProducerConsumer.remove;
consume_item(item);
end
end;
```
## 经典同步问题
生产者和消费者问题前面已经讨论过了。
### 1. 哲学家进餐问题
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br>
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。
下面是一种错误的解法,如果所有哲学家同时拿起左手边的筷子,那么所有哲学家都在等待其它哲学家吃完并释放自己手中的筷子,导致死锁。
```c
#define N 5
void philosopher(int i) {
while(TRUE) {
think();
take(i); // 拿起左边的筷子
take((i+1)%N); // 拿起右边的筷子
eat();
put(i);
put((i+1)%N);
}
}
```
为了防止死锁的发生,可以设置两个条件:
- 必须同时拿起左右两根筷子;
- 只有在两个邻居都没有进餐的情况下才允许进餐。
```c
#define N 5
#define LEFT (i + N - 1) % N // 左邻居
#define RIGHT (i + 1) % N // 右邻居
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N]; // 跟踪每个哲学家的状态
semaphore mutex = 1; // 临界区的互斥,临界区是 state 数组,对其修改需要互斥
semaphore s[N]; // 每个哲学家一个信号量
void philosopher(int i) {
while(TRUE) {
think(i);
take_two(i);
eat(i);
put_two(i);
}
}
void take_two(int i) {
down(&mutex);
state[i] = HUNGRY;
check(i);
up(&mutex);
down(&s[i]); // 只有收到通知之后才可以开始吃,否则会一直等下去
}
void put_two(i) {
down(&mutex);
state[i] = THINKING;
check(LEFT); // 尝试通知左右邻居,自己吃完了,你们可以开始吃了
check(RIGHT);
up(&mutex);
}
void eat(int i) {
down(&mutex);
state[i] = EATING;
up(&mutex);
}
// 检查两个邻居是否都没有用餐,如果是的话,就 up(&s[i]),使得 down(&s[i]) 能够得到通知并继续执行
void check(i) {
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
state[i] = EATING;
up(&s[i]);
}
}
```
### 2. 读者-写者问题
允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。
一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。
```c
typedef int semaphore;
semaphore count_mutex = 1;
semaphore data_mutex = 1;
int count = 0;
void reader() {
while(TRUE) {
down(&count_mutex);
count++;
if(count == 1) down(&data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问
up(&count_mutex);
read();
down(&count_mutex);
count--;
if(count == 0) up(&data_mutex);
up(&count_mutex);
}
}
void writer() {
while(TRUE) {
down(&data_mutex);
write();
up(&data_mutex);
}
}
```
以下内容由 [@Bandi Yugandhar](https://github.com/yugandharbandi) 提供。
The first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).
```c
int readcount, writecount; //(initial value = 0)
semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)
//READER
void reader() {
<ENTRY Section>
down(&readLock); // reader is trying to enter
down(&rmutex); // lock to increase readcount
readcount++;
if (readcount == 1)
down(&resource); //if you are the first reader then lock the resource
up(&rmutex); //release for other readers
up(&readLock); //Done with trying to access the resource
<CRITICAL Section>
//reading is performed
<EXIT Section>
down(&rmutex); //reserve exit section - avoids race condition with readers
readcount--; //indicate you're leaving
if (readcount == 0) //checks if you are last reader leaving
up(&resource); //if last, you must release the locked resource
up(&rmutex); //release exit section for other readers
}
//WRITER
void writer() {
<ENTRY Section>
down(&wmutex); //reserve entry section for writers - avoids race conditions
writecount++; //report yourself as a writer entering
if (writecount == 1) //checks if you're first writer
down(&readLock); //if you're first, then you must lock the readers out. Prevent them from trying to enter CS
up(&wmutex); //release entry section
<CRITICAL Section>
down(&resource); //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource
//writing is performed
up(&resource); //release file
<EXIT Section>
down(&wmutex); //reserve exit section
writecount--; //indicate you're leaving
if (writecount == 0) //checks if you're the last writer
up(&readLock); //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading
up(&wmutex); //release exit section
}
```
We can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesnt need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.
From the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.
```source-c
int readCount; // init to 0; number of readers currently accessing resource
// all semaphores initialised to 1
Semaphore resourceAccess; // controls access (read/write) to the resource
Semaphore readCountAccess; // for syncing changes to shared variable readCount
Semaphore serviceQueue; // FAIRNESS: preserves ordering of requests (signaling must be FIFO)
void writer()
{
down(&serviceQueue); // wait in line to be servicexs
// <ENTER>
down(&resourceAccess); // request exclusive access to resource
// </ENTER>
up(&serviceQueue); // let next in line be serviced
// <WRITE>
writeResource(); // writing is performed
// </WRITE>
// <EXIT>
up(&resourceAccess); // release resource access for next reader/writer
// </EXIT>
}
void reader()
{
down(&serviceQueue); // wait in line to be serviced
down(&readCountAccess); // request exclusive access to readCount
// <ENTER>
if (readCount == 0) // if there are no readers already reading:
down(&resourceAccess); // request resource access for readers (writers blocked)
readCount++; // update count of active readers
// </ENTER>
up(&serviceQueue); // let next in line be serviced
up(&readCountAccess); // release access to readCount
// <READ>
readResource(); // reading is performed
// </READ>
down(&readCountAccess); // request exclusive access to readCount
// <EXIT>
readCount--; // update count of active readers
if (readCount == 0) // if there are no readers left:
up(&resourceAccess); // release resource access for all
// </EXIT>
up(&readCountAccess); // release access to readCount
}
```
## 进程通信
进程同步与进程通信很容易混淆,它们的区别在于:
- 进程同步:控制多个进程按一定顺序执行;
- 进程通信:进程间传输信息。
进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。
### 1. 管道
管道是通过调用 pipe 函数创建的fd[0] 用于读fd[1] 用于写。
```c
#include <unistd.h>
int pipe(int fd[2]);
```
它具有以下限制:
- 只支持半双工通信(单向交替传输);
- 只能在父子进程或者兄弟进程中使用。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/53cd9ade-b0a6-4399-b4de-7f1fbd06cdfb.png"/> </div><br>
### 2. FIFO
也称为命名管道,去除了管道只能在父子进程中使用的限制。
```c
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
```
FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2ac50b81-d92a-4401-b9ec-f2113ecc3076.png"/> </div><br>
### 3. 消息队列
相比于 FIFO消息队列具有以下优点
- 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
### 4. 信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问。
### 5. 共享存储
允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。
需要使用信号量用来同步对共享存储的访问。
多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段。
### 6. 套接字
与其它通信机制不同的是,它可用于不同机器间的进程通信。
## 1 定义
1. 操作系统必须全方位地管理计算机系统中运行的程序。因此,操作系统为正在运行程序建立一个管理实体——进程
2. 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动
3. 进程是操作系统进行资源分配和调度的一个独立单位
### 一个进程包括五个部分
1. OS管理运行程序的数据结构P
2. 运行程序的内存代码C
3. 运行程序的内存数据D
4. 运行程序的通用寄存器信息R
5. OS控制程序执行的程序状态字信息PSW
### 概念级的进程状态
1. 运行态:进程占有处理器运行
2. 就绪态:进程具备运行条件等待处理器运行
3. 等待态:进程由于等待资源、输入输出、信号等而不具备运行条件
### 三态模型
1. 运行态→等待态等待资源、I/O、信号
2. 等待态→就绪态资源满足、I/O结束、信号完成
3. 就绪态→运行态;处理器空闲时选择更高优先权进程抢占
4. 运行态→就绪态:运行时间片刻、有更高优先权进程
### 进程挂起
1. OS无法预期进程的数目与资源需求计算机系统在运行过程中可能出现资源不足的情况
2. 运行资源不足表现为性能低和死锁两种情况
3. 解决办法进程挂起剥夺某些进程的内存及其他资源调入OS管理的对换区不参加进程调度待适当时候再调入内存、恢复资源、参与运行
4. 挂起态与等待态有着本质区别,后者占有已申请到的资源处于等待,前者没有任何资源
### 进程挂起的选择与恢复
1. 选择等待态、就绪态进程进入挂起等待态
2. 运行态进程可以挂起自己
3. 等待事件结束后,挂起就绪态
4. 一般选择挂起就绪态进程予以恢复
## 2 进程控制块Process Control Block
### 作用
* PCB是OS用于记录和刻画进程状态及环境信息的数据结构
* 借助PCBOS可以全面管理进程物理实体刻画进程的执行现状控制进程的执行
### 划分
* 标识信息
1. 进程标识
2. 用于存放唯一标识该进程的信息
1. 系统分配的标识号
2. 系统分配的进程组标识号
3. 用户定义的进程名
4. 用户定义的进程组名
* 现场信息
1. 用户可见寄存器内容
2. 控制/状态寄存器内容
3. 用户/核心栈指针
4. 用于存放该进程运行时的处理器现场信息
1. 用户可见寄存器内容:数据寄存器、地址寄存器
2. 控制与状态寄存器内容PC、IR、PSW
3. 栈指针内容:核心栈与用户栈指针
* 控制信息:用于存放与管理、调度进程相关的信息
1. 调度相关信息:状态、等待事件/原因、优先级
2. 进程组成信息:代码/数据地址、外存映像地址
3. 队列指引元:进程队列指针、父子兄弟进程指针
4. 通信相关信息:消息队列、信号量、锁
5. 进程特权信息:内存访问权限、处理器特权
6. 处理器使用信息:占用的处理器、时间片、处理器使用事件/已执行总时间、记账信息
7. 资源清单信息:正占用的资源、已使用的资源
## 3 模式切换和进程切换
1. 一些中断/异常不会引起进程状态转换,不会引起进程切换,只是在处理完成后把控制权交回给被中断进程
### 处理流程:
1. (中断/异常触发正向模式切换压入PSW/PC
2. 保存被中断进程的现场信息
3. 处理中断/异常
4. 恢复被中断进程的现场信息
5. 中断返回指令触发逆向模式转换弹出PSW/PC
## 3.1 模式切换(处理器状态切换)
1. 进程切换必须在操作系统内核模式下完成,这就需要模式切换
### 切换:
1. 用户模式到内核模式:由中断/异常/系统调用,中断用户进程执行而触发
2. 内核模式到用户模式OS执行中断返回指令将控制权交还用户进程而触发
### 基本工作任务
1. 中断装置完成正向模式切换,包括:
1. 处理器模式转为内核模式
2. 保存当前进程的PC/PSW值到核心栈
3. 转向中断/异常/系统调用处理程序
2. 中断返回指令完成逆向模式转换,包括:
1. 从待运行进程核心栈中弹出PSW/PC值
2. 处理器模式转为用户模式
## 3.2 进程切换
### 定义
* 进程切换指从正在运行的进程中收回处理器,让待运行进程来占有处理器运行
* 进程切换实质上就是被中断运行进程与待运行进程的上下文切换,处理过程:
1. 保存被中断进程的上下文
2. 转向进程调度
3. 恢复待运行进程的上下文
### 工作过程
1. (中断/异常等触发正向模式切换并压入PSW
2. 保存被中断进程的现场信息
3. 处理具体中断/异常
4. 把被中断进程的系统堆栈指针SP值保存到PCB
5. 调整被中断进程的PCB信息如进程状态
6. 把被中断进程的PCB加入相关队列
7. 选择下一个占用CPU运行的进程
8. 修改被选中进程的PCB信息如进程状态
9. 设置被选中进程的地址空间,恢复存储管理信息
10. 恢复被选中进程的SP值到处理器寄存器SP
11. 恢复被选中进程的现场信息进入处理器
12. 中断返回指令触发逆向模式转换并弹出PSW/PC
### 发生时机:进程切换一定发生在中断/异常/系统调用处理过程中
1. 阻塞式系统调用、虚拟地址异常导致被中断进程进入等待态
2. 时间片中断、I/O中断后发现更高优先级进程导致被中断进程转入就绪态
3. 终止用系统调用、不能继续执行的异常导致被中断进程进入终止态
## 4 原语与进程控制原语
1. 进程控制过程中涉及对OS核心数据结构进程表/PCB池/队列/资源表)的修改
2. 为防止与时间有关的错误,应使用原语
3. 原语是由于若干条指令构成的完成某种特定功能的程序,执行上具有不可分割性
4. 原语的执行可以通过关中断实现
5. 进程控制使用的原语称为进程控制原语
6. 另一类常用原语是进程通信原语
## 5 进程的控制与管理
1. 进程创建进程表加一项申请PCB并初始化生成标识建立映像分配资源移入就绪队列
2. 进程撤销从队列中移除归还资源撤销标识回收PCB移除进程表项
3. 进程阻塞保存现场信息修改PCB移入等待队列调度其他进程执行
4. 进程唤醒等待队列中移除修改PCB移入就绪队列该进程优先级高于运行进程触发抢占
5. 进程挂起:修改状态并出入相关队列,收回内存等资源送至对换区
6. 进程激活:分配内存,修改状态并出入相关队列
7. 其他:如修改进程特权
## 6 队列管理模块
1. 队列管理模块是操作系统实现进程管理的核心模块
2. 操作系统建立多个进程队列,包括就绪队列和等待队列
3. 按需组织为先进先出队列与优先队列
4. 队列中的进程可以通过PCB中的队列指引元采用单/双指引元或索引连接
5. 出队与入队操作
6. 进程与资源调度围绕进程队列展开
## 7 关键的进程管理软件
### 系统调用/中断/异常处理程序
### 队列管理模块
### 进程控制程序
### 进程调度程序(独立进程居多)
### 进程通信程序(多个程序包)
### 终端登陆与作业控制程序、性能监控程序、审计程序等外围程序
## 8 进程上下文Process context
1. 进程的执行需要环境支持包括CPU现场和Cache中的执行信息
2. OS中的进程物理实体和支持进程运行的环境合成进程上下文包括以下
1. 用户级上下文:用户程序块/用户数据区/用户栈/用户共享内存
2. 寄存器上下文PSW/栈指针/通用寄存器
3. 系统级上下文PCB/内存区表/核心栈
3. 进程上下文刻画了进程的执行情况
## 9 进程映像Process Image
1. 某一时刻进程的内容及其执行状态集合
1. 进程控制块:保存进程的标识信息、状态信息和控制信息
2. 进程程序块:进程处理的数据空间,包括数据、处理函数的用户栈可修改的程序
3. 核心栈:进程在内核模式下运行时使用的堆栈,中断或系统过程使用
2. 进程映像是内存级的物理实体,又称为进程的内存映像
## 10 线程
### 传统进程是单线程结构进程
#### 问题:
1. 进程切换开销大
2. 进程通信开销大
3. 限制了进程并发的粒度
4. 降低了并行计算的效率
#### 解决思路:
1. 分离两个功能,”独立分配资源“与”被调度分派执行“
2. 进程作为系统资源分配和保护的独立单位,不需要频繁地切换
3. 线程作为系统调度和分派的基本单位,能轻装运行,会被频繁地调度和切换
4. 线程的出现会减少进程并发执行所付出的时空开销,使得并发粒度更细、并发性更好
### 多线程环境下进程的概念
1. 在多线程环境中,进程是操作系统中进行保护和资源分配的独立单位,具有:
1. 用来容纳进程映像的虚拟地址空间
2. 对进程、文件和设备的存取保护机制
### 多线程环境下线程的概念
1. 线程是进程的一条执行路径,是调度的基本单位,同一个进程中的所有线程共享进程获得的主存空间和资源。它具有:
1. 线程执行状态
2. 受保护的线程上下文,当线程不运行时,用于存储线程信息
3. 独立的程序指令计数器
4. 执行堆栈
5. 容纳局部变量的静态存储器
### 多线程环境下线程的状态与调度
1. 线程状态有运行、就绪和睡眠,无挂起
2. 与线程状态变化有关的线程操作有:
1. 孵化、封锁、活化、剥夺、指派、结束
3. OS感知线程环境下
1. 处理器调度对象是线程
2. 进程没有三状态(或者说只有挂起状态)
4. OS不感知线程环境下
1. 处理器调度对象仍是进程
2. 用户空间中的用户调度程序调度线程
### 并发多线程程序设计的优点
1. 快速线程切换
2. 减少(系统)管理开销
3. (线程)通信易于实现
4. 并行程序提高
5. 节省内存空间
### 多线程技术的应用
1. 前台和后台工作
2. C/S应用模式
3. 加快执行速度
4. 设计用户接口
### 内核级线程KLTKernel-Level Threads
1. 线程管理的所有工作由OS内核来做
2. OS提供了一个应用程序设计接口API供开发者使用KLT
3. OS直接调度KLT
4. KLT适用于解决物理并行性问题
5. 特点
1. 进程中的一个线程被阻塞了,内核能调度同一进程的其他线程占用处理器运行
2. 多处理器环境中,内核能同时调度同一进程中多个线程并行执行
3. 内核自身也可用多线程技术实现,能提高操作系统的执行速度和效率
4. 应用程序线程在用户态运行,线程调度和管理在内核实现,在同一进程中,控制权从一个线程传送到另一个线程时需要模式切换,系统开销较大
### 用户级线程ULTUser-Level Threads
1. 用户空间运行的线程库,提供多线程应用程序的开发和运行支撑环境
2. 任何应用程序均需通过线程库进行程序设计,再与线程库连接后运行
3. 线程管理的所有工作都由应用程序完成,内核没有意识到线程的存在
4. ULT适用于解决逻辑并行性问题
5. 特点
1. 所有线程管理数据结构均在进程的用户空间中,线程切换不需要内核模式,能节省模式切换开销和内核的宝贵资源
2. 允许进程按应用特定需要选择调度算法,甚至根据应用需要裁剪调度算法
3. 能运行在任何OS上内核在支持ULT方面不需要做任何工作
4. 不能利用多处理器的优点OS调度进程仅有一个ULT能执行
5. 一个ULT的阻塞将引起整个进程的阻塞
### Jacketing技术
1. 把阻塞式系统调用改造成非阻塞式的
2. 把线程陷入系统调用时执行jacketing程序
3. 由jacketing程序来检查资源使用情况以决定是否执行进程切换或传递控制权给另一个线程
### 多线程实现的混合式策略
1. 线程创建是完全在用户空间做的
2. 单应用的多个ULT可以映射成一些KLT通过调整KLT数目可以达到较好的并行效果
3. 特点
1. 组合ULT/KLTs设施
2. 线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行
3. 一个应用中的多个ULT被映射到一些小于等于ULT数目内核级线程上
4. 程序员可以针对特定应用和机器调节KLT数目以达到整体最佳结果
5. 该方法将会结合纯粹ULT方法和KLT方法的优点同时减少它们的缺点
### 线程混合式策略下的线程状态
1. KLT负责三态系统调用负责
2. ULT负责三态用户调度负责
3. 活跃态ULT代表绑定KLT的三态
4. 活跃态ULT运行时可激活用户调度
5. 非阻塞系统调用可使用Jacketing启动用户调度调整活跃态ULT

View File

@@ -0,0 +1,84 @@
# 处理器
## 处理器调度的层次
### 高级调度(长程调度,作业调度):决定能否加入到执行的进程池中
1. 分时OS中高级调度决定
1. 是否接受一个终端用户的连接
2. 命令能否被系统接纳并构成进程
3. 新建态进程是否加入就绪进程队列
2. 批处理OS中功能是按照某种原则从后备作业队列中选取作业进入主存并为作业做好运行前的准备工作和完成后的善后工作
### 中级调度(平衡负载调度):决定主存中的可用进程集合
1. 引起中级调度是为了提高内存利用率和作业吞吐量
2. 中级调度决定哪些进程被运行驻留在主存中参与竞争处理器及其他资源,起到短期调整系统负荷的作业
3. 中级调度把一些进程换出内存,从而实质进入“挂起”状态,不参与进程调度,以平顺系统的负载
### 低级调度(短程调度,进程调度,处理器调度):决定哪个可用进程占用处理器执行
1. 按照某种原则把处理器分配给就绪态进程或KLT
2. 进程调度程序(分派程序),操作系统中实现处理器调度的程序,是操作系统的最核心部分
3. 处理器调度策略的优劣直接影响到整个系统的性能
4. 功能:
1. 记住进程或内核级线程的状态
2. 决定某个进程或KLT什么时候获得处理器以及占用多长时间
3. 把处理器分配给进程或KLT
4. 收回处理器
## 2 选择处理器调度算法的原则
1. 资源利用率使得CPU或其他资源的使用率尽可能高且能够并行工作
2. 响应时间:使交互式用户的响应时间尽可能小,或尽快处理实时任务
3. 周转时间:提交给系统开始到执行完成获得结果为止的这段时间间隔称为周转时间,应该使周转时间或平均周转时间尽可能短
4. 吞吐量:单位时间处理的进程数尽可能多
5. 公平性确保每个用户每个进程获得合理的CPU份额或其他资源份额
## 3 优先数调度算法
### 根据分配给进程的优先数决定运行进程
1. 抢占式优先数调度算法
2. 非抢占式优先数调度算法
### 优先数的确定准则
1. 进程负担任务的紧迫程度
2. 进程的交互性
3. 进程使用外设的频度
4. 进程进入系统的时间长短
### 与进入系统时间相关的优先数
1. 计算时间短(作业/进程)优先
2. 剩余计算时间短进程优先
3. 响应比高者(作业/进程)优先
1. 响应比=等待时间/进入时间
4. 先来先服务:先进入先被选择
1. 多用于高级调度;低级调度中,以计算为主的进程过于优越
## 4 其他算法
### 时间片轮转调度算法
1. 根据各个进程进入就绪队列的时间先后轮流占用CPU一个时间片
2. 时间片中断
3. 时间片的确定:选择长短合适的时间片,过长则退化为先来先服务算法,过短则调度开销大
4. 单时间片,多时间片和动态时间片
### 分级调度算法(多队列策略,反馈循环队列)
1. 建立多个不同优先级的就绪进程队列
2. 多个就绪进程队列间按照优先数调度
3. 高优先级就绪进程,分配的时间片短
4. 单个就绪进程队列中进程的优先数和时间片相同
5. 分级原则
1. 一般分级原则
1. 外设访问,交互性,时间紧迫程度,系统效率,用户立场,……
2. 现代操作系统的实现模型
1. 多个高优先级的实时进程队列,如:硬实时、网络、软实时
2. 多个分时任务的进程队列,根据基准优先数和执行行为调整
3. 队列数可能多达32-128个
### 彩票调度算法
1. 为进程发放针对系统各种资源如CPU时间的彩票当调度程序需要做出决策时随机选择一张彩票持有该彩票的进程将获得系统资源
2. 合作进程之间的彩票交换

View File

@@ -0,0 +1,390 @@
# 存储管理
## 1 逻辑地址(相对地址):用户编程所使用的地址空间
### 逻辑地址从0开始编号两种形式
1. 一维逻辑地址(地址)
2. 二维逻辑地址(段号:段内地址)
### 段式程序设计
1. 把一个程序设计成多个段
1. 代码段、数据段、堆栈段等等
2. 用户可以自己应用段覆盖技术扩充内存空间使用量
1. 这一技术是程序设计技术不是OS存储管理的功能
## 2 物理地址(绝对地址)
1. 程序执行所使用的地址空间
1. 处理器执行指令时按照物理地址进行
## 3 主存储器的复用
1. 多道程序设计需要复用主存
2. 按照分区复用:
1. 主存划分为多个固定/可变尺寸的分区
2. 一个程序/程序段占用一个分区
3. 按照页架复用:
1. 主存划分成多个固定大小的页架
2. 一个程序/程序段占用多个页架
## 4 存储管理的基本模式
1. 单连续存储管理:一维逻辑地址空间的程序占用一个主存固定分区或可变分区
2. 段式存储管理:段式二维逻辑地址空间的程序占用多个主存可变分区
3. 页式存储管理:一维逻辑地址空间的程序占用多个主页页架区
4. 段页式存储管理:段式二维逻辑地址空间的程序占用多个主存页架区
## 5 地址转换(重定位):把逻辑地址转换成绝对地址
1. 静态重定位:在程序装入内存时进行地址转换
1. 由装入程序执行早期小型OS使用
2. 动态重定位在CPU执行程序时进行地址转换
1. 从效率出发,依赖硬件地址转换机构
## 6 主存储器空间的分配与去配
1. 分配:进程装入主存时,存储管理软件进行具体的主存分配操作,并设置一个表格记录主存空间的分配情况
2. 去配:当某个进程撤离或主动归还主存资源时,存储管理软件要收回它所占用的全部或者部分存储空间,调整主存分配表信息
## 7 主存储器空间的共享
1. 多个进程共享主存储器资源:多道程序设计技术使若干个程序同时进入主存储器,各自占用一定数量的存储空间,共同使用一个主存储器
2. 多个进程共享主存储器的某些区域:若干个协作进程有共同的主存程序块或者主存数据块
## 8 存储保护
1. 为避免主存中的多个进程相互干扰,必须对主存中的程序和数据进行保护
1. 私有主存区中的信息:可读可写
2. 公共区中的共享信息:根据授权
3. 非本进程信息:不可读写
2. 这一功能需要软硬件协同完成
1. CPU检查是否允许访问不允许则产生地址保护异常由OS进行相应处理
## 9 主存储器空间的扩充
1. 存储扩充:把磁盘作为主存扩充,只把部分进程或进程的部分内容装入内存
1. 对换技术:把部分不运行的进程调出
2. 虚拟技术:只调入进程的部分内容
2. 这一工作需要软硬件协作完成
1. 对换进程决定对换,硬件机构调入
2. CPU处理到不在主存的地址发出虚拟地址异常OS将其调入重执指令
## 10 虚拟存储器思想的提出
### 主存容量限制带来诸多不便
1. 用户编写程序必须考虑主存容量限制
2. 多道程序设计的道数受到限制
### 用户编程行为分析
1. 全面考虑各种情况,执行时有互斥性
2. 顺序性和循环性等空间局部性行为
3. 某一阶段执行的时间局部性行为
### 考虑部分调入进程内容
### 基本思想
1. 存储管理把进程全部信息放在辅存中,执行时先将其中一部分装入主存,以后根据执行行为随用随调入
2. 如主存中没有足够的空闲空间,存储管理需要根据执行行为把主存中暂时不用的信息调出到辅存上去
### 实现思路
1. 需要建立与自动管理两个地址空间
1. (辅存)虚拟地址空间:容纳进程装入
2. (主存)实际地址空间:承载进程执行
2. 对于用户,计算机系统具有一个容量大得多的主存空间,即虚拟存储器
3. 虚拟存储器是一种地址空间扩展技术,通常意义上对用户编程是透明的,除非用户需要进行高性能的程序设计
## 11 存储管理涉及的存储对象
1. 存储管理是OS管理主存储器的软件部分
2. 为获得更好的处理性能部分主存程序与数据特别是关键性能数据被调入Cache存储管理需要对其进行管理甚至包括对联想存储器的管理
3. 为获得更大的虚拟地址空间,存储管理需要对存放在硬盘、固态硬盘、甚至网络硬盘上的虚拟存储器文件进行管理
## 12 高速缓存存储器Cache
1. Cache是介于CPU和主存储器间的高速小容量存储器由静态存储芯片SRAM组成容量较小但比主存DRAM技术更加昂贵而快速接近于CPU的速度
2. CPU往往需要重复读取同样的数据库Cache的引入与缓存容量的增大可以大幅提升CPU内部读取数据的命中率从而提高系统性能
3. 构成
1. 高速存储器
2. 联想存储器:根据内容进行寻址的存储器
3. 地址转换部件通过联想存储器建立目录表以实现快速地址转换。命中时直接访问Cache未命中时从内存读取放入Cache
4. 替换部件:在缓存已满时按一定策略进行数据块替换,并修改地址转换部件
4. 分级由于CPU芯片面积和成本Cache很小。根据成本控制划分L1L2L3三级
1. L1 Cache分为数据缓存和指令缓存内置成本最高对CPU的性能影响最大通常在32KB-256KB之间
2. L2 Cache分内置和外置两种后者性能低一些通常在512KB-8MB之间
3. L3 Cache多为外置在游戏和服务器领域有效但对很多应用来说总线改善比设置L3更加有利于提升系统性能
## 13 单连续分区存储管理:每个进程占用一个物理上完全连续的存储空间(区域)
1. 主存区域划分为系统区和用户区
2. 设置一个栅栏寄存器界分两个区域,硬件用它在执行时进行存储保护
3. 一般采用静态重定位进行地址转换
1. 静态重定位:在装入一个作业时,把该作业中程序的指令地址和数据地址全部转换成绝对地址
4. 硬件实现代价低
5. 适用于单用户单任务操作系统如DOS
## 14 可变分区存储管理:按照进程的内存需求来动态划分
### 基本思想
1. 固定分区存储管理不够灵活,既不适应大尺寸程序,又存在内存内零头,有浪费
2. 能否按照进程实际内存需求动态划分分区,并允许分区个数可变
### 创建一个进程时,根据进程所需主存量查看主存中是否有足够的空闲时间
1. 若有,则按需要量分割一个分区
2. 若无,则令该进程等待主存资源
### 由于分区大小按照进程实际需要量来确定,因此分区个数是随机变化的
### 内存分配
1. 最先适应分配算法
2. 邻近适应分配算法
3. 最优适应分配算法
4. 最坏适应分配算法
### 内存零头
1. 固定分区方式会产生内存内零头,可变分区方式也会随着进程的内存分配产生一些小的不可用的内存分区,称为内存外零头
2. 最优适配算法最容易产生外零头
3. 任何适配算法都不能避免产生外零头
### 移动技术
1. 移动分区以解决内存外零头
2. 需要动态重定位支撑
### 主存分区表
#### 已分配区情况表
| 起址 | 长度 | 标志 |
| ---- | ---- | ---- |
| 4K | 6K | J1 |
| 46K | 6K | J2 |
| | | 空 |
| | | 空 |
| ... | ... | ... |
#### 未分配区情况表
| 起址 | 长度 | 标志 |
| ---- | ---- | ------ |
| 10K | 36K | 未分配 |
| 52K | 76K | 未分配 |
| | | 空 |
| | | 空 |
| ... | ... | ... |
## 15 固定分区存储管理
1. 支持多个分区
2. 分区数量固定
3. 分区大小固定
4. 可用静态重定位
5. 硬件实现代价低
6. 早期OS采用
7. 主存分配表
| 分区号 | 起始地址 | 长度 | 占用标志 |
| ------ | -------- | ---- | -------- |
| 1 | 4K | 8K | 0 |
| 2 | 12K | 16K | Job1 |
| 3 | 28K | 16K | 0 |
| 4 | 44K | 24K | 0 |
| 5 | 68K | 24K | Job2 |
| 6 | 92K | 36K | 0 |
## 16 页式存储管理
### 基本原理
1. 分页存储器将主存划分成多个大小相等的页架
2. 受页架尺寸限制,程序的逻辑地址也自然分成页
3. 不同的页可以放在不同页架中,不需要连续
4. 页表用于维系进程的主存完整性
### 地址
1. 页式存储管理的逻辑地址由两部分组成:页号和单元号
2. 物理地址:页架号和单元号
3. 地址转换可以通过查页表完成
### 内存分配/去配
1. 用一张位示图来记录主存分配情况
2. 建立进程页表维护主存逻辑完整性
### 页的共享
1. 页式存储管理能够实现多个进程共享程序和数据
2. 数据共享:不同进程可以使用不同页号共享数据页
3. 程序共享:不同进程必须使用相同页号共享代码页
1. 共享代码页中的JMP<页内地址>)指令,使用不同页号是做不到
### 地址转换代价
1. 页表放在主存:每次地址转换必须访问两次主存
1. 按页号读出页表中的相应页架号
2. 按计算出来的绝对地址进行读写
2. 存在问题:降低了存取速度
3. 解决办法利用Cache存放部分页表
### 快表:存放在高速存储器中的页表部分
1. 为提供地址转换速度,设置一个专用的高速存储器,用来存放页表的一部分
2. 快表表项:页号,页架号
3. 这种高速存储器是联想存储器,即按照内容寻址,而非按照地址访问·
### 引入快表后的地址转换代价
1. 假设主存访问时间为200毫微秒快表访问时间为40毫微秒查快表的命中率是90%平均地址转换代价为200+40\*90%+200+200\*10%=256毫微秒
2. 比两次访问主存时间400毫微秒下降了36%400-256/400
### 基于快表的地址转换流程
1. 按逻辑地址中的页号查快表
2. 若该页已在快表中,则由页架号和单元号形成绝对地址
3. 若该页不在快表中,则再查主存页表形成绝对地址,同时将该页登记到快表中
4. 当快表填满后,又要登记新页时,则需在快表中按一定策略淘汰一个旧登记项
### 多道程序环境下的进程表
1. 进程表中登记了每个进程的页表
2. 进程占有处理器运行时,其页表起始地址和长度送入页表控制寄存器
3. 页表控制寄存器
| 用户作业名 | 页表地址 | 页表长度 |
| ---------- | -------- | -------- |
| AB | 0010 | 4 |
| CD | 0014 | 3 |
| EF | 0017 | 7 |
### 页式虚拟存储管理
#### 定义
1. 把进程全部装入虚拟存储器,执行时先把部分页面装入实际内存,然后,根据执行行为,动态调入不在主存的页,同时进行必要的页面调出
2. 现代OS的主流存储管理技术
3. 首次只把进程第一页信息装入内存,称为请求页式存储管理
页式虚拟存储管理的页表
| 标志位 | 主存块号 | 辅助存储器地址 |
| ------ | -------- | -------------- |
| | | |
扩充页表项,指出:
1. 每页的虚拟地址、实际地址
2. 主存驻留地址、写回标志、保护标志、引用标志、可移动标志
#### 实现
1. CPU处理地址
1. 若页驻留,则获得块号形成绝对地址
2. 若页不在内存则CPU发出缺页中断
2. OS处理缺页中断
1. 若有空闲页架,则根据辅存地址调入页,更新页表与快表等
2. 若无空闲页架,则决定淘汰页,调出已修改页,调入页,更新页表与快表
### 页面调度
1. 当主存空间已满而又需要装入新页时,页式虚拟存储管理必须按照一定的算法把已在主存的一些页调出去
2. 选择淘汰页的工作称为页面调度
3. 选择淘汰页的算法称为页面调度算法
4. 页面调度算法设计不当,会出现(刚被淘汰的页面立即又要调入,并如此反复)
5. 这种现象称为抖动或颠簸
6. 先进先出FIFO页面调度算法
1. 总是淘汰最先调入主存的那一页,或者说主存驻留时间最长的那一页(常驻的除外)
2. 模拟的是程序执行的顺序性,有一定合理性
7. 最近最少用LRU页面调度算法
1. 淘汰最近一段时间较久未被访问的那一页,即那些刚被使用过的页面,可能马上还要被使用到
1. OPT页面调度算法
1. 当要调入新页面时,首先淘汰以后不再访问的页,然后选择距现在最长时间后再访问的页
2. 该算法由Belady提出称为Belady算法又称最佳算法OPTOptimal page replacement
3. OPT只可模拟不可实现
2. 模拟了程序执行的局部属性,既考虑了循环性又兼顾了顺序性
3. 严格实现的代价大(需要维持特殊队列)
4. 模拟实现:
1. 每页建一个引用标志,供硬件使用
2. 设置一个时间间隔中断中断时页引用标志置0
3. 地址转换时页引用标志置1
4. 淘汰页面时从页引用标志为0z的页中间随机选择
5. 时间间隔多长是个难点
8. 最不常用LFU页面调度算法
1. 淘汰最近一段时间内访问次数较少的页面对OPT的模拟性比LRU更好
2. 基于时间间隔中断,并给每一页设置一个计数器
3. 时间间隔中断发生后所有计数器清0
4. 每访问页1次就给计数器加1
5. 选择计数值最小的页面淘汰
9. 时钟CLOCK页面调度算法
1. 采用循环队列机制构造页面队列,形成了一个类似于钟表面的环形表
2. 队列指针则相当于钟表面上的表针,指向可能要淘汰的页面
3. 使用页引用标志位
4. 工作流程:
1. 页面调入主存时其引用标志位置1
2. 访问主存页面时其引用标志位置1
3. 淘汰页面时,从指针当前指向的页面开始扫描循环队列
1. 把所遇到的引用标志位是1的页面的引用标志位清0并跳过
2. 把所遇到的引用标志位是0的页面淘汰指针推进一步
### 缺页中断率
1. 假定进程P共n页系统分配页架数m个
2. P运行中成功访问次数为S不成功访问次数为F总访问次数A=S+F
3. 缺页中断率定义为f=F/A
4. 缺页中断率是衡量存储管理性能和用户编程水平的重要依据
5. 影响因素
1. 分配给进程的页架数:可用页架数越多,则缺页中断率就越低
2. 页面的大小:页面尺寸越大,则缺页中断率就越低
3. 用户的程序编制方法:在大数据量情况下,对缺页中断率也有很大影响
```c
// 程序将数组置为“0”假定仅分得一个主存页架页面尺寸为128个字数组元素按行存放开始时第一页在主存
int A[128][128];
for (int j=0;j<128;j++)
for (int i=0;i<128;i++)
A[i][j]=0;
// 每执行一次赋值就要产生一次缺页中断共产生128x128-1次缺页中断
int A[128][128];
for (int i=0;i<128;i++)
for (int j=0;j<128;j++)
A[i][j]=0;
// 共产生128-1次缺页中断
```
### 反置页表
#### 提出:
1. 页面及相关硬件机制在地址转换、存储保护、虚拟地址访问中发挥了关键作用
2. 为页式存储管理设置专门硬件机构
3. 内存管理单元MMUCPU管理虚拟/物理存储器的控制线路,把虚拟地址映射为物理地址,并提供存储保护,必要时确定淘汰页面
4. 反置页表IPTMMU用的数据结构
#### 基本设计思想
1. 针对内存中的每个页架建立一个页表,按照块号排序
2. 表项包含:正在访问改页框的进程标识、页号及特征位和哈希链指针等
3. 用来完成内存页架到访问进程页号的对应,即物理地址到逻辑地址的转换
#### 页表项
1. 页号:虚拟地址页号
2. 进程标志符:使用该页的进程号(页号和进程标志符结合起来标志一个特定进程的虚拟地址空间的一页)
3. 标志位:有效、引用、修改、保护的锁定等标志信息
4. 链指针:哈希链
#### 基于反置页表的地址转换过程
1. MMU通过哈希表把进程标识和虚页号转换成一个哈希值指向IPT的一个表目
2. MMU遍历哈希链找到所需进程的虚页号该项的索引就是页架号通过拼接位移便可生成物理地址
3. 若遍历整个反置页表中未能找到匹配页表项,说明该页不在内存,产生缺页中断,请求操作系统调入
## 17 段式存储管理
### 段式程序设计
1. 每个程序可由若干段组成每一段都可以从“0”开始编址段内的地址是连续的
2. 分段存储器的逻辑地址由两部分组成,段号、单元号
### 基本思想
1. 段式存储管理基于可变分区存储管理实现,一个进程要占用多个分区
2. 硬件需要增加一组用户可见的段地址寄存器(代码段、数据段、堆栈段、附加段),供地址转换使用
3. 存储管理需要增加设置一个段表,每个段占用一个段表项,包括:段始址、段限长,以及存储保护、可移动、可扩充等标志位
### 段的共享
1. 通过不同进程段表中的项指向同一个段基址来实现
2. 对共享段的信息必须进行保护,如规定只能独处不能写入,不满足保护条件则产生保护中断
### 段式虚拟存储管理的基本思想
1. 把进程的所有分段都存放在辅存中,进程运行时先把当前需要的一段或几段装入主存,在执行过程中访问到不在主存的段时再把它们动态装入
2. 段式虚拟存储管理中段的调进调出是由OS自动实现的对用户透明
3. 与段覆盖技术不同它是用户控制的主存扩充技术OS不感知
### 段表扩充
1. 特征位00不在内存01在内存11共享段
2. 存取权限00可执行01可读11可写
3. 扩充位0固定长1可扩充
4. 标志位00未修改01已修改11不可移动
| 段号 | 特征 | 存取权限 | 扩充位 | 标志位 | 主存始址 |
| ---- | ---- | -------- | ------ | ------ | -------- |
| | | | | | |
### 段页式存储管理
1. 基本思想
1. 段式存储管理可以基于页式存储管理实现
2. 每一段不必占据连续的存储空间,可存放在不连续的主存页架中
3. 能够扩充为段页式虚拟存储管理
4. 装入部分段,或者装入段中部分页面

View File

@@ -0,0 +1,345 @@
# I/O存储管理
## 1 I/O设备外围设备外部设备外设
### 定义
1. 现代计算机系统通常配备大量的I/O设备用于计算机系统与外部世界如用户、其他计算机或电子设备等进行信息交换或存储
2. I/O操作内存和I/O设备之间的信息传送操作
1. 不仅影响计算机的通用性和可扩充性,也是计算机系统综合处理能力及性价比的重要因素
### 分类
1. 按信息传输方向划分
1. 输入设备:将外界信息输入计算机(键盘、鼠标、扫描仪等)
2. 输出设备:将计算结果输出(显示器、打印机等)
3. 输入输出设备:既可以输入信息,也可以输出信息(磁盘驱动器、网卡等)
2. 按交互功能划分
1. 人机交互设别:用于用户与计算机之间的交互通信(鼠标、键盘、显示器等)
2. 存储设备:持久性存储大量信息并快速检索(磁盘驱动器、光盘驱动器等)
3. 机机通信设备:用于计算机和计算机之间的通信(网卡、调制解调器等)
3. 按设备管理划分
1. 字符设备:以字符为单位进行信息交换,发送或接收一个字符流(鼠标、显示器等)
2. 块设备:以固定大小的数据块(块是存储介质上联系信息组成的一个区域)进行信息交换(磁盘驱动器等)
3. 网络设备:用于与远程设备通信的设备(网卡等,可以抽象为传送字符流的特殊字符设备,也可以抽象为传送连续小块数据的块设备)
### 设备管理目标
1. 克服设备和CPU速度的不匹配所引起的问题使主机和设备并行工作提高设备使用效率
2. 对设备进行抽象,屏蔽设备的物理细节和操作过程,配置驱动程序,提供统一界面,供用户或高层软件使用
1. 抽象为文件系统中的节点,统一管理
2. 裸设备不被操作系统直接管理由应用程序读写I/O效率更高
### 设备管理功能
1. 设备中断处理
2. 缓冲区处理
3. 设备的分配和去配
4. 设备驱动调度
5. 实现虚拟设备
### 设备管理层次
1. I/O硬件
1. I/O设备及其接口线路
2. 控制部件
3. 通道
2. I/O软件
1. 系统I/O软件
2. 用户空间I/O软件
## 2 设备控制器设备适配器、I/O控制器、I/O控制接口、I/O模块或I/O接口
### 定义
1. 为达到模块化和通用性的设计目标通常将I/O设备中的机械部件和电子部件分开处理
2. 其中,电子部件称为设备控制器
3. 操作系统与控制器交互,而非与设备交互
### 功能CPU与设备之间的接口
1. 接收和识别CPU或通道发来的命令
2. 实现数据交换
3. 发现和记录设备及自身的状态信息供CPU处理使用
4. 当连接多台设备时,设备地址识别
### 组成部分
1. 状态/控制寄存器
2. 数据缓冲寄存器
3. 地址译码器和I/O控制逻辑
4. 外设接口控制逻辑
5. P.S
1. 主机侧I/O总线接1、2、3
2. 设备侧接口电缆接4多个
3. 数据线连接1和2地址线和控制线接3数据状态控制接4
4. 12与3连接4与3连接
### 轮询方式
1. 流程:
1. 处理器向控制器发送一个I/O命令
2. 如果设备未就绪,则重复测试过程,直至设备就绪
3. 执行数据交换
4. 等待I/O操作完成后才可以继续其他操作
2. 处理I/O请求会终止原程序的执行
3. CPU需要等待I/O设备就绪
4. CPU需要参与数据传送
5. CPU和设备只能串行工作效率低下
### 中断方式
1. 流程:
1. 处理器向控制器发出一个I/O命令然后继续执行后续指令
1. 如果该进程不需要等待I/O完成后续指令可以仍是该进程中的指令
2. 否则,该进程在这个中断上挂起,处理器执行其他工作
2. 控制器检查设备状态,就绪后发起中断
3. CPU响应中断转向中断处理程序
4. 中断处理程序执行数据读写操作
5. 恢复执行原先的程序
2. 响应中断后会终止原程序的执行
3. CPU不需要等待I/O设备就绪
4. CPU需要参与数据传送
5. CPU和设备部分并行操作效率有所提高
### 直接存储器访问DMA
1. DMA模块模仿处理器来控制主存和设备控制器之间的数据交换
2. 流程:
1. 处理器向DMA模块发出I/O命令
2. 处理器继续执行其他工作DMA模块负责传送全部数据
3. 数据传送结束后DMA中断处理器
3. 方式
1. CPU不会终止原程序的执行
2. CPU只在数据传送的开始和结束时参与
1. 开始时CPU需要对DMA模块进行初始化
2. 结束时CPU响应中断但不必保存现场
3. 周期窃取
1. 当DMA和CPU同时经总线访问内存时CPU总是将总线的占有权让给DMA一个或几个主存周期
2. 周期窃取对延迟CPU与主存的数据交换影响不大
1. 数据传送过程是不连续的和不规则的
2. CPU大部分情况下与Cache进行数据交换直接访问内存较少
### I/O通道通道控制器、I/O处理器
1. 设备控制器包含自身专用的处理器和通道程序
1. I/O指令不再由处理器执行而是存在主存中由I/O通道所包含的处理器执行
2. 采用四级连接:处理器,通道,控制器,设备
1. 可控制多台同类或不同类的设备
3. 流程:
1. CPU在遇到I/O请求启动指定通道
2. 一旦启动成功通道开始控制I/O设备进行操作CPU执行其他任务
3. I/O操作完成后I/O通道发出中断CPU停止当前工作转向处理I/O操作结束事件
4. CPU与通道并行工作
5. 带有局部存储器的I/O通道
1. 相当于一台自治的计算机I/O指令存储在控制器自带的局部存储器中并由I/O通道所包含的处理器执行
6. 可以控制大量的I/O设备同时最小化CPU的干涉
7. 常用于交互式终端通信,负责包括控制终端在内的大部分任务
### I/O控制方式的演化
1. 采用轮询方式的设备控制器CPU需要等待设备就绪且参与数据传送
2. 采用中断方式的设备控制器CPU无需等待设备就绪但响应中断后参与数据传送
3. 通过DMA直接控制存储器CPU在数据传送开始和结束时参与与主存进行数据交换时不参与
### I/O发展对总线的影响
#### 单总线
1. 将CPU、主存和I/O模块连接到同一组总线上
2. 优点:结构简单,易于扩充
3. 缺点主存需要和I/O模块共用总线设备增多会造成总线变长进而增加传输时延无法适用于大量高速设备
#### 传统的三级总线
1. 主存和Cache通过主存总线传送数据主存总线和扩展总线上的I/O设备之间传送数据通过扩展总线接口缓冲
2. 优点主存与I/O之间的数据传送与处理器的活动分离可以支持更多的I/O设备
3. 缺点不适用于I/O设备数据速率相差太大的情形
#### 采用南北桥的多级总线
1. 通过存储总线、PCI总线、EISA总线分别连接主存、高速I/O设备和低速I/O设备
2. 优点可以支持不同数据速率的I/O设备
#### 采用I/O通道的多级总线
1. 支持CPU、主存和多个I/O通道之间的数据传送
2. 支持I/O通道和I/O控制器以及I/O控制器以及I/O控制器和设备之间的数据传送
## 3 I/O软件
### 设计目标
1. 高效率改善设备效率尤其是磁盘I/O操作的效率
2. 通用性:用统一的标准来管理所有设备
### 设计思路
1. 把软件组织称层次结构,低层软件用来屏蔽硬件细节,高层软件向用户提供简洁、友善的界面
### 主要考虑的问题
1. 设备无关性:编写访问文件的程序与具体设备无关
2. 出错处理:低层软件能处理的错误不让高层软件感知
3. 同步/异步传输:支持阻塞和中断驱动两种工作方式
4. 缓冲技术:建立数据缓冲区,提高吞吐率
## 4 I/O中断处理程序
1. 位于操作系统底层,与硬件设备密切相关,与系统其余部分尽可能少地发生联系
2. 进程请求I/O操作时通常被挂起直到数据传输结束后并产生I/O中断时操作系统接管CPU后转向中断处理程序
3. 当设备向CPU提出中断请求时CPU响应请求并转入中断处理程序
4. 功能:
1. 检查设备状态寄存器内容判断产生中断的原因根据I/O操作的完成情况进行相应的处理
1. 如果数据传输有错,向上层软件报告设备的出错信息,实施重新执行
2. 如果正常结束,唤醒等待传输的过程,使其转换为就绪态
3. 如果有等待传输的I/O命令通知相关软件启动下一个I/O请求
## 5 设备驱动程序
1. 包括与设备密切相关的所有代码
2. 从独立于设备的软件中接收并执行I/O请求
1. 把用户提交的逻辑I/O请求转化为物理I/O操作的启动和执行
2. 监督设备是否正确执行,管理数据缓冲区,进行必要的纠错处理
3. 功能:
1. 设备初始化
1. 在系统初次启动或设备传输数据时,预置设备和控制器以及通道状态
2. 执行设备驱动例程
1. 负责启动设备,进行数据传输
2. 对于具有通道方式,还负责生成通道指令和通道程序,启动通道工作
3. 调用和执行中断处理程序
1. 负责处理设备和控制器及通道所发出的各种中断
4. 层次:
1. 每个设备驱动程序只处理一种设备,或者一类紧密相关的设备
2. 设备驱动程序分为整体驱动程序和分层驱动程序
1. 整体驱动程序直接向操作系统提供接口和控制硬件
1. 适用于功能简单的驱动程序,效率较高,但较难迁移
2. 分层驱动程序将驱动程序分成多层放在栈中系统接到I/O请求时先调用栈顶的驱动程序栈顶的驱动程序可以直接处理请求或向下调用更底层的驱动程序直至请求被处理
1. 适用于功能复杂,重用性要求较高的驱动程序,结构清晰且便于移植,但会增加一部分系统开销
## 6 独立于设备的I/O软件
1. 执行适用于所有设备的常用I/O功能并向用户层软件提供一致性接口
2. 功能:
1. 设备命名:通过路径名寻址设备
2. 设备保护:检查用户是否有权访问所申请设备
3. 提供与设备无关的数据单位:字符数量,块尺寸
4. 缓冲技术:传输速率,时间约束,不能直接送达目的地
5. 设备分配和状态跟踪:分配到不同类型的设备
6. 错误处理和报告:驱动程序无法处理的错误
## 7 用户空间的I/O软件
### 库函数
1. 一小部分I/O软件不在操作系统中是与应用程序链接在一起的库函数甚至完全由运行于用户态的程序组成
2. 系统调用通常由库函数封装后供用户使用封装函数只是将系统调用所用的参数放在合适位置然后执行访管指令来陷入内核再由内核函数实现真正的IO操作
### SPOOLing软件
1. 在内核外运行的系统I/O软件采用预输入、缓输出和井管理技术通过创建守护进程和特殊目录解决独占型设备的空占问题
## 8 I/O缓冲
### 目的解决CPU与设备之间速度不匹配的矛盾协调逻辑记录大小和物理记录大小不一致的问题提高CPU和设备的并行性减少I/O操作对CPU的中断次数放宽对CPU中断响应时间的要求
### 缓冲区在内存中开辟的存储区专门用于临时存放I/O操作的数据
### 操作:
1. 写操作:将数据送至缓冲区,直到装满,进程继续计算,同时系统将缓冲区的内容写到设备上
2. 读操作:系统将设备上的物理记录读至缓冲区,根据要求将当前所需要的数据从缓冲区中读出并传送给进程
### 单缓冲:操作系统在主存的系统区中开设一个缓冲区
1. 输入:将数据读至缓冲区,系统将缓冲区数据送至用户区,应用程序对数据进行处理,同时系统读入接下来的数据
2. 输出:把数据从用户区复制到缓冲区,系统将数据输出后,应用程序继续请求输出
### 双缓冲:使用两个缓冲区
1. 输入设备先将数据输入缓冲区1系统从缓冲区1把数据传到用户区供应用程序处理同时设备将数据传送到缓冲区2
2. 输出应用程序将数据从用户传送到缓冲区1系统将数据传送到设备同时应用程序将数据传送到缓冲区2
### 循环缓冲:操作系统分配一组缓冲区,每个缓冲区都有指向下一个缓冲区的链接指针,构成循环缓冲
1. 解决设备和进程速度不匹配的问题
2. 为系统公共资源,供进程共享并由系统统一分配和管理
## 9 设备独立性
1. 作业执行前会对设备提出申请时,指定某台具体的物理设备会让设备分配变得简单,但如果所指定设备出现故障,即便计算机系统中有同类设备也不能运行
2. 设备独立性
1. 用户通常不指定物理设备,而是指定逻辑设备,使得用户作业和物理设备分离开来,再通过其他途径建立逻辑设备和物理设备之间的映射
3. 设备管理中需要将逻辑设备名转换为物理设备名,为此系统需要提供逻辑设备名和物理设备名的对应表以供转换使用
4. 微型计算机的操作系统中不一定支持设备独立性
5. 优点:
1. 应用程序与具体物理设备无关,系统增减或变更设备时不需要修改源程序
2. 易于应对I/O设备故障提高系统可靠性
3. 增加设备分配的灵活性,更有效地利用设备资源,实现多道程序设计
## 10 设备分配方式
### 独占设备
1. 只能由一个进程独占式使用
2. 可以让多个进程同时使用的设备称为"共享设备“,其管理工作主要是驱动调度和实施驱动,一般不必分配
### 分配方式
1. 静态分配:实现简单,能够防止系统发生死锁,但会降低设备利用率
2. 动态分配:提高设备利用率
## 11 设备分配的数据结构
### 设备类表
1. 每类设备对应于设备类表的中一栏
2. 包括:设备类,总台数,空闲台数,设备表起始地址等
3. 支持设备独立性时才会使用
### 设备表
1. 每类设备都有各自的设备表,用来登记这类设备中的每台物理设备
2. 包括:物理设备名(号),逻辑设备名(号),占有设备的进程号,是否分配,好/坏标志等
## 12 磁盘结构
### 基本概念
1. 磁盘由多个盘片组成
2. 每个盘片被划分为多个同心圆结构的磁道
1. 不同盘片上位于相同位置的磁道构成的圆柱体称为柱面
3. 每个磁道分为固定多个或不等个数的扇区
1. 为了对大量扇区寻址,操作系统将相邻的扇区组合成簇存储文件
4. 物理块(扇区)地址:(柱面号,磁头号,扇区号)
1. 区别“0面0道”中的“面”是指磁头不是柱面
### 磁盘读写数据
1. 读写数据时,磁头必须定位到指定的磁道上的指定扇区的开始处
2. 过程:
1. 寻道:控制移动臂到达指定柱面,选择磁头号
2. 旋转:等待要读写的扇区旋转到磁头下
3. 数据传送
### 磁盘存取时间:磁盘完成数据读写所需要的时间
1. 寻道时间、旋转延迟、传送时间的总和 $T_a=T_s+\frac{1}{2r}+\frac{b}{rN}$
2. Ta存取时间
3. Ts寻道时间
4. r磁盘旋转速度单位转/秒)
5. B要传送的字节数
6. N一个磁道中的字节数
### 磁盘调度策略
1. 磁盘可能同时接收到若干I/O请求
1. 如果随机选择并响应I/O请求可能得到最坏的性能
2. 驱动调度
1. 系统采用一种调度策略能够按最佳次序执行要求访问磁盘的多个I/O请求
2. 能减少为若干I/O请求服务所需要消耗的总时间
3. 策略
1. 移臂调度
1. 目的:使移动臂的移动时间最短,从而减少寻道总时间
2. 调度策略
1. 先进先出
1. 移动臂是随机移动,寻道性能较差
2. 按顺序处理请求,对所有进程公平
2. 最短查找时间优先
1. 先执行查找时间最短的请求,具有较好的寻道性能
2. 存在"饥饿”现象
3. 扫描算法
1. 单向扫描
1. 移动臂总向一个方向扫面,归途中不提供服务
2. 适用于不断有大量柱面均匀分布的请求的情形
2. 双向扫描
1. 移动臂每次向一个方向移动遇到最近的I/O请求便进行处理到达最后一个柱面后再向相反方向移动
2. 对最近扫描所跨越区域的请求响应较慢
3. 电梯调度
1. 无请求时移动臂停止不动,有请求时按电梯规律移动
2. 每次选择沿移动臂的移动方向最近的柱面
3. 如果当前移动方向上没有但相反方向有请求时,改变移动方向
2. 旋转调度
1. 目的:使得选择延迟的总时间最少
2. 循环排序
1. 通过优化I/O请求排序在最少旋转圈数内完成位于同一柱面的访问请求
2. 旋转位置测定硬件和多磁头同时读写技术有利于提高旋转调度的效率
3. 优化分布
1. 通过信息在存储空间的排列方式来减少旋转延迟
2. 交叉因子
1. 如果沿着磁道按序对扇区编号可能由于磁盘转速太快造成处理当前扇区的数据时下一个扇区已经跳过需要再转一圈才能继续读数据。因此对扇区编号时会间隔编号例如交叉因子为2:1表示相邻编号之间会间隔1个扇区3:1表示会间隔2两个扇区
3. 按柱面而非盘面进行数据读写
1. 连续记录数据时,先记录在同一柱面的不同磁道上,然后再更换柱面,可以减少数据读写时的移臂操作
## 13 虚拟设备
### 使用一类物理设备模拟另一类物理设备的技术。比如:内存卡模拟磁盘、块设备模拟字符设备、输入输出重定向……
### 经典的SPOOLing系统
1. 为存放输入数据和输出数据,系统在磁盘上开辟输入井和输出井
1. 井是用作缓冲的存储区域
2. 组成
1. 预输入程序:将数据从输入设备传送到磁盘输入井
2. 缓输出程序:将数据从磁盘输出井传送到输出设备
3. 井管理程序:控制作业和井之间的数据交换
3. 作用
1. 预输入:操作系统将作业需要的输入数据成批从输入设备上预先输入至磁盘的输入缓冲区中暂存
1. 调度作业执行时,作业使用数据不必再启动输入设备,从磁盘的输入缓冲区读入即可
2. 缓输出:作业不启动输出设备,只是将输出数据暂存到磁盘的输出缓冲区
1. 作业执行完毕后,由操作系统成批输出
4. 不仅设备利用率提高,作业的运行时间也会缩短,每个作业都感觉各自拥有所需的独占设备
### 打印机SPOOLing
1. 打印机空占问题
1. 如果用户进程通过打开打印机的设备文件来申请和使用打印机,往往会造成该进程打开设备文件后长达数小时不用,但其他进程又无法使用打印机
2. 打印机守护进程和SPOOLing打印目录
1. 守护进程是唯一有特权使用打印机设备的进程
2. 打印文件前,用户进程先产生完整的带输出文件,并存放在打印目录下
3. 打印机空闲时,启动守护进程,打印带输出文件

View File

@@ -0,0 +1,347 @@
# 文件系统
## 1 文件
### 定义
* 具有符号名的,在逻辑上具有完整意义的一组相关信息项的序列
* 文件document与计算机文件file
* 文件名是由字母、数字和其他符合组成的一个字符串,其格式和长度因系统而异
### 命名:
1. 文件名和拓展名:前者用于识别文件,后者用于标识文件特性,二者用'.'隔开
2. 不同OS有约定的拓展名Unix不做介绍Windows如下
1. COM可执行的浮动二进制代码文件
2. EXE可执行的浮动二进制代码文件
3. LIB库程序文件
4. BAT批命令文件
5. OBJ编译或汇编生成的目标文件
### 分类:
1. 按用途:系统文件、库文件、用户文件
2. 按保护级别:只读文件、读写文件、不保护文件
3. 按信息时限:临时文件、永久文件、档案文件
4. 按设备类型:磁盘文件、磁带文件、光盘文件、软盘文件
5. 按逻辑结构或物理结构
### 优点:
1. 用户使用方便,按名存取
2. 文件安全可靠,提供保护措施
3. 文件可备份,可组织重执
4. 文件可共享:提供利用率
5. 把数据组织成文件形式加以管理和控制是计算机数据管理的重大进展
## 2 文件系统
1. 操作系统中负责存取和管理信息的模块,它用统一的方式管理用户和系统信息的存储、检索、更新、共享和保护,并为用户提供一整套方便有效的文件使用和操作方法
2. 反映了用户概念中的逻辑结构,而且和存放它的辅助存储器(文件存储器)的存储结构紧密相关。同一个文件必须从逻辑文件和物理文件两个侧面来观察它
### 功能(面向用户):
1. 文件的按名存取
2. 文件的共享和保护
3. 文件的操作和使用
### 实现功能:
1. 文件目录的建立和维护
2. 存储空间的分配和回收
3. 数据的保密和保护
4. 监督用户存取和修改文件的权限
5. 实现在不同存储介质上信息的标识方式、编址方法、存储次序,以及信息检索等问题
### 组成:
#### 文件组织
1. 组织方法
1. 逻辑结构
1. 流式文件
2. 记录式文件
2. 物理结构
1. 顺序文件
2. 连接文件
3. 直接文件
4. 索引文件
#### 文件存取
1. 存取方法
1. 概念
1. 操作系统为用户程序提供的使用文件的技术和手段
2. 在某种程度上依赖于文件的物理结构
2. 方法
1. 顺序存取
1. 按记录顺序进行读/写操作的存取方法
2. 读操作根据读指针读出当前 记录,同时推进读指针,指向下一次要读出的记录
3. 写操作则设置写指针,把一个记录写道文件末端,同时推进写指针
4. 允许对读指针进行前跳或后退n整数个记录的操作
2. 直接存取
1. 很多应用场合要求快速地以任意次序直接读写某个记录。航空订票系统,把特定航班的所有信息存放在物理块中,用户预定某航班时,直接计算出该航班的存位置
3. 索引存取
1. 基于索引文件的索引存取方法
2. 对于这种文件,信息块的地址都可以通过查找记录键而换算出
3. 除可采用按键存取外,也可以采用顺序存取或直接存取的方法
4. 实际的系统中,大都采用多级索引,以加速记录查找过程
#### 文件控制
1. 控制系统
1. 逻辑的控制系统
2. 物理的控制系统
#### 文件使用
1. 文件操作
1. 打开文件
2. 关闭文件
3.
4.
5. 控制
## 3 卷和块
1. 文件存储介质有磁盘、光盘和磁盘
2. 卷:存储介质的物理单位,对应于一盘磁带、一块软盘、一个光盘片、一个硬盘分区
3. 块:存储介质上连续信息所组成的一个区域,也叫做物理记录
4. 块在主存储器和辅助存储器进行信息交换的物理单位,每次总是交换一块或整数块信息
5. 决定块的大小要考虑用户使用方式、数据传输效率和存储设备类型等多种因素
6. 不同类型的存储介质,块的长短常常各不相同;对同一类型的存储介质,块的大小一般相同,但也可以不同
7. 外围设备由于启停机械动作或识别不同块的要求,两个相邻块之间必须留有间隙
1. 间隙是块之间不记录用户代码信息的区域
8. 顺序存取存储设备的信息安排
1. 顺序存取设备是严格依赖信息的物理位置次序进行定位和读写的存储设备
2. 磁带机是最常用的一种顺序存取存储设备,它具有存储容量大、稳定可靠、卷可装卸和便于保存等优点,广泛用作存档
3. 磁带的一个突出特点是块长的变化范围较大,块可以很小,也可以很大,原则上没有限制
4. 光盘也是一种顺序存取存储设备
9. 直接存取存储设备的信息安排
1. 磁盘是一种直接存取存储设备(随机存取存储设备)
2. 移臂与旋转两维组织,存取速度高
3. 它的每个物理记录有确定的位置和唯一的地址,存取任何一个物理块所需的时间几乎不依赖于此信息的位置
## 4 逻辑文件(文件的逻辑结构)
### 定义
* 独立于物理环境的,用户概念种的抽象信息组织方式
* 用户能观察到的,并加以处理的数据集合
### 文件的逻辑结构分为两种形式
* 流式文件
1. 文件内的数据不再组成记录,只是由一串依此的字节组成的信息流序列
2. 这种文件常常按长度来读取所需信息,也可以用插入的特殊字符作为分界
* 记录式文件
1. 一种有结构的文件,它是若干逻辑记录信息所组成的记录流文件
2. 逻辑记录是文件种按信息在逻辑上的独立含义所划分的信息单位
* 记录式文件与数据库
1. 数据库管理系统也支持逻辑记录
2. 区别:数据库种的记录之间可以通过数据冗余构成某种联系
3. 数据库管理系统支持基于联系的数据查询,文件系统则不行
## 5 物理文件(文件的物理结构)
1. 文件的物理结构和组织是指文件在物理存储空间种的存放方法和组织关系
2. 文件的存储结构涉及块的划分、记录的排列、索引的组织、信息的搜索等许多问题
3. 其优劣直接影响文件系统的性能
4. 顺序文件(连续文件)
1. 将一个文件中逻辑上连续的信息存放到存储介质的依此相邻的块中便形成顺序结构
2. 磁带文件、光盘文件是典型例子
3. 优点(顺序存取记录时速度较快):
1. 批处理文件,系统文件用得最多
2. 采用磁盘存放顺序文件时,总可以保持快速存取的优点
4. 缺点:
1. 建立文件前需要能预先确定文件长度,以便分配存储孔空间
2. 修改、插入和增加文件记录有困难
5. 连接文件(串联文件)
1. 特点:使用连接字来表示文件中各个物理块之间的先后次序
2. 每一块文件信息的物理地址由文件目录给出而每一块的连接字指出了文件的下一个物理块位置连接字内容为0时表示文件至本块结束
3. 像输入井、输出井等都用此类文件
4. 优点:
1. 易于对文件记录做增删改,易于动态增长记录
2. 不必预先确知文件长度
3. 存储空间利用率高
5. 缺点:
1. 存放指针需额外的存储空间
2. 由于存取须通过缓冲区,待获得连接字后,才能找到下一物理块的地址,因而,仅适用于顺序存取
6. 直接文件(散列文件)
1. 通过计算记录的关键字建立与其物理存储地址之间的对应关系
2. 这种变换通常采用散列法hash法
3. 计算寻址结构可能出现”冲突“,即不同的关键字可能变换出相同的地址来,解决办法有拉链法、循环探查法、二次散列法、溢出区法等
7. 索引文件
1. 索引文件为每个文件建立了一张索引表,其中,每个表目包含一个记录的键(或逻辑记录号)及其存储地址
2. 索引表的地址可由文件目录指出,查阅索引表找到相应记录键(逻辑记录号),然后获得数据存储地址
3. 访问方式:
1. 在文件存储器上分两个区:索引区和数据区
2. 访问索引文件需两个步骤:查找索引表;获得记录物理地址
3. 需要两次访问辅助存储器,若文件索引已预先调入主存储器,那么,就可以减少一次内外村信息交换
4. 特点:
1. 索引结构可以被认为是连接结构的一种扩展,除了具备连接文件的优点外,还客服了它只能作顺序存取的缺点,具有直接读写任意一个记录的能力,便于文件增删改
2. 索引文件的缺点:增加了索引表的空间开销和查找时间
5. 索引表组织
1. 一级索引
2. 两级索引
3. 多级索引
## 6 文件目录
1. 实现文件的”按名存取“的关键数据结构
2. 文件系统的基本功能之一就是负责文件目录的建立、维护和检索,要求编排的目录便于查找、防止冲突
3. 文件目录需要永久保存,因此也组织成文件存放在磁盘上,称目录文件
4. 一级目录结构
1. 在操作系统中构造一张线性表,与每个文件的相关属性占用一个目录项,构成了一级目录结构
2. 由于用户与文件众多,容易重名,不利记忆
5. 二级目录结构
1. 第一级为主文件目录,它用于管理所有用户文件目录,它的目录项登记了系统接受的用户的名字及该用户文件目录的地址
2. 第二级为用户的文件目录,它为该用户的每个文件保存一个登记栏,其内容与一级目录的目录项相同
3. 每一用户只允许查看自己的文件目录
4. 特点
1. 采用二级目录管理文件时,因为任何文件的存取都通过主文件目录,于是可以检查访问文件者的存取权限,避免一个用户未经授权就存取另一个用户的文件,使用户文件的私密性得到保证,实现了对文件的保密和保护
2. 特别是不同用户具有同名文件时,由于各自有不同的用户文件目录而不会导致混乱
3. 对于同一个用户而言,同样存在文件多、容易重名问题
6. 树形目录结构
1. 每一级目录可以登记下一级目录,也可以登记文件,从而,形成了层次文件目录结构
2. 层次目录结构通常采用树形目录结构,它是一棵倒向的有根树,树根是根目录;从根向下,每一个树分叉是一个子目录;而树叶是文件
3. 特点
1. 较好地反映现实世界中具有层次关系的数据集合和较确切地反映系统内部文件的组织结构
2. 不同文件可以重名,只要它们不位于同一末端的子目录中
3. 易于规定不同层次或子树中文件的不同存取权限,便于文件的保护、保密和共享
4. 文件定位
1. 在树形目录结构中,一个文件的全名包括从根目录开始到文件为止,通路上遇到的所有子目录路径,又称为路径名
2. 各子目录名之间用正斜线/(反斜线\)隔开
3. 一个硬盘分区可以组织成一颗子树
1. 每颗子树可以对应于一个逻辑盘符Win
2. 把众多子树嫁接成一颗大树UNIX
## 7 文件查找
### 查找方法
* 文件查找是文件目录管理的额重要工作,“按名存取”文件就是系统根据用户提供的文件路径名来搜索各级文件目录,找到该文件
1. 从根目录查起(绝对路径名)
2. 从"当前目录"查起(相对路径名),用'.'表示当前目录,'..'表示父目录
3. 现代操作系统都设置有改变工作目录命令,即变更当前工作目录
### 目录项查找
1. 搜索具体目录项时,可以采用顺序查找法,依此扫描文件目录中的目录项,将目录项中的名字与欲查找的文件名相比较
2. 优化方法
1. 目录表项是按键的顺序编排,可以采用“二分查找法”
2. "杂凑法“,把每个文件名经过变换函数变换成唯一的目录表表项
### 文件目录处理
1. 树形目录结构:当一个文件经过许多目录节点时,使用很不方便;系统在沿路径查找目录时,往往要多次访问文件存储器,使访问速度大大减慢
2. 若把所有文件的目录都复制到主存,访问速度是加快了,但又增加了主存的开销
3. 一种有效办法是把常用和正在使用的哪些文件目录复制进主存,这样,既不增加太多的主存开销,又可以明显减少目录查找时间
### 活动文件表
1. 系统可以为每个用户进程建立一张活动文件表,当用户使用一个文件之前,先通过”打开“操作,把该文件有关目录信息复制到指定主存区域,有关信息填入活动文件表,以建立用户进程和该文件索引的联系
2. 当不再使用该文件时,使用”关闭“,切断用户进程和这个文件的联系,同时,若该目录已被修改过,则应更新辅存中对应的文件目录
## 8 文件的安全与保护
### 定义
* 文件是计算机系统的重要资源,因此,要求文件系统具有保障文件安全的手段,提供文件保密的措施,有效地实现文件的共享
### 文件共享:不同用户共同使用某些文件
* 计算机用户完成共同任务所必需的
* 好处:
1. 减少大量重复性劳动
2. 免除系统复制文件的工作
3. 节省文件占用的存储空间
4. 减少程序设计输入输出文件的次数
* 并发控制
1. 在允许文件共享的系统中,操作系统应提供手段实现对共享文件的同步控制
2. 多个进程可能同时存取一个文件,如果它们同时进行读操作,操作系统应对文件进行公用控制
3. 如果有进程进行写操作例如有两个进程进程A要求修改文件同时进程B要求读出同一文件中的数据则操作系统给必须提供同步控制机制以保证文件数据的完整性
### 文件保护:防止文件被破坏
1. 操作系统必须提供文件保护机制,有效实现文件的完整性
2. 方法
1. 文件副本
1. 文件系统必须要有防止硬软件故障,保存信息完整性的能力。主要的实现机制就是文件副本
2. 动态多副本技术
1. 在多个介质上维持同一内容的文件,并且在更新内容时同时进行
2. 需要增加设备费用和系统负载。一般适用于容量较小且较为重要的文件,例如不需更新的系统文件及专用文件,当文件发生故障时只要切换到备用设备就可以
3. 转储、备份与恢复
1. 文件转储:定时把文件复制转储到其他介质上,当某介质上出现故障时,复原转储文件
2. 转储方式
1. 一定时间间隔或一个单位处理结束时,系统自动复写更新过的文件和数据
2. 每天或每周把文件信息全部复写一遍需要时再通过装入转储文件来恢复系统诸如BACKUP、RESTORE等命令
2. 文件存取矩阵与文件读取表
1. 系统为每个用户设置访问每个文件对象的存取属性
2. 系统的全部用户对全部文件的存取属性就组成的一个二维矩阵,称为存取控制矩阵
3. 由于操作系统拥有很多用户和众多文件,存取控制矩阵是一个稀疏矩阵,可以将其简化为一张存取控制表
4. 每行包括:用户、文件、存取属性
5. 存取控制表仅登记那些对文件拥有存取属性的部分
6. 基于存取控制矩阵/表的文件保护
1. 存取属性:可以有访问、读、写、执行、创建、删除、授权等等
2. 系统通过查阅(矩阵/表)核对用户对文件的存取权限
3. 文件属主使用GRANT、REVOKE等命令进行授权甚至把授权权转授给他信任的用户
4. 系统管理用户(超级用户)等同于文件属主权限,并获得对系统文件的授权访问权权限
3. 文件属性
1. 存取控制表的一种简化方法是用户分类,再针对每类用户规定文件属性
2. 用户分类:属主、合作者、其他
3. 文件属性:读、写、执行、...
4. 文件属性可以放在文件目录项中,管理大为简化
5. 用户使用文件时,通过核对文件属性,实现保护
6. 文件属性的例
1. chmod命令可以改变文件属性
2. chown命令用于变更文件属主
3. chgrp命令用户变更用户伙伴
| Name | 读 | 写 | 执行 |
| -------- | ---- | ---- | ---- |
| 文件主 | 1 | 1 | 0 |
| 伙伴 | 1 | 0 | 0 |
| 其他用户 | 1 | 0 | 0 |
### 文件保密:防止文件及其内容被其他用户窃取
1. 措施
1. 隐蔽文件目录
2. 设置口令
3. 使用密码
## 9 文件的使用
### 用户通过两类接口与文件系统联系
1. 与文件有关的操作命令。UNIX中cat、cd、cp、find、mv、rm、mkdir、rmdir等等
2. 提供给用户程序使用的文件类系统调用,基本文件类系统调用:建立、打开、读/写、定位、关闭、撤销
1. 建立文件
1. 用于创建一个文件
2. 所需参数:文件名、设备类(号)、文件属性及存取控制信息
3. 处理流程:在相应设备上建立一个文件目录项,为文件分配第一个物理块,在活动文件表中申请一个项,登记有关目录信息,并返回一个文件句柄
2. 撤销文件
1. 用于删除一个文件
2. 所需参数:文件名、设备类(号)
3. 处理流程:若文件没有关闭,先关闭文件;若为共享文件,进行联访处理;在目录文件中删除相应目录项;释放文件占用的文件存储空间
3. 打开文件
1. 用于建立起文件和用户进程之间的使用联系
2. 所需参数:文件名、设备类(号)、打开方式
3. 处理流程:在主存活动文件表中申请一个项,返回一个文件句柄;根据文件名查找目录文件,把目录信息复制到活动文件表相应栏;若打开的是共享文件,则应有相应处理
4. 关闭文件
1. 用于结束一个文件的读写
2. 所需参数:文件句柄
3. 处理流程将活动文件表中该文件的”当前使用用户数“减1完成”推迟写";若活动文件表目内容已被改过,则应先将表目内容写回文件存储器上相应表目中,以使文件目录保持最新状态
5. 读/写文件
1. 用于读写文件
2. 所需参数:文件句柄、用户数据区地址、读写的记录或字节个数
3. 处理流程:按文件句柄从活动文件表中找到该文件的目录项信息;根据目录项指出的该文件的逻辑和物理组织方式,把相关逻辑记录转换成物理块
6. 定位文件
1. 用于调整所打开文件的读写指针位置
2. 所需参数:文件句柄、定位指针
## 10 辅存空间管理
1. 磁盘等大容量辅存空间被OS及许多用户共享用户进程运行期间常常要建立和删除文件OS应能自动管理和控制辅存空间
2. 随着用户文件不断建立和撤销,文件存储空间会出现许多“碎片”
3. OS解决“碎片”的办法是整理“碎片”在整理过程中往往对文件重新组织让其存放在连续存储区中
4. 分配方式
1. 连续分配:存放在辅存空间连续存储区中(连续的物理块号)
1. 优点:顺序访问时速度快,管理较为简单,但为了获得足够大的连续存储区,需定时进行“碎片”整理
2. 非连续分配:动态分配给若干扇区或簇(几个连续扇区),不要求连续
1. 优点:辅存空间管理效率高,便于文件动态增长和收缩
5. 空闲块的管理:位示图
1. 使用若干字节构成一张表表中每一字位对应一个物理块字位的次序与块的相对次序一致。字位为“1”表示相应块已占用字位为“0”状态表示该块空闲
2. 主要优点:可以把位示图全部或大部分保存在主存中,再配合现代计算机都具有的位操作指令,可实现高速物理块分配和去配
3. 空闲块成组连接法
1. 分配算法
1. IF 空闲块数=1 THEN
1. IF 第1个单元=0 THEN 等待
2. ELSE 复制第1个单元对应块到专用块并分配之
2. ELSE 分配第(空闲块数)个单元对应块,空闲块数-1
2. 归还算法
1. IF 空闲块数<100 THEN
1. 专用块的空闲块数+1空闲块数个单元置归还块号
2. ELSE 复制专用块到归还块专用块的空闲块数置1第1单元置归还块号
## 11 文件系统的实现层次
1. 用户接口:接受用户发来的系统调用,进行语法检查,进入逻辑文件控制子系统
2. 逻辑文件控制子系统:根据文件路径名,搜索文件目录,建立活动文件表,根据文件结构和存取方法,把逻辑记录转换成相对物理块号和块内相对地址
3. 文件保护子系统:识别调用者的身份,验证存取权限,判定本次文件操作的合法性
4. 物理文件控制子系统实现缓冲区管理根据物理结构将相对物理块号转换为实际物理块号负责文件存储空间的分配生成I/O控制系统的调用形式
5. I/O控制子系统执行具体的物理块I/O操作

View File

@@ -0,0 +1,611 @@
# 并发程序设计
## 1 顺序程序设计
1. 程序是实现算法的操作(指令)序列
2. 每个程序在处理器上执行是严格有序的,称为程序执行的内部顺序性
3. 程序设计的一般习惯是顺序程序设计
1. 把一个具体问题的求解过程设计成一个程序或者若干严格顺序执行的程序序列,称为程序执行的外部顺序性
4. 特性:
1. 程序执行的顺序性:程序指令执行是严格按序的
2. 计算环境的封闭性:程序运行时如同独占受操作系统保护的资源
3. 计算结果的确定性:程序执行结果与执行速度和执行时段无关
4. 计算过程的可再见性:程序对相同数据集的执行轨迹是确定的
## 2 进程的并发执行
1. 多道程序设计让多个程序同时进入内存去竞争处理器以获得运行机会
2. OS允许计算机系统在一个时间段内存在多个正在运行的进程即允许多个进程并发执行
3. OS保证按照“顺序程序设计”方法编程的程序在并发执行时不受影响如同独占计算机
4. 这些按照顺序程序设计思想编程的进程在OS中并发执行属于无关的并发进程
## 3 处理器利用率计算
1. 顺序程序设计input78s、process52s、output20s52/(78+52+20)=35%
2. 并发程序设计时间同上while(1){input,send}; while(1){receive,process,send}; while(1){receive,output}(52*n)/(78*n+52+20)=67%n趋近于无穷大
## 4 并发程序设计
### 概念
* 把一个具体问题求解设计成若干个可同时执行的程序模块的方法
### 特性:
1. 并发性:多个进程在多道程序系统中并发执行或者在多处理器系统中并行执行:提高了计算效率
2. 共享性:多个进程共享软件资源
3. 交往性:多个进程并发执行时存在制约:增加了程序设计的难度
### 无关与交往的并发进程
1. 无关:一组并发进程分别在不同变量集合上运行,一个进程的执行与其他并发进程的进展无关
2. 交往:一组并发进程共享某些变量,一个进程的执行可能影响其他并发进程的结果
### 与时间有关的错误
1. 对于一组交往的并发进程,执行的相对速度无法相互控制
2. 如果程序设计不当,可能出现各种“与时间有关的”错误
3. 表现一:结果错误;表现二:永远等待
### 进程互斥与进程同步
1. 交互的并发进程在执行时必须进行制约,才能保证得到合理的结果
2. 进程互斥:并发进程之间因相互争夺独占性资源而产生的竞争制约关系
3. 进程同步:并发进程之间为完成共同任务基于某个条件来协调执行先后关系而产生的协作制约关系
### 互斥与临界区
1. 临界资源:互斥共享变量所代表的资源,即一次只能被一个进程使用的资源
2. 临界区:并发进程中与互斥共享变量相关的程序段
1. 确定临界资源 share <variable>
2. 确定临界区 region <variable> do <statement_list>
3. 两个进程的临界区有相同的临界资源,就是相关的临界区,必须互斥进入
4. 两个临界区不相关,进入就没有限制
5. 临界区管理的三个要求
1. 一次至多允许一个进程停留在相关的临界区内
2. 一个进程不能无限止地停留在临界区内
3. 一个进程不能无限止地等待进入临界区
6. 临界区管理
1. 同时进入临界区
```c
process P1
begin
while inside2 do [ ];
inside1 := true;
临界区;
inside1 := false;
end;
process P2
begin
while inside1 do [ ];
inside2 := true;
临界区;
inside2 := false;
end;
```
2. 同时等待进入临界区
```c
process P1
begin
inside1 := true;
while inside2 do [ ];
临界区;
inside1 := false;
end;
process P2
begin
inside2 := true;
while inside1 do [ ];
临界区;
inside2 := false;
end;
```
7. 解决思路
```c
// 测试锁
TS(x) {
if (x==false) {x=true;return true;
} else return false;
}
Boolean lock;
lock=false; // 临界区可用
process Pi {
Boolean pi;
repeat pi=TS(lock) until pi; // 循环请求锁
临界区
lock=false; // 解锁
}
// 交换锁
swap(a, b){temp=a;a=b;b=temp;}
Boolean lock;
lock=false; // 临界区可用
process Pi {
Boolean pi;
pi=true;
repeat swap(lock, pi) until !pi; // 循环建立锁
临界区;
lock=false; // 解锁
}
```
8. 实现临界区管理的硬件设施
1. TS和swap指令均是忙式等待效率低
2. 简单的解决办法是在进出临界区时开关中断,这样临界区执行就不会中断了,执行就有原子性。关中断;临界区;开中断
3. 操作系统原语就采用这种实现思路
4. 但是,临界区的指令长度应该是短小精悍,这样才能保证系统效率
5. 不建议用户程序使用,滥用是可怕的!
3. 多个并发进程访问临界资源时,存在竞争制约关系。如果两个进程同时停留在相关的临界区内就会出现与时间相关的错误
## 5 PV操作与进程互斥
### 问题
1. TS或swap指令管理临界区采用忙式轮询效率低
2. 关开中断管理临界区,不便交给用户程序使用
### 信号量的构思
1. 一种可动态定义的软件资源:信号量
1. 核心数据结构:等待进程队列
2. 信号量声明:资源报到,建立队列
3. 申请资源的原语:若申请不到,调用进程入队等待
4. 归还资源的原语:若队列中有等待进程,需释放
5. 信号量撤销:资源注销,撤销队列
2. 记录型信号量:一种带数值的软资源
```c
typedef struct semaphore {
int value; // 信号量值
struct pcb *list; // 信号量等待进程队列指针
}
```
1. 每个信号量建立一个等待进程队列
2. 每个信号量相关一个整数值
1. 正值表示资源可复用次数
2. 0值表示无资源且无进程等待
3. 负值表示等待队列中进程个数
3. P、V操作原语
```c
procedure P(semaphore:s) {
s -= 1; // 信号量-1
if (s<0) W(s); // 若信号量小于0则调用进程被置成等待信号量s的状态
}
procedure V(semaphore:s) {
s += 1; // 信号量+1
if (s<=0) R(s); // 若信号量小于等于0则释放一个等待信号量s的进程
}
```
4. PV操作解决进程互斥问题框架
```c
semaphore s;
s=1;
begin
process Pi {
......
P(s);
临界区;
V(s);
......
}
end;
```
5. PV操作解决进程同步问题
1. 进程同步:并发进程为完成共同任务基于某个条件来协调执行先后关系而产生的协作制约关系
2. 一个进程的执行等待来自于其他进程的消息
3. 解决的基本思路:
1. 定义一个信号量:其数值代表可用消息数
2. 等待消息进程执行P无消息则等待
3. 发出消息进程执行V有等待进程则释放
4. 1生产者1消费者1缓冲区问题
1. 生产者和消费者共享缓冲区
2. 缓冲区有空位时,生产者可放入产品,否则等待
3. 缓冲区有产品时,消费者可取出产品,否则等待
```c
Int B;
process producer
begin
L1:
produce a product;
B = product;
goto L1;
end;
process consumer
begin
L2:
product = B;
consume a product;
goto L2;
end;
// 正确执行顺序P→(同步关系1等待产品)C→(同步关系2等待缓冲)P→C→P→C...
// 信号量仅仅解决信号传递数据传送需要共享缓冲区
```
4. 解决思路:
1. 同步关系1消费者一开始在等待产品到来考虑设置一个信号量等待产品一开始无产品初值为0
2. 同步关系2消费者则在等待缓冲区中有空位也可设置一个信号量等待缓冲区一开始缓冲区有空位初值为1
```c
// PV解决1生产者1消费者1缓冲区问题
Int B; // 共享缓冲区
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = 1; // 缓冲区内允许放入一件产品
sget = 0; // 缓冲区内没有产品
process producer {
L1:
produce a product;
P(sput);
B = product;
V(sget);
goto L1;
}
process consumer {
L2:
P(sget);
product = B;
V(sput);
consume a product;
goto L2;
}
// PV解决1生产者1消费者N缓冲区问题
Int B[k]; // 共享缓冲区队列
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = k; // 缓冲区内允许放入k件产品
sget = 0; // 缓冲区内没有产品
Int putptr, getptr; // 循环队列指针
putptr = 0; getptr = 0;
process producer_i {
L1:
produce a product;
P(sput);
B[putptr] = product;
putptr = (putptr+1) mod k;
V(sget);
goto L1;
}
process consumer_j {
L2:
P(sget);
product = B[getptr];
getptr = (getptr+1) mod k;
V(sput);
consume a product;
goto L2;
}
// PV解决N生产者N消费者N缓冲区问题
Int B[k]; // 共享缓冲区队列
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = k; // 缓冲区内允许放入k件产品
sget = 0; // 缓冲区内没有产品
Int putptr, getptr; // 循环队列指针
putptr = 0; getptr = 0;
s1, s2:semaphore; // 互斥使用putptrgetptr
s1 = 1;s2 = 1;
process producer_i {
L1:
produce a product;
P(sput);
P(s1);
B[putptr] = product;
putptr = (putptr+1) mod k;
V(s1);
V(sget);
goto L1;
}
process consumer_j {
L2:
P(sget);
P(s2);
product = B[getptr];
getptr = (getptr+1) mod k;
V(s2);
V(sput);
consume a product;
goto L2;
}
```
```c
// 苹果橘子问题
Int plate;
Semaphore sp; // 盘子里可以放几个水果
Semaphore sg1; // 盘子里有桔子
Semaphore sg2; // 盘子里有苹果
sp = 1; // 盘子里允许放入一个水果
sg1 = 0; // 盘子里没有桔子
sg2 = 0; // 盘子里没有苹果
process father {
L1: 削一个苹果;
P(sp);
把苹果放入plate;
V(sg2);
goto L1;
}
process mother {
L2: 剥一个桔子;
P(sp);
把桔子放入plate;
V(sg1);
goto L2;
}
process son {
L3: P(sg1);
从plate中取桔子;
V(sp);
吃桔子;
goto L3;
}
process daughter {
L4: P(sg2);
从plate中取苹果;
V(sp);
吃苹果;
goto L4;
}
```
## 5 管程概念的提出
1. 管程试图抽象相关并发进程对共享变量访问,以提供一个友善的并发程序设计开发环境
2. 管程是由若干公共变量及其说明和所有访问这些变量的过程所组成
3. 管程把分散在各个进程中互斥地访问公共变量的那些临界区集中起来管理,管程的局部变量只能由该管程的过程存取
4. 进程只能互斥地调用管程中的过程
5. 管程的条件变量
1. 条件变量condition variables当调用管程过程的进程无法运行时用于阻塞进程的信号量
2. 同步原语wait当一个管程过程发现无法继续时如发现没有可用资源时它在某些条件变量上执行wait这个动作引起调用进程阻塞
3. 同步原语signal用于释放在条件变量上阻塞的进程
6. 管程过程执行中signal的处理问题
1. 当使用signal释放一个等待进程时可能出现两个进程同时停留在管程内。解决办法
1. 执行signal的进程等待直到被释放进程退出管程或等待另一个条件
2. 被释放进程等待直到执行signal的进程退出管程或等待另一个条件
2. 霍尔采用了第一种办法
3. 汉森选了二者的折中规定管程过程所执行的signal操作是过程体的最后一个操作
## 6 霍尔管程的实现方法
1. 使用signal释放一个等待进程时霍尔管程让执行signal的进程等待直到被释放进程退出管程或等待另一个条件
2. 霍尔管程基于PV操作原语实现
1. Wait和signal可以是程序过程
2. 可以用语言机制实现霍尔管程
```c
// 互斥调用霍尔管程的信号量
TYPE interf=RECORD
mutex: semaphore; // 调用管程过程前使用的互斥信号量
next: semaphore; // 发出signal的进程挂起自己的信号量
next_count: integer; // 在next上等待的进程数
END;
// 互斥调用霍尔管程的框架
P(IM.mutex);
<过程体>
if IM.next_cout > 0 then V(IM.next);
else V(Im.mutex);
// 霍尔管程的条件变量
x_sem: semaphore; // 与资源相关的信号量
x_count: integer; // 在x_sem上等待的进程数
// 霍尔管程的wait进程
procedure wait(var x_sem: semaphore, var x_count: integer, var IM: interf);
begin
x_count += 1;
if IM.next_count > 0 then V(IM.next);
else V(IM.mutex);
P(x_sem);
x_count -= 1;
end;
// 霍尔管程的signal过程
procedure signal(var x_sem: semaphore, var x_count: integer, var IM: interf);
begin
if x_count > 0 then begin
IM.next_count += 1;
V(x_sem);
P(IM.next); // 进入等待调用管程的队列
IM.next_count -= 1;
end;
end;
// 哲学家问题
TYPE dining_philosophers = MONITOR
var state: array[0..4] of (thinking, hungry, eating);
s: array[0..4] of semaphore;
s_count: array[0..4] of integer;
define pickup, putdown;
use wait, signal;
procedure test(k: 0..4) P
if state[(k-1) mod 5] <> eating and state[k]=hungry
and state[(k+1) mod 5] <> eating then
{state[k] := eating; signal(s[k],s_count[k],IM);}
}
procedure pickup(i:0..4) {
state[i] := hungry;
test(i);
if state[i] <> eating
then wait(s[i], s_count[i], IM);
}
procedure putdown(i:0..4 {
state{i]:=thinking;
test((i-1) mod 5);
test((i+1) mod 5);
}
begin
for i:=0 to 4 do
state[i] := thinking;
end;
begin
process philosopher_i() {
L:thinking();
P(IM.mutex);
dining_philosophers.pickup(i);
if IM.next_count > 0 then V(IM.next); else V(IM.mutex);
eating();
P(IM.mutex);
dining_phi;osophers.putdown(i);
if IM.next_count > 0 then V(IM.next); else V(IM.mutex);
goto L;
}
end;
// 读者写者问题
TYPE read-writer = MONITOR
var Rc, Wc: integer; R, W:semaphore; rc, wc: integer;
define start_read, end_read, start_writer, end_writer;
use wait, signal, check, release;
begin rc:=0;wc:=0;Rc:=0;Wc:=0;R:=0;W:=0; end;
procedure start_read;
begin
if wc > 0 then wait(R,Rc,IM);
rc += 1;
signal(R, Rc, IM); // 连续释放读者
end;
procedure end_read;
begin
rc += 1;
if rc = 0 then signal(W, Wc,IM);
end;
procedure start_write;
begin
wc += 1;
if rc > 0 or wc > 1 then wait(W, Wc, IM);
end;
procedure end_write;
begin
wc += 1;
if wc > 0 then signal(W,Wc,IM);
else signal(R,Rc,IM);
end;
```
## 7 进程通信的概念
1. 交往进程通过信号量操作实现进程互斥和同步,这是一种低级通信方式
2. 进程有时还需交换更多的信息(如把数据传送给另一个进程),可以引进高级通信方式——进程通信机制,实现进程间用信件来交换信息
3. 进程通信扩充了并发进程的数据共享
4. 进程直接通信
1. 发送或接收信件的进程指出信件发给谁或从谁那里接收信件
1. send(P信件)把信件发送给给进程P
2. receive(Q信件)从进程Q接收信件
5. 进程间接通信
1. 发送或接收信件通过一个信箱来进行,该信箱有唯一标识符
2. 多个进程共享一个信箱
1. send(A信件)把信件传送到信箱A
2. receive(A信件)从信箱A接收信件
3. 间接通信的信箱
1. 存放信件的存储区域,每个信箱可以分成信箱特征和信箱体两部分
1. 信箱特征指出信箱容量、信件格式、指针等
2. 信箱体用来存放信件,信箱体分成若干个区,每个区可容纳一封信
6. 发送信件原语的处理流程
1. 若指定的信箱未满,则把信件送入信箱中指针所指示的位置,释放等待该信箱中信件的等待者;否则,发送信件者被置成等待信箱的状态
7. 接收信件原语的处理流程
1. 若指定信箱中有信件,则取出一封信件,释放等待信箱的等待者;否则,接收信件者被置成等待信箱中信件的状态
## 8 基于流的进程通信
1. 多个进程使用一个共享的消息缓冲区(管道、多路转接器、套接字)
2. 一些进程往消息缓冲区中写入字符流send/write
3. 一些进程从消息缓冲区中读出字符流receive/read
4. 信息交换单位基于字符流,长度任意
## 9 远程过程调用RPC
1. 采用客户/服务器计算模式
2. 服务器进程提供一系列过程/服务,供客户进程调用
3. 客户进程通过调用服务器进程提供的过程/服务获得服务
4. 考虑到客户计算机和服务器计算机的硬件异构型外部数据表示XDR被引入来转换每台计算机的特殊数据格式为标准数据格式
## 10 死锁的产生
### 定义
1. 允许多个进程并发执行共享系统资源时,系统必须要提供同步机制和进程通信机制。然而,对这种机制使用不当的话,可能会出现进程永久被阻塞的现象。例如,两个进程分别等待对方占有的一个资源,于是两者都不能执行而处于永远等待,这种现象称为“死锁”
### 死锁:每一个进程都在等待被另一个进程所占有的、不能抢占的资源
1. 存在n个进程P1P2...Pn
2. 进程Pi因为申请不到资源Ri而处于等待状态
3. 而Ri又被Pi+1占有Rn被P1占有
4. 显然这n个进程的等待状态永远不能结束这n个进程就处于死锁状态
### 产生
1. 竞争资源产生死锁
2. PV操作使用不当产生死锁
3. 同类资源分配不当引起死锁
1. 若系统中有m个资源被n个进程共享当每个进程都要求K个资源而$m< n\times(K-1)+1$时,如果分配不当就可能引起死锁
4. 对临时性资源使用不加限制引起死锁
1. 在进程通信时使用的信件可以看作是一种临时性资源,如果对信件的发送和接收不加限制的话,则可能引起死锁
5. 产生的必要条件
1. 互斥条件:进程应互斥使用资源,任一时刻一个资源仅为一个进程独占
2. 占有和等待条件:一个进程请求资源得不到满足而等待时,不释放已占有的资源
3. 不剥夺条件:任一进程不能从另一进程那里抢夺资源
4. 循环等待条件:存在一个循环等待链,每一个进程分别等待它前一个进程所持有的资源
### 解决办法
#### 产生死锁因素:
1. 系统拥有的资源数量
2. 资源分配的策略
3. 进程对资源的使用要求以及并发进程的推进顺序
#### 三个方面解决死锁问题
1. 死锁防止
1. 破坏四个必要条件之一
2. 破坏第一个条件,把独占型资源改造成共享性资源,使资源可同时访问而不是互斥使用。但对许多资源无法做到
3. 破坏第三个条件,采用剥夺式调度方法,但剥夺式调度方法目前只适用于对主存资源和处理器资源的分配,而不适用于所有资源
4. 破坏第二个条件,采用静态分配
1. 静态分配:一个进程必须在执行前就申请它所要的全部资源,并且直到它索要的资源都得到满足之后才开始执行
2. 所有并发执行的进程要求的资源总和不超过系统拥有的资源数
3. 进程在执行过程中不再申请资源,所以就破坏了第二个条件
5. 破坏第四个条件,层次分配
1. 层次分配将资源分成多个层次
2. 一个进程得到某一层的一个资源后,它只能再申请在较高层的资源
3. 当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源
4. 当一个进程获得了某一层的一个资源后,它想再申请该层中的另一个资源,那么必须先释放该层中的已占资源
2. 死锁避免
1. 当不能防止死锁的产生时,如果能掌握并发进程中与每个进程有关的资源申请情况,仍然可以避免死锁的发生
2. 只需要在位申请者分配资源前先测试系统状态,若把资源分配给申请者会产生死锁的话,则拒绝分配,否则接收申请,为它分配资源
3. 银行家算法:借钱给有偿还能力的客户
1. 系统首先检查申请者对资源的最大需求量,如果现存的资源可以满足它的最大需求量时,就满足当前的申请
2. 换言之,仅仅在申请者可能无条件地归还它所申请的全部资源时,才分配资源给它
3. 例:
1. 假设系统有三个进程P、Q、R系统只有一类资源共10个。分配情况
1. P占有4个还需申请4个
2. Q占有2个还需申请2个
3. R占有2个还需申请7个
2. 现在总共占用了8个资源只能再分配个两个资源。所以先分配给Q等Q完成之后就有4个资源继续分配给P等P完成之后就有8个资源最后给R
3. 死锁检测和恢复
1. 对资源的分配不加限制,但系统定时运行一个“死锁检测”程序,判定系统内是否已出现死锁,若检测到死锁则设法加以解除
2. 检测:
1. 可设置两张表格来记录进程使用资源的情况
1. 等待资源表记录每个被阻塞进程等待的资源
2. 占用资源表记录每个进程占有的资源
2. 进程申请资源时,先查该资源是否为其他进程所占用;若资源空闲,则把该资源分配给申请者且登入占用表;否则,则登入进程等待资源表
3. 死锁检测程序定时检测这两张表若有进程Pi等待资源rk且rk被进程Pj占用则说明Pi和Pj具有“等待占用关系”记为WPiPj
4. 死锁检测程序反复检测这两张表,可以列出所有的“等待占用关系”
5. 如果出现WPiPjWPjPk...WPmPnWPnPi显然系统中存在一组循环等待资源的进程PiPjPk...PmPn也就是说出现了死锁
6. 两张表可以用一个矩阵A表示
| 进程/$A[b_{ij}]$/进程 | P1 | P2 | ... | Pn |
| --------------------- | ---- | ---- | ---- | ---- |
| P1 | b11 | b12 | ... | b1n |
| P2 | b21 | b22 | ... | b2n |
| ... | ... | ... | ... | ... |
| Pn | bn1 | bn2 | ... | bnn |
其中b_ij=1表示当Pi等待被Pj占用的资源时=0表示Pi与Pj不存在等待占用关系时
7. 算法
```c
// Warshall的传递闭包算法检测是否有死锁发生
// 即对矩阵A构造传递闭包A*[bij]
for k:= 1 to n do
for i: 1 to n do
for j:= 1 to n do
bij:= bij(bij∩bkj)
```
#### 死锁检测后的解决办法
1. 采用重新启动进程执行的办法,恢复工作应包含重启动一个或全部进程,以及从哪一点开始重启动
2. 全部卷入死锁从头开始启动,但这样的代价相当大
3. 进程执行过程中定时设置校验点,从校验点开始重执行
4. 中止一个卷入死锁的进程,以后重执行

123
操作系统/9 其他.md Normal file
View File

@@ -0,0 +1,123 @@
# 其他
## 1 计算机系统层级(由上到下):
1. 应用——用户
2. 语言处理——应用程序员的视图
3. 操作系统——语言处理程序设计者的视图
4. 计算机硬件——操作系统设计者的视图
## 2 存储程序计算机特点
1. 以运算单元为中心,控制流由指令流产生
2. 采用存储程序原理,面向主存组织数据流
3. 主存是按地址访问、线性编址的空间
4. 指令由操作码和地址码组成
5. 数据以二进制编码
## 3 程序员的计算机系统视图(由下到上)
1. 计算机硬件系统——机器指令(语言)
1. 机器指令是计算机系统执行的基本命令,是中央处理器执行的基本单位
2. 指令由一个或多个字节组成,包括操作码字段、一个或多个操作数地址字段、以及一些表征机器状态的状态字以及特征码
3. 指令完成各种算术逻辑运算、数据传输、控制流跳转
4. 分类
1. 特权指令:只能被操作系统内核使用的指令
2. 非特权指令:能够被所有程序使用的指令
3. 用户程序并非能够使用全部的机器指令,那些与计算机核心资源相关的特殊指令会被保护
2. 操作系统与实用程序——扩展机器指令
1. 资源管理:机器语言+广义指令(扩充了硬件资源管理)
2. 文件系统:机器语言+系统调用(扩充了信息资源管理)
3. 数据库管理系统——数据库语言
4. 语言处理程序——高级语言(面向问题的语言)
5. 支撑软件——程序员(涵盖以上)
## 4 计算机程序执行过程:
1. 高级语言源程序通过转换程序,再通过编译程序和解释程序,变成目标代码程序
2. 汇编语言源程序通过汇编程序,变成目标代码程序
3. 目标代码程序通过连接程序,变成可执行程序,通过装入程序,变成内存执行程序
4. 同时进入数据库管理系统和操作系统,数据库管理系统同时进入文件系统和设备管理(属于操作系统)
P.S.
操作系统
1. 文件系统
2. 设备管理
3. 内存管理
4. 进程管理
## 5 指令执行过程
1. CPU根据PC取出指令放入IR并对指令译码然后发出各种控制命令执行微操作系列从而完成一条指令的执行
2. 步骤
1. 取指根据PC从存储器或高速缓冲存储器中取指令到IR
2. 解码解译IR中的指令来绝对其执行行为
3. 执行连接到CPU部件执行运算产生结果并写回同时在CC里设置运算结论标志跳转指令操作PC其他指令递增PC值。
P.S.
在一条指令进行解码操作时,另一条指令可以开始取指;在进行执行操作时,另一条可以在执行解码,而第三条可以开始取指
多道程序同时计算CPU速度与I/O速度不匹配的矛盾非常突出。只有让多道程序同时进入内存争抢CPU运行才可以使得CPU和外围设备充分并行从而提高计算机系统的使用效率
## 6 多道程序设计
### 概念
* 指让多个程序同时进入计算机的主存储器进行计算
### 特点:
1. CPU与外部设备充分并行
2. 外部设备之间充分并行
3. 发挥CPU的使用效率
4. 提高单位时间的算题量
### 实现:
1. 进程:进入内存执行的程序建立管理实体
2. OS应能管理与控制进程程序的执行
3. OS协调管理各类资源在进程间的使用
1. 处理器的管理和调度
2. 主存储器的管理和调度
3. 其他资源的管理和调度
### 实现要点:
1. 如何使用资源:调用操作系统提供的服务例程(如何陷入操作系统)
2. 如何复用CPU调度程序在CPU空闲时让其他程序运行
3. 如何使CPU与I/O设备充分并行设备控制器与通道专用的I/O处理器
4. 如何让正在运行的程序让出CPU中断中断正在执行的程序引入OS处理
## 7 操作系统的人机交互
1. 交互设备
1. 传统的终端设备
2. 新型的模式识别设备
2. 发展
1. 交互式控制方式
1. 行命令控制方式1960年代开始使用
2. 全屏幕控制方式1970年代开始使用
2. 强调人而不是技术是人机交互的中心
3. WIMP界面
1. 窗口Windows、图标Icons、菜单Menu、指示装置Pointing Devices图形用户界面
4. 多媒体计算机
5. 虚拟现实系统
## 8 处理器模式
1. 计算机通过设置处理器模式实现特权指令管理
2. 计算机一般设置0、1、2、3四种运行模式建议分别对应
1. 0操作系统内核
2. 1系统调用
3. 2共享库程序
4. 3用户程序等保护级别
3. 0模式可以执行全部指令3模式只能执行非特权指令其他每种运行模式可以规定执行的指令子集
4. 一般来说现代操作系统只使用0和3两种模式对应于内核模式和用户模式
### 处理器模式切换
1. 简称模式切换,包括“用户模式→内核模式”和“内核模式→用户模式”的转换
2. 中断、异常或系统异常等事件导致用户程序向OS内核切换触发用户模式→内核模式
1. 程序请求操作系统服务
2. 程序运行时发生异常
3. 程序运行时发生并响应中断
3. OS内核处理完成后调用中断返回指令如Inter的iret触发内核模式→用户模式

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,141 @@
# 计算机操作系统 - 内存管理
<!-- GFM-TOC -->
* [计算机操作系统 - 内存管理](#计算机操作系统---内存管理)
* [虚拟内存](#虚拟内存)
* [分页系统地址映射](#分页系统地址映射)
* [页面置换算法](#页面置换算法)
* [1. 最佳](#1-最佳)
* [2. 最近最久未使用](#2-最近最久未使用)
* [3. 最近未使用](#3-最近未使用)
* [4. 先进先出](#4-先进先出)
* [5. 第二次机会算法](#5-第二次机会算法)
* [6. 时钟](#6-时钟)
* [分段](#分段)
* [段页式](#段页式)
* [分页与分段的比较](#分页与分段的比较)
<!-- GFM-TOC -->
## 虚拟内存
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7b281b1e-0595-402b-ae35-8c91084c33c1.png"/> </div><br>
## 分页系统地址映射
内存管理单元MMU管理着地址空间和物理内存的转换其中的页表Page table存储着页程序地址空间和页框物理内存空间的映射表。
一个虚拟地址分成两个部分,一部分存储页面号,一部分存储偏移量。
下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位。例如对于虚拟地址0010 000000000100前 4 位是存储页面号 2读取表项内容为110 1页表项最后一位表示是否存在于内存中1 表示存在。后 12 位存储偏移量。这个页对应的页框的地址为 110 000000000100
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png" width="500"/> </div><br>
## 页面置换算法
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。
页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
### 1. 最佳
> OPT, Optimal replacement algorithm
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。
是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。
举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列:
```html
70120304230321201701
```
开始运行时,先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。
### 2. 最近最久未使用
> LRU, Least Recently Used
虽然无法知道将来要使用的页面情况但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
为了实现 LRU需要在内存中维护一个所有页面的链表。当一个页面被访问时将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
```html
47071012126
```
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/eb859228-c0f2-4bce-910d-d9f76929352b.png"/> </div><br>
### 3. 最近未使用
> NRU, Not Recently Used
每个页面都有两个状态位R 与 M当页面被访问时设置页面的 R=1当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:
- R=0M=0
- R=0M=1
- R=1M=0
- R=1M=1
当发生缺页中断时NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。
NRU 优先换出已经被修改的脏页面R=0M=1而不是被频繁使用的干净页面R=1M=0
### 4. 先进先出
> FIFO, First In First Out
选择换出的页面是最先进入的页面。
该算法会将那些经常被访问的页面换出,导致缺页率升高。
### 5. 第二次机会算法
FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候检查最老页面的 R 位。如果 R 位是 0那么这个页面既老又没有被使用可以立刻置换掉如果是 1就将 R 位清 0并把该页面放到链表的尾端修改它的装入时间使它就像刚装入的一样然后继续从链表的头部开始搜索。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ecf8ad5d-5403-48b9-b6e7-f2e20ffe8fca.png"/> </div><br>
### 6. 时钟
> Clock
第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5f5ef0b6-98ea-497c-a007-f6c55288eab1.png"/> </div><br>
## 分段
虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。
下图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22de0538-7c6e-4365-bd3b-8ce3c5900216.png"/> </div><br>
分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png"/> </div><br>
## 段页式
程序的地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。
## 分页与分段的比较
- 对程序员的透明性:分页透明,但是分段需要程序员显式划分每个段。
- 地址空间的维度:分页是一维地址空间,分段是二维的。
- 大小是否可以改变:页的大小不可变,段的大小可以动态改变。
- 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。

View File

@@ -0,0 +1,144 @@
# 计算机操作系统 - 死锁
<!-- GFM-TOC -->
* [计算机操作系统 - 死锁](#计算机操作系统---死锁)
* [必要条件](#必要条件)
* [处理方法](#处理方法)
* [鸵鸟策略](#鸵鸟策略)
* [死锁检测与死锁恢复](#死锁检测与死锁恢复)
* [1. 每种类型一个资源的死锁检测](#1-每种类型一个资源的死锁检测)
* [2. 每种类型多个资源的死锁检测](#2-每种类型多个资源的死锁检测)
* [3. 死锁恢复](#3-死锁恢复)
* [死锁预防](#死锁预防)
* [1. 破坏互斥条件](#1-破坏互斥条件)
* [2. 破坏占有和等待条件](#2-破坏占有和等待条件)
* [3. 破坏不可抢占条件](#3-破坏不可抢占条件)
* [4. 破坏环路等待](#4-破坏环路等待)
* [死锁避免](#死锁避免)
* [1. 安全状态](#1-安全状态)
* [2. 单个资源的银行家算法](#2-单个资源的银行家算法)
* [3. 多个资源的银行家算法](#3-多个资源的银行家算法)
<!-- GFM-TOC -->
## 必要条件
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c037c901-7eae-4e31-a1e4-9d41329e5c3e.png"/> </div><br>
- 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
- 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
- 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
- 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
## 处理方法
主要有以下四种方法:
- 鸵鸟策略
- 死锁检测与死锁恢复
- 死锁预防
- 死锁避免
## 鸵鸟策略
把头埋在沙子里,假装根本没发生问题。
因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。
当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。
大多数操作系统,包括 UnixLinux 和 Windows处理死锁问题的办法仅仅是忽略它。
## 死锁检测与死锁恢复
不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
### 1. 每种类型一个资源的死锁检测
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b1fa0453-a4b0-4eae-a352-48acca8fff74.png"/> </div><br>
上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
图 a 可以抽取出环,如图 b它满足了环路等待条件因此会发生死锁。
每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
### 2. 每种类型多个资源的死锁检测
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br>
上图中,有三个进程四个资源,每个数据代表的含义如下:
- E 向量:资源总量
- A 向量:资源剩余量
- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
- R 矩阵:每个进程请求的资源数量
进程 P<sub>1</sub> 和 P<sub>2</sub> 所请求的资源都得不到满足,只有进程 P<sub>3</sub> 可以,让 P<sub>3</sub> 执行,之后释放 P<sub>3</sub> 拥有的资源,此时 A = (2 2 2 0)。P<sub>2</sub> 可以执行,执行后释放 P<sub>2</sub> 拥有的资源A = (4 2 2 1) 。P<sub>1</sub> 也可以执行。所有进程都可以顺利执行,没有死锁。
算法总结如下:
每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。
1. 寻找一个没有标记的进程 P<sub>i</sub>,它所请求的资源小于等于 A。
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。
### 3. 死锁恢复
- 利用抢占恢复
- 利用回滚恢复
- 通过杀死进程恢复
## 死锁预防
在程序运行之前预防发生死锁。
### 1. 破坏互斥条件
例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
### 2. 破坏占有和等待条件
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
### 3. 破坏不可抢占条件
### 4. 破坏环路等待
给资源统一编号,进程只能按编号顺序来请求资源。
## 死锁避免
在程序运行时避免发生死锁。
### 1. 安全状态
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br>
图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b运行结束后释放 B此时 Free 变为 5图 c接着以同样的方式运行 C 和 A使得所有进程都能成功运行因此可以称图 a 所示的状态时安全的。
定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
### 2. 单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png"/> </div><br>
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
### 3. 多个资源的银行家算法
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br>
上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
检查一个状态是否安全的算法如下:
- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行那么系统将会发生死锁状态是不安全的。
- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
如果一个状态不是安全的,需要拒绝进入这个状态。

View File

@@ -0,0 +1,20 @@
# 计算机操作系统
- [概述](计算机操作系统%20-%20概述.md)
- [进程管理](计算机操作系统%20-%20进程管理.md)
- [死锁](计算机操作系统%20-%20死锁.md)
- [内存管理](计算机操作系统%20-%20内存管理.md)
- [设备管理](计算机操作系统%20-%20设备管理.md)
- [链接](计算机操作系统%20-%20链接.md)
## 参考资料
- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014.
- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001.
- Bryant, R. E., & OHallaron, D. R. (2004). 深入理解计算机系统.
- 史蒂文斯. UNIX 环境高级编程 [M]. 人民邮电出版社, 2014.
- [Operating System Notes](https://applied-programming.github.io/Operating-Systems-Notes/)
- [Operating-System Structures](https://www.cs.uic.edu/\~jbell/CourseNotes/OperatingSystems/2_Structures.html)
- [Processes](http://cse.csusb.edu/tongyu/courses/cs460/notes/process.php)
- [Inter Process Communication Presentation[1]](https://www.slideshare.net/rkolahalam/inter-process-communication-presentation1)
- [Decoding UCS Invicta Part 1](https://blogs.cisco.com/datacenter/decoding-ucs-invicta-part-1)

View File

@@ -0,0 +1,61 @@
# 计算机操作系统 - 设备管理
<!-- GFM-TOC -->
* [计算机操作系统 - 设备管理](#计算机操作系统---设备管理)
* [磁盘结构](#磁盘结构)
* [磁盘调度算法](#磁盘调度算法)
* [1. 先来先服务](#1-先来先服务)
* [2. 最短寻道时间优先](#2-最短寻道时间优先)
* [3. 电梯算法](#3-电梯算法)
<!-- GFM-TOC -->
## 磁盘结构
- 盘面Platter一个磁盘有多个盘面
- 磁道Track盘面上的圆形带状区域一个盘面可以有多个磁道
- 扇区Track Sector磁道上的一个弧段一个磁道可以有多个扇区它是最小的物理储存单位目前主要有 512 bytes 与 4 K 两种大小;
- 磁头Head与盘面非常接近能够将盘面上的磁场转换为电信号或者将电信号转换为盘面的磁场
- 制动手臂Actuator arm用于在磁道之间移动磁头
- 主轴Spindle使整个盘面转动。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/014fbc4d-d873-4a12-b160-867ddaed9807.jpg"/> </div><br>
## 磁盘调度算法
读写一个磁盘块的时间的影响因素有:
- 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上)
- 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
- 实际的数据传输时间
其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。
### 1. 先来先服务
> FCFS, First Come First Served
按照磁盘请求的顺序进行调度。
优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。
### 2. 最短寻道时间优先
> SSTF, Shortest Seek Time First
优先调度与当前磁头所在磁道距离最近的磁道。
虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两端的磁道请求更容易出现饥饿现象。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/4e2485e4-34bd-4967-9f02-0c093b797aaa.png"/> </div><br>
### 3. 电梯算法
> SCAN
电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。
电梯算法(扫描算法)和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。
因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/271ce08f-c124-475f-b490-be44fedc6d2e.png"/> </div><br>

View File

@@ -0,0 +1,68 @@
# 计算机操作系统 - 链接
<!-- GFM-TOC -->
* [计算机操作系统 - 链接](#计算机操作系统---链接)
* [编译系统](#编译系统)
* [静态链接](#静态链接)
* [目标文件](#目标文件)
* [动态链接](#动态链接)
<!-- GFM-TOC -->
## 编译系统
以下是一个 hello.c 程序:
```c
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
```
在 Unix 系统上,由编译器把源文件转换为目标文件。
```bash
gcc -o hello hello.c
```
这个过程大致如下:
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b396d726-b75f-4a32-89a2-03a7b6e19f6f.jpg" width="800"/> </div><br>
- 预处理阶段:处理以 # 开头的预处理命令;
- 编译阶段:翻译成汇编文件;
- 汇编阶段:将汇编文件翻译成可重定位目标文件;
- 链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。
## 静态链接
静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/47d98583-8bb0-45cc-812d-47eefa0a4a40.jpg"/> </div><br>
## 目标文件
- 可执行目标文件:可以直接在内存中执行;
- 可重定位目标文件:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件;
- 共享目标文件:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接;
## 动态链接
静态库有以下两个问题:
- 当静态库更新时那么整个程序都要重新进行链接;
- 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。
共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示Windows 系统上它们被称为 DLL。它具有以下特点
- 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
- 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/76dc7769-1aac-4888-9bea-064f1caa8e77.jpg"/> </div><br>

View File

@@ -0,0 +1 @@
[计算机操作系统](https://github.com/CyC2018/CS-Notes/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%20-%20%E7%9B%AE%E5%BD%95.md)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
# 基础知识
> 主要用来总结面试过程中的常见问题
> 参考文献
> * [操作系统教程总结](https://blog.csdn.net/yanglingwell/article/details/53745758)
> * [计算机操作系统知识梳理](https://blog.csdn.net/wzk646795873/article/details/79783695)
## 1 操作系统的资源管理技术
* 资源管理解决**物理资源数量不足**和**合理分配资源**这两个问题。
![](image/2021-03-29-18-49-41.png)
* 操作系统虚拟机为用户提供了一种简单、清晰、易用、高效的计算机模型。虚拟机的每种资源都是物力资源通过复用、虚拟和抽象而得到的产物。**虚拟机提供进程运行的逻辑计算环境**。从概念上来说,一个进程运行在一台虚拟机上,可以认为一个进程就是一台虚拟机,一台虚拟机就是一个进程。
### 复用:空分复用共享和时分复用共享。
* 空分复用共享(space-multiplexed sharing): 将资源从“空间”上分割成更小的单位供不同进程使用。在计算机系统中,内存和外存(磁盘)等是空分复用共享的。
* 时分复用共享(time-multiplexed sharing): 将资源从“时间”上分割成更小的单位供不同进程使用。在计算机系统中,处理器和磁盘机等是时分复用共享的。
### 虚拟:对资源进行转化、模拟或整合。
* 把一个物理资源转变成多个逻辑上的对应物,也可以把多个物理资源变成单个逻辑上的对应物,即创建无须共享独占资源的假象,或创建易用且多于实际物理资源的虚拟资源假象,以达到多用户共享一套计算机物理资源的目的。虚拟技术可用于外部设备(外部设备同时联机操作(SPOOLing)),存储资源(虚拟内存)和文件系统(虚拟文件系统(Virtual File System, VFS))中。
* 复用和虚拟相比较,复用所分割的是实际存在的物理资源,而虚拟则实现假想的同类资源。**虚拟技术解决某类物理资源不足的问题**,提供易用的虚拟资源和更好的运行环境。
### 抽象:屏蔽硬件资源的物理特性和实现细节
* 通过创建软件来屏蔽硬件资源的物理特性和实现细节,简化对硬件资源的操作、控制和使用。
* 复用和虚拟的主要目标是解决**物理资源数量不足的问题**,抽象则用于处理系统复杂性,重点**解决资源易用性**。
## 2 系统调用
* 系统调用: 为给应用程序的运行提供良好环境,内核提供了一系列具有预定功能的服务例程,通过一组称为**系统调用System Call**的接口呈现给用户,系统调用把应用程序的请求传送至内核,调用相应的服务例程完成所需处理,将处理结果返回给应用程序。(注:系统调用的编号称为功能号)
![](image/2021-03-29-18-57-45.png)
* 系统调用的执行过程: 当CPU执行程序中编写的 **访管指令(supervisor==自陷指令(trap)==中断指令(interrupt)** 等引起处理器中断的机器指令,实现的系统调用,产生异常信号,通过**陷阱机制**(也称**异常处理机制**,当异常或中断发生时,处理器捕捉到一个执行线程,并且将控制权转移到操作系统中某一个固定地址的机制),处理器的状态由用户态(user mode, 又称目态或普通态)转变为内核态(kerbel mode, 又称管态),进入操作系统并执行相应服务例程,以获得操作系统服务。当系统调用执行完毕时,处理器再次切换状态,控制返回至发出系统调用的程序。
* 系统调用是应用程序获得操作系统服务的唯一途径。
### 系统调用的作用:
1. 内核可以基于权限和规则对资源访问进行裁决,保证系统的安全性。
2. 系统调用对资源进行抽象,提供一致性接口,避免用户在使用资源时发生错误,且编程效率大大提高。
### 系统调用与函数调用的区别:
* 调用形式和实现方式不同。功能号 VS 地址; 用户态转换到内核态 VS 用户态。
* 被调用代码的位置不同。 动态调用 + 操作系统 VS 静态调用 + 用户级程序。
* 提供方式不同。 操作系统 VS 编程语言。
## 3 操作系统内核
### 定义
* 内核: 是一组程序模块,作为可信软件来提供支持进程并发执行的基本功能和基本操作,通常驻留在内核空间,运行于内核态,具有直接访问硬件设备和所有内存空间的权限,是仅有的能够执行特权指令的程序。
## 内核的功能
1. 中断处理。中断处理是内核中最基本的功能,也是操作系统赖以活动的基础。
2. 时钟管理。时钟管理是内核的基本功能。
3. 短程调度。短程调度的职责是分配处理器,按照一定的策略管理处理器的转让,以及完成保护和恢复现场工作。
4. 原语管理。 原语是内核中实现特定功能的不可中断过程。
* 内核是操作系统对裸机的第一次改造,内核和裸机组成了第一层虚拟机,进程在虚拟机上运行。
## 4 处理器状态:内核态和用户态
### 概念
* 仅在内核态下才能使用的指令称为特权指令,执行这些指令不仅影响运行程序自身,而且还会干扰其他程序及操作系统。 非特权指令在内核态和和用户态下都能工作。
* 现代计算机为处理器建立硬件标志位,称处理器状态位,通常是**程序状态字(Program Status Word, PSW)**中的一位,来将处理器的状态设置为内核态或用户态。
### 用户态向内核态转换的情况:
1. 程序请求操作系统服务, 执行系统调用。
2. 在程序运行时产生中断事件(如I/O操作完成),运行程序被中断,转向**中断处理程序**处理。
3. 在程序运行时产生异常事件(如在目态下执行特权指令),运行程序被打断,转向**异常处理程序**工作。
> 以上三种情况都是通过中断机制发生,可以说中断和异常是用户态到内核态转换的仅有途径。
### 用户栈和核心栈
1. 用户栈是用户进程空间中的一块区域。用于保存应用程序的子程序(函数)间相互调用的参数,返回值,返回点和子程序的局部变量。
2. 核心栈是内存中操作系统空间的一块区域。用于保存中断现场和保存操作系统程序(函数)间相互调用的参数,返回值,返回点和程序的局部变量。
## 5 中断Interupt
### 定义
* 中断程序执行过程中遇到急需处理的事件时暂时终止现行程序在CPU上的运行转而执行相应的**事件处理程序**,待处理完成后再返回断点或调度其他程序的执行过程。
### 中断的分类:
1. 外中断(又称中断或异步中断) 来自处理器之外的中断信号,如,时钟中断、键盘中断等。外中断可分为可屏蔽中断和非可屏蔽中断。
2. 内中断(又称异常或同步中断),来自处理器内部的中断信号,如,访管中断,硬件故障中断,程序性中断等。内中断不能被屏蔽。
### 中断和异常的响应:
* 发现中断源 → 保护现场 → 转向(中断/异常)事件处理程序执行 → 恢复现场
## 6 进程
### 定义
* 进程:具有独立功能的程序在某个数据集合上的一次运行活动,也**是操作系统进行资源分配和保护的基本单位** 。
1. 从原理角度看,进程是支持程序执行的一种系统机制,它对处理器上运行程序的活动进行抽象。
2. 从实现角度看,进程是一种数据结构,用来准确地刻画运行程序的状态和系统动态变化状况。
### 进程状态的七态模型
![](image/2021-03-29-19-12-03.png)
1. 新建态(new): 进程被创建,尚未进入就绪队列。
1. 就绪态(ready): 进程具备运行条件,等待系统分配处理器。
1. 挂起就绪态(ready suspend):进程具备运行条件,但目前在外存中。
1. 运行态(running): 进程占有处理器正在运行。
1. 终止态(exit): 进程达到正常结束点或被其他原因所终止,下一步将被撤销。
1. 等待态(wait): 又称阻塞态或休眠态。进程正在等待某个事件完成,目前不具备运行条件。
1. 挂起等待态(blocked suspend): 进程正在等待某个事件完成,并且在外存中。
### 概述
* 程序和数据刻画进程的静态特征,称为进程控制块的一种数据结构刻画进程的动态特征。**进程映像(process image)** 包括进程控制块、进程程序块、进程核心块、进程数据块等要素。
* 进程控制块(Process Control Block, PCB):进程存在的唯一标识,操作系统掌握进程的唯一资料结构和管理进程的主要依据。包括标识信息、现场信息和控制信息等信息。
* 进程队列(process queue)处于同一状态的所有进程的PCB链接在一起的数据结构。 有两种队列组织方式:链接方式和索引方式。
* 进程切换必定在内核态而非用户态发生。
* 进程可以分为两部分,资源集合和线程集合。进程要支撑线程运行,为线程提供虚拟地址空间和各种资源。进程封装管理信息,线程封装执行信息。
![](image/2021-03-29-19-19-37.png)
## 7 处理器调度
### 处理器调度层次:
![](image/2021-03-29-19-41-25.png)
1. **高级调度** 又称作业调度、长程调度。从输入系统的一批 **作业(job, 用户提交给操作系统计算的一个独立任务)** 中按照预定的调度策略挑选若干作业进入内存,为其分配所需资源并创建对应作业的用户进程。
2. **中级调度** 又称平衡调度,中程调度。根据内存资源情况决定内存所能容纳的进程数目,并完成外存和内存中进程对换工作。
3. **低级调度**:又称进程调度/线程调度,短程调度。根据某种原则决定就绪队列中那个进程/线程先获得处理器,并将处理器出让给它使用。
### 低级调度算法:
1. 先来先服务(First Come First Server, FCFS)算法。
3. 最短作业优先(Shortest Job First, SJF)算法。
3. 最短剩余时间优先(Shortest Remaining Time First, SRTF)算法: 假设当前某进程/线程正在运行,如果***有新进程/线程移入就绪队列***若它所需的CPU运行时间比当前运行的进程/线程所需的剩余CPU时间还短抢占式最短作业优先算法强行剥夺当前执行者的控制权调度新进程/线程执行。
4. 最高响应比优先(Highest Response Ratio First, HRRF)算法:非剥夺式算法。其中,响应比 = (作业已等待时间 + 作业处理时间) / 作业处理时间。
5. 优先级调度算法:优先级高的选择进程/线程优先选择。
6. 轮转调度(Round-Robin, RR)算法: 也称时间片调度。就绪队列的进程轮流运行一个时间片。
7. 多级反馈队列(Multi-Level Feedback Queue, MLFQ)算法。
### 衡量调度算法的性能指标:
1. 资源利用率: CPU利用率 = CPU有效工作时间/(CPU有效工作时间 + CPU空闲等待时间)
2. 吞吐率: 单位时间内CPU处理作业的个数。
3. 公平性: 确保每个进程都能获得合理的CPU份额和其他资源份额不会出现饥饿现象。
4. 响应时间: 从交互式进程提交一个请求(命令)直到获得响应之间的时间间隔。
5. 周转时间: 批处理用户从向系统提交作业开始到作业完成为止的时间间隔。平均周转时间:$T=(\sum_{i=1}^n t_i)/n$ 其中 $t i$表示作业i的周转时间。平均带权作业周转时间 $T = ( \sum_{i=1}^n w_i ) / n$, 其中 $w_i = t_i / t_k$ 表示作业i的周转时间。 $t_k$表示作业i的运行时间。
## 8 进程的交互
### 概述
* **进程互斥(Mutual Exclusion)**: 若干进程因相互抢夺独占型资源而产生的竞争制约关系。
* **进程同步(Synchronization)**: 为完成共同任务的并发进程基于某个条件来协调其活动,因为需要在某些位置上排定执行的先后次序而等待、传递信息或消息所产生的协作制约关系。
### 资源竞争会引发两个控制问题:
1. **死锁**:一组进程因争夺资源陷入永远等待的状态。
2. **饥饿**:一个可运行进程由于由于其他进程总是优先于它,而被调度程序无限期地拖延而不能被执行。
## 9 临界区管理
### 概念
* 并发进程中与共享变量有关的程序段称为临界区(Critical Section)。共享变量所代表的资源称为临界资源(Critical Resource),即一次仅能供一个进程使用的资源。
### 临界区调度原则:
1. 择一而入。 一次之多只有一个进程进入临界区内执行。
2. 忙则要等。 如果已有进程在临界区中, 试图进入此临界区的其他进程应等待。
3. 有限等待。 进入临界区内的进程应在有限时间内退出。
### 临界区管理的软件算法Peterson算法。
* 为每个进程设置标志,当标志值为 true 时表示该进程要求进入临界区,另外再设置一个指示器 turn 以指示可以由哪个进程进入临界区,当 turn = i 时则可由 Pi 进入临界区。
```C++
/* Peterson 算法 */
bool inside[2];
inside[0] = false;
inside[1] = false;
enum { 0, 1 } turn;
/* 进程0 */
process P0(){
inside[0] = true; //请求...
turn = 1;
while(inside[1] && turn == 1) ; //等待...
/*临界区 */
inside[0] = false; //归还...
}
/* 进程1 */
process P1(){
inside[1] = true; //请求...
turn = 0;
while(inside[0] && turn == 0) ; //等待...
/*临界区 */
inside[1] = false; //归还...
}
```
> Peterson算法满足临界区管理的三个原则。
### 临界区管理的硬件设施:
1. 关中断。 在进程进入临界区时关中断,进程退出临界区时开中断。
2. . 测试并设置指令。 利用机器指令TS(Test and Set)实现临界区的上锁和开锁原语操作。
3. 对换指令。 利用对换指令实现临界区的上锁和开锁原语操作。
## 10 信号量(samaphore)和PV操作
### 说明
PV操作都是原语操作 不可中断。
### 信号量和PV操作
* 信号量
```
// 信号量
typedef struct semaphore {
int value; // 信号量值
struct pcb* list; // 指向“等待该信号量的进程队列”的指针
};
```
* P操作
```
// P操作
void P(semaphore s){
s.value--; // 信号量值减一
// 如果信号量值小于0 执行P操作的进程调用sleep(s.list)阻塞自己,
// 被置成“等待信号量s”状态并移入s信号量队列转向进程调度程序。
if(s.value < 0) sleep(s.list);
}
```
* V操作
```
// V操作
void V(semaphore s){
s.value++; // 信号量值加一
// 如果信号量小于等于0 则调用wakeup(s.list)释放一个等待信号量s的进程
// 并转换成就绪态, 进程则继续执行。
if(s.value <= 0) wakeup(s.list);
}
```
1. 若信号量值 s.value 为正值, 此值等于在封锁进程之前对信号量 s 可施行P操作的次数s所代表的实际可用的资源数。
2. 若信号量值 s.value 为负值, 其绝对值等于登记在 s 信号量队列中的等待进程的数目。
3. 通常**P操作意味着请求一个资源****V操作意味着释放一个资源**。在一定条件下P操作也可表示挂起进程的操作V操作代表唤醒被挂起进程的操作。
### 信号量实现互斥
```
semaphore mutex;
mutex = 1
//进程Pi i = 1 2 ... n
process Pi(){
P(mutex);
/* 临界区 */
V(mutex);
}
```
## 11 管程
### 概念
* 管程(monitor):代表**共享资源的数据结构和在其上执行的一组并发进程**就构成管程,管程被请求和释放资源的进程锁调用。
* 信号量机制的缺点进程自备同步操作P(S)和V(S)操作大量分散在各个进程中,不易管理,易发生死锁。
* 管程特点:管程封装了同步操作,对进程隐蔽了同步细节,简化了同步功能的调用界面。用户编写并发程序如同编写顺序(串行)程序。
### 引入管程机制的目的:
1. 把分散在各进程中的临界区集中起来进行管理
2. 防止进程有意或无意的违法同步操作;
3. 便于用高级语言来书写程序,也便于程序正确性验证。
### 实现
![](image/2021-03-29-19-42-48.png)
1. 条件变量。 管程内的一种数据结构。只有在管程中才能被访问,进程可以在条件变量上等待或被唤醒。只能通过 wait() 和 signal() 原语操作来控制。
2. wait()原语。 挂起调用进程并释放管程,直至另一个进程在条件变量上执行 signal()。
3. signal() 原语。如果有其他的进程因对条件变量执行 wait() 而被挂起,便释放之。 如果没有进程在等待,那么相当于空操作,信号不被保存。
## 12 死锁
### 死锁的主要解决方法
1. 死锁防止
1. 死锁避免
1. 死锁检测和恢复。
### 死锁产生的必要条件:
1. 互斥条件。 临界资源是独占资源,进程应互斥且排他地使用这些资源。
2. 占有等待条件。 进程在请求资源得不到满足而等待时,不释放已占有的资源。
3. 不剥夺条件。已获资源只能由进程资源释放,不允许被其他程序剥夺。
4. 循环等待条件。 存在循环等待链,其中每个进程都在等待下一个进程所持有的资源。
### 死锁的防止
* 破坏死锁产生的必要条件。
* 使资源可同时使用(破坏互斥条件)、
* 静态分配资源(破坏占有和等待条件)、
* 剥夺调度(破坏不剥夺条件)、
* 层次分配策略(循环等待条件)等。
### 死锁避免:
* 银行家算法
### 死锁检测和恢复:
* 进程-资源分配图(额…还是去百度吧。)
* 如果进程-资源分配图中无环路,此时系统没有死锁。
* 如果进程-资源分配图中有环路,且每个资源类中只有一个资源,则系统发生死锁。
* 如果进程-资源分配图中有环路,且所涉及的资源类有多个资源,则不一定会发生死锁。
## 13 可变分区存储管理
### 可变分区存储分配算法:
1. 最先适应分配算法。从未分配区的开始位置开始扫描,在找到的第一个能满足长度要求的空闲区上分配存储空间。
2. 下次适应分配算法。从未分配区上次扫描结束处开始顺序查找,在找到的第一个能满足长度要求的空闲区上分配存储空间。
3. 最优适应分配算法。扫描整个未分配区,选择能满足用户进程要求的最小分区分配存储空间。
4. 最坏适应分配算法。扫描整个未分配区,选择能满足用户进程要求的最大分区分配存储空间。
5. 快速适应分配算法。为经常用到的长度的空闲区设立单独的空闲区链表。
## 14 分页存储管理
### 基本概念:
1. 页面。 进程逻辑地址空间分成大小相等的区,每个区称为页面或页。(页面的本质是逻辑地址空间)
2. 页框。 又称页帧。内存物理地址空间分成大小相等的区,其大小和页面大小相等,每个区就是一个页框。(页框的本质是物理地址空间)
3. 逻辑地址。分页存储器的逻辑地址由页号和页内偏移两部分组成。
![](image/2021-03-29-19-48-17.png)
4. 内存页框表。页框表的表项给出物理块使用情况0为空闲1为占用。
5. 页表。页表是操作系统为进程建立的,是程序页面和内存页框的对照表,页表的每一栏指明程序中的某一页面和分得的页框之间的关系。
### 分页存储管理的地址转换
![](image/2021-03-29-19-49-37.png)
* 翻译快表:也称转换后援缓冲(Translation Look_aside Buffer, TLB)。用来存放进程最近访问的部分页表项。(翻译快表之于页表类似于Cache之于存储器)
* 二级页表:把整个页表分割成许多小页表,每个称为页表页,每个页表页含有若干个页表表项。页表页允许分散对应不连续的页框。为了找到页表页,应建立地址索引,称为页目录表,其表项指出页表页起始地址。
* 二级页表实现逻辑地址到物理地址转换的过程: 由硬件“页目录表基址寄存器”指出当前运行进程的页目录表的内存起始地址,加上“页目录位移”作为索引,可找到页表页在内存的起始地址,再以“页目录位移”作为索引,找到页表页在内存的起始位置,再以“页表页位移”作为索引,找到页表页的表项,此表项中包含一个页面对应的页框号,由页框号和页内偏移便可生成物理地址。
![](image/2021-03-29-19-53-09.png)
* 类比于书的目录,找某一段内容的时候,先在目录上找到对应的章节,再在对应的章节下面找具体的知识点。比如,我要在《操作系统原理》中查“多级页表”。首先我知道它是在存储管理一章的,于是就找到了“第四章 存储管理”(类似于找到了页目录表)。 然后在第四章下面找“多级页表”(类似于在页目录表下面找具体的页表页)。最后找到“多级页表”对应的页码(类似于在页表页中找到其对应的页框)。最后查阅对应的章节页码(类似于读取对应页框的数据)。
## 15 分段存储管理
### 分段和分页的比较:
1. 分段是信息的逻辑单位,由源程序的逻辑结构及含义所决定,是用户可见的,段长由用户根据需要来确定,段起始地址可以从任何内存地址开始。引入的目的是满足用户模块化程序设计的需要。
2. 分页是信息的物理单位,与源程序的逻辑无关,是用户不可见的,页长由系统(硬件)决定,页面只能从页大小的整数倍地址开始。引入目的是实现离散分配并提高内存利用率。
## 16 虚拟存储管理
### 虚拟存储管理的基本思路:
![](image/2021-03-29-19-54-14.png)
* 把磁盘空间当做内存的一部分,进程的程序和数据部分放在内存中,部分放在磁盘上。程序运行时,它执行的指令或访问的数据在哪里由存储管理负责判断,并针对情况采取响应的措施。
### 请求分页虚存管理
* 将进程信息副本存放在外存中,当它被调度投入运行时,程序和数据没有全部装进内存,仅装入当前使用页面,进程执行过程中访问到不在内存的页面时,产生缺页异常,再由系统自动调入。
### 全局页面替换策略
> 页面替换算法的作用范围是整个系统,不考虑进程的属主:
1. 最佳页面替换算法(Optimal Replacement OPT)。 淘汰不再访问的页或者距现在最长时间后才访问的页。
2. 先进先出页面替换算法(First in First Out Replacement FIFO)。淘汰在内存中驻留时间最长的页。
3. 最近最少使用页面替换算法(Least Recently Used Replacement, LRU)。 淘汰最近一段时间内最久未被使用的页面。
4. 第二次机会页面替换算法(Second Chance Replacement SCR)。 首先检查FIFO页面队列中的队首这是最早进入内存的页面如果其“引用位”为0那么它最早进入且未被引用此页被淘汰。如果其“引用位”为1说明虽然它最早进内存但最近仍在使用于是将“引用位”清零并把这个页面移到队尾把它看做新调入的页面再给它一次机会。
5. 时钟页面替换算法(Clock Policy Replacement Clock)。与SCR算法思路一致。只是用循环队列来构造页面队列队列指针指向可能被淘汰的页面。如果队列指针指向的页的“引用位”为1则将其置为0同时队列指针指向下一个页。
### 局部页面替换算法
> 页面替换算法的作用局限于进程自身,要为进程维护称为工作集的一组页面:
1. 局部最佳页面替换算法(Local Minimum Replacement, MIN)。 在t时刻时若页面P在未来(t, t+delta)时间段内未被引用,则它被淘汰。
2. 工作集置换算法。 在t时刻时若页面P在未来(t-delta, t)时间段内未被引用,则它被淘汰。
3. 模拟工作集替换算法。
4. 缺页频率替换算法。
## 17 请求段页式虚拟内存管理
* 虚地址以程序的逻辑结构划分为段。
* 实地址划分为位置固定、大小相等的页框(块)。
* 逻辑地址分为三个部分段号s、段内页号p、页内位移d。对于用户而言段式虚拟地址应该由段号s和段内位移d组成操作系统内部自动把d解释成段内页号p和页内位移号d。
## 18 I/O硬件原理:I/O控制方式
### 轮询方式
* 又称程序直接控制方式。使用查询指令测试设备控制器的忙闲状态位,确定内存和设备是否能能交换数据。(@杨领well注:所谓轮询,就好比,老湿依次问每一个童鞋:“有问题没?”, 如果没问题,就继续问下一个童鞋。如果这个童鞋有问题,这个老湿就停下了解决这个问题。然后又继续询问下一个童鞋。)
### 中断方式
* 要求CPU和设备控制器及设备之间存在中断请求线设备控制器的状态寄存器有相应的中断允许位。
1. 进程发出启动I/O指令。
2. 设备控制器检查状态寄存器的内容执行相应的I/O操作一旦传输完成设备控制器通过中断请求线发出I/O中断信号。
3. CPU收到并响应I/O中断后转向设备的I/O中断处理程序执行。
4. 中断处理程序执行数据读取操作将I/O缓冲寄存器的内容写入内存。操作结束后退出中断程序恢复之前的状态。
5. 执行中断前之前运行的进程。(类似于老湿在上面讲课,有童鞋问问题时,老湿就记录下自己讲到的位置,然后取回答童鞋的问题,回答完之后,又回到刚刚讲课的地方继续讲课)
### DMA(Direct Memory Access, 直接存储器存取)方式
* 内存和设备之间有一条数据通路成块的传输数据无须CPU干9预实际数据传输操作由DMA直接完成。
### 通道方式
* CPU在执行主程序时遇到I/O请求启动在指定通道上选址的设备一旦启动成功通道开始控制设备进行操作这时CPU就可以执行其他任务并与通道并行工作直到I/O操作完成当通道发出I/O操作结束中断时处理器才响应并停止当前工作转而处理I/O操作结束时间。
## 19 I/O软件原理
* I/O中断处理程序 通常是设备驱动程序的组成部分之一。检查设备状态寄存器内容判断产生中断原因根据I/O操作的完成情况进行相应处理。若数据传输有错应向上层软件报告设备出错信息实施重新执行若正常结束应唤醒等待传输的进程使其转换为就绪态若有等待传输的I/O命令应通知相关软件启动下一个I/O请求。
* I/O设备驱动程序设备驱动程序是设备专有的。把用户提交的逻辑I/O请求转化为物理I/O的启动和执行。同时监督设备是否正确执行管理数据缓冲区进行必要的纠错处理。
* 独立于设备的I/O软件
* 用户空间的I/O软件
## 20 缓冲技术
> 这里是指cache。缓存。而不是缓冲buffer。cache的本命缓冲存储器。本质上是一个存储器。
### 缓冲技术的基本思想
* 当进程执行写操作输出数据时,先向系统申请一个输出缓冲区,然后将数据送至缓冲区,若是顺序写请求,则不断地把数据填入缓冲区,直至装满为止,此后进程可以继续计算,同时,系统将缓冲区的内容写在设备上。当进程执行读操作输入数据时,先向系统申请一个输入缓冲区,系统将设备上的一条物理记录读至缓冲区,根据要求把当前所需要的逻辑记录从缓冲区中选出并传送给进程。
### 单缓冲
* 是最简单的缓冲技术每当有I/O请求时操作系统就在内存的系统区中开设一个缓冲区。不允许多个进程同时对一个缓冲器操作。
![](image/2021-03-29-20-01-44.png)
* 双缓冲: CPU可把输出到设备的数据放入其中一个缓冲器(区)、让设备慢慢处理;然后,它又可以从另一个为终端设置的缓冲器(区)中读取所需要的输入数据。
![](image/2021-03-29-20-01-54.png)
* 多缓冲: 是把多个缓冲区连接起来组成两部分,一部分专门用于输入,另一部分专门用于输出的缓冲结构。
![](image/2021-03-29-20-02-02.png)
## 21 驱动调度技术
### 磁盘的物理结构:
![](image/2021-03-29-20-02-49.png)
* 磁盘包括多个盘面,每个盘面有一个读写磁头,所有的磁头都固定在唯一的移动臂上同时移动。一个盘面上的读写磁头的轨迹称为磁道,读写磁头下的所有磁道形成柱面,一个磁道又可以划分为多个扇区。在磁盘上定位某个物理记录需要知道其柱面号、磁头号以及扇区号。定位物理记录时,磁头到达指定扇区的时间称为查找时间, 选择磁头号并旋转至指定扇区的时间称为搜索延迟。
### 磁道(柱面)的搜索定位算法:
1. 先来先服务算法(First Come First Server algorithm, FCFS)。
2. 最短查找时间优先算法: 总是执行查找时间最短的请求。
3. 扫描算法: 移动臂来回的扫过所有柱面扫描处遇到I/O请求便进行处理。
4. 分步扫描算法: 将I/O请求分为长度为N的子队列按FIFO算法依次处理每个队列而每个子队列采用扫描算法处理完一个后再服务下一个队列。
5. 电梯调度算法: 又称LOOK算法。
![](image/2021-03-29-20-03-28.png)
### 磁头号由外向内递增。
6. 循环扫描算法: 移动臂总是从0号柱面至最大号柱面顺序扫描然后直接返回0号柱面重复进行归途中不提供服务(而扫描算法归途是要提供服务的)。
## 22 设备独立性
* 设备独立性: 用户通常不指定物理设备,而是指定逻辑设备,使得用户作业和物理设备分离开来,再通过其他途径建立逻辑设备和物理设备之间的映射,设备的这种特性称为设备独立性。
## 23 虚拟设备
### 外部设备同时联机操作(Simultaneous Peripheral Operations On Line, SPPPLing)
1. 预输入程序。 控制信息从输入设备至输入井,填写预输入表以便在作业执行过程中要求输入信息时可以随时找到其存放位置。
2. 井管理程序。 作业执行过程中要求启动某台设备进程I/O操作时作业控制程序截获这个要求并调用井管理程序控制从相应输入井读取信息或将信息送至输出井。
3. 缓输出程序。 当处理器空闲时,操作系统调用缓输出程序执行缓输出,它查看缓输出表是否有输出打印的文件,文件打印前还可能组织作业或文件标题,也可能对从输出井中读出的信息进行格式加工。
![](image/2021-03-29-20-15-32.png)
## 24 文件逻辑结构
### 文件的逻辑结构的两种形式:
1. 流式文件。 一种无结构的文件,文件内的数据不再组成记录,只是一串顺序的信息集合,称为字节流文件。
2. 记录式文件。 一种有结构的文件,包含若干逻辑记录,逻辑记录是文件中按信息在逻辑上独立含义所划分的信息单位。
## 25 文件物理结构
* 文件物理结构: 文件的物理结构和组织是指逻辑文件在物理存储空间中的存放方法和组织关系。
* 常见组织方式:顺序文件、连接文件、直接文件和索引文件。
## 26 Q&A
### 1 什么是操作系统?操作系统在计算机系统中的主要作用是什么?
* 定义: 操作系统尚无严格的定义。 一般可把操作系统定义为: 管理系统资源、控制程序执行、改善人机界面、提供各种服务,并合理组织计算机工作流程和为用户方便有效地使用计算机提供良好的运行环境的一种软件系统。
* 作用:
1. 服务用户。 操作系统作为用户接口和公共服务程序。
2. 进程交互。 操作系统作为进程执行的控制者和协调者。
3. 系统实现。 操作系统作为扩展机或虚拟机。
4. 资源管理。 操作系统作为资源的管理者和控制者。
### 2 什么是多道程序设计?多道程序设计有什么特点?
* 多道程序设计(multiprogramming) 允许多个作业(程序)同时进入计算机系统的内存并启动交替计算的方法。
* 多道程序设计的特点: 从宏观上看是并行的,从微观上看是串行的。
### 3 计算机操作系统为什么引入进程?
* 刻画程序的并发性。
* 解决资源的共享性。
### 4 在分时系统中,什么是响应时间?它与哪些因素有关?
* 从交互式进程提交一个请求(命令)直到获得响应之间的时间间隔称为响应时间。
* 影响分时操作系统的响应时间的因素很多CPU的处理速度、联机终端的数目、所用时间片的大小、系统调度开销和对换信息量的多少等。
### 5 解释并发性与并行性
* 计算机操作系统中把并行性和并发性明显区分开,主要是从微观的角度来说的,具体是指进程的并行性(多处理机的情况下,多个进程同时运行)和并发性(单处理机的情况下,多个进程在同一时间间隔运行的)。
* 并行性是指硬件的并行性,两个或多个事件在同一时刻发生。
* 并发性是指进程的并发性,两个或多个事件在同一时间段内发生。
### 6 试述存储管理的基本功能。
* 存储分配。
* 地址映射。
* 存储保护。
* 存储共享。
* 存储扩充。
### 7 何谓地址转换(重定向)?哪些方法可以实现地址转换?
* 地址重定位: 又称地址转换,地址映射。 可执行程序逻辑地址转换(绑定)为物理地址的过程。
* 实现方法:
1. 静态地址重定位。 由装载程序实现装载代码模块的加载和地址转换,把它装入分配给进程的内存指定区域,其中的所有逻辑地址修改成内存物理地址。
2. 动态地址重定位。由装载程序实现装载代码模块的加载和地址转换,把它装入分配给进程的内存指定区域,但对链接程序处理过的应用程序的逻辑地址则不做任何修改,程序内存起始地址被置于硬件专用寄存器 —— 重定位寄存器。程序执行过程中每当CPU引用内存地址(访问程序和数据)时,由硬件截取此逻辑地址,并在它被发送到内存之前加上重定位寄存器的值,以便实现地址转换。
![](image/2021-03-29-20-16-28.png)
3. 运行时链接地址重定位
### 8 程序链接的三种方式:
1. 静态链接。在程序装载到内存和运行前,就已将它的所有目标模块及所需要的库函数进行链接和装配成一个完整的可执行程序且此后不可拆分。
2. 动态链接。在程序装入内存前并未事先进行程序各目标模块的链接,而是在程序装载时一边装载一边链接,生成一个可执行文件。
3. 运行时链接。 将某些目标模块或库函数的链接 推迟到执行时才进行。
### 9 什么是文件的共享?介绍文件共享的分类和实现思想。
* 文件共享: 不同进程共同使用同一个文件。
* 文件共享的分类:
1. 静态共享。 两个或多个进程通过文件链接(一个文件同时属于多个目录,但实际上仅有一处物理存储)达到共享同一个文件的目的,无论进程是否运行,其文件的链接关系都是存在的,因此称为静态共享。
2. 动态共享。 系统不同的应用程序或同一用户的不同进程并发地访问同一文件,这种共享关系只有当进程存在时才可能出现,一旦进程消亡,其共享关系也就随之消失。
3. 符号链接共享。
### 10 进程和线程以及它们的区别
1. 进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现操作系统的并发。
2. 线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发。
3. 一个程序至少有一个进程,一个进程至少有一个线程,线程依赖进程的存在。
4. 进程执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
### 11 进程间的通信的几种方式
1. 管道pipe及命名管道named pipe管道可用于具有亲缘关系的父子进程间通信命名管道除了具有管道所具有功能外还允许无亲缘关系进程的通信。
2. 信号signal是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
3. 消息队列:是消息的链接表,克服上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
4. 共享内存:最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
5. 信号量:主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。
6. 套接字:这是一致性更为一般进程间通信机制,它可用网络中不同机器之间进程间通信,应用非常广泛。
### 12 线程同步的方式
1. 互斥量Synchronized/lock采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
2. 信号量Semaphore它允许同一时刻多个线程访问同一资源,但需要控制同一时刻访问此资源的最大线程数。
3. 事件信号Wait/Notify通过通知操作的方式来保存多线程同步,还可以方便的实现多线程优先级比较操作。
### 13 什么是死锁?死锁产生的条件?
* 死锁的概念:
* 在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗讲,就是两个或者多个进程无限期的阻塞、相互等待的一种状态。
* 死锁产生的四个必要条件
* 互斥:至少有一个资源必须属于非共享模式,即一次只能被一个进程使用;若其他申请使用该资源,那么申请进程必须等到该资源被释放为止;
* 占用并等待:一个进程必须占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有;
* 非抢占;进程不能被抢占,即资源只能被进程在完成任务后资源释放。
* 循环等待:若干进程之间形成一种头尾相接的环形等待资源关系。
* 死锁处理的基本策略和常用方法:解决死锁的基本方法主要有:预防死锁,避免死锁,检测死锁,解除死锁等思想。
* 死锁预防。死锁预防基本思想:只要确保死锁发生的四个必要条件至少有一个不成立,就能预防死锁。
* 打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这是由资源本身属性所决定的,因此,这种办法通常并无实用价值。
* 打破占用并等待条件:可以实行资源预先分配策略(进程在运行前一次性向系统申请它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,才一次性将所申请资源全部分配给该线程)或者只允许进程在没有占用资源时才可以申请资源(一个进程可申请一些资源并使用它们,但是在当前进程申请更多资源之前,它必须全部释放当前所占有的资源)。但是这种策略也存在一些缺点:在很多情况下,无法预知一个进程执行前所需的全部资源,因为进程是动态执行的,不可预知的;同时,会降低资源利用率,导致降低了进程的并发性。
* 打破非枪占条件:允许进程强行从占有者哪里夺取某些资源,也就是说,但一个进程占有了一部分资源,在其申请新的资源且得不到满足时,它必须释放所有占有的资源以便让其它线程使用。这种预防死锁的方式实现起来困难,会降低系统性能。
* 打破循环等待条件:实行资源有序分配策略。对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源,这样就不回产生环路,预防死锁的发生。
* 死锁避免:死锁避免的基本思想是动态检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。资源分配图算法和银行家算法是两种经典的死锁避免的算法,其可以确保系统始终处于安全状态。其中,资源分配图算法应用场景为每种资源类型只有一个实例(申请边,分配边,需求边,不形成环才允许分配),而银行家算法应用于每种资源类型可以有多个实例的场景。
* 死锁解除:死锁解除的两种常用方法为进程终止和资源抢占。所谓进程终止是指简单地终止一个或多个进程以打破循环等待,分终止所有死锁进程、一次只终止一个进程直到取消死锁循环为止。所谓资源抢占就是指从一个或多个死锁进程哪里抢占一个或多个资源,此时需考虑三问题
* 选择一个牺牲品。
* 回滚到安全状态
* 饥饿(在代价因素中加上回滚次数,回归的越多则越不可能作为牺牲品,避免一个进程总是被回滚)。
### 14 进程有哪几种状态?
* 就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源;
* 运行状态占用处理机资源运行处于此状态的进程数小于等于CPU数
* 阻塞状态:进程等待某种条件,在条件满足之前无法执行;
 ![](image/2021-03-29-20-39-11.png)
### 15 线程有几种状态?
* 在 Java虚拟机 中,线程从最初的创建到最终的消亡,要经历若干个状态:创建(new)、就绪(runnable/start)、运行(running)、阻塞(blocked)、等待(waiting)、时间等待(time waiting) 和 消亡(dead/terminated)。
![](image/2021-03-29-20-39-28.png)
### 16 分页和分段有什么区别(内存管理)?
* 段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中将程序的地址空间划分为若干段segment如代码段数据段堆栈段这样每个进程有一个二维地址空间相互独立互不干扰。段式管理的优点是没有内碎片因为段大小可变改变段大小来消除内碎片。但段换入换出时会产生外碎片比如4k的段换5k的段会产生1k的外碎片
* 页式存储管理方案是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中将程序的逻辑地址划分为固定大小的页page而物理内存划分为同样大小的帧程序加载时可以将任意一页放入内存中任意一个帧这些帧不必连续从而实现了离散分离。页式存储管理的优点是没有外碎片因为页的大小固定但会产生内碎片一个页可能填充不满
* 两者不同:
* 目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息;
* 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定;
* 地址空间不同:段向用户提供二维地址空间;页向用户提供的是一维地址空间;
* 信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制;
* 内存碎片页式存储管理的优点是没有外碎片因为页的大小固定但会产生内碎片一个页可能填充不满而段式管理的优点是没有内碎片因为段大小可变改变段大小来消除内碎片。但段换入换出时会产生外碎片比如4k的段换5k的段会产生1k的外碎片
### 17 操作系统中进程调度策略有哪几种?
1. FCFS(先来先服务,队列实现,非抢占的)先请求CPU的进程先分配到CPU。
2. SJF(最短作业优先调度算法)平均等待时间最短但难以知道下一个CPU区间长度。
3. 优先级调度算法优先级越高越先分配到CPU相同优先级先到先服务存在的主要问题是低优先级进程无穷等待CPU会导致无穷阻塞或饥饿解决方案老化。
4. 时间片轮转调度算法(可抢占的)队列中没有进程被分配超过一个时间片的CPU时间除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片那么该进程就被抢占并放回就绪队列。
5. 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。
6. 多级反馈队列调度算法与多级队列调度算法相比其允许进程在队列之间移动若进程使用过多CPU时间那么它会被转移到更低的优先级队列在较低优先级队列等待时间过长的进程会被转移到更高优先级队列以防止饥饿发生。
### 18 说一说进程同步有哪几种机制?
* 原子操作、信号量机制、自旋锁管程、会合、分布式系统
### 19 什么是虚拟内存?
* 内存发展历程没有内存抽象(单进程,除去操作系统所用的内存之外,全部给用户程序使用) —>有内存抽象(多进程,进程独立的地址空间,交换技术(内存大小不可能容纳下所有并发执行的进程)—> 连续内存分配(固定大小分区(多道程序的程度受限),可变分区(首次适应,最佳适应,最差适应),碎片)—> 不连续内存分配(分段,分页,段页式,虚拟内存)
* 虚拟内存。虚拟内存允许执行进程不必完全在内存中。虚拟内存的基本思想是:每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上,如图所示。
![](image/2021-03-29-20-41-37.png)
* 由图可以看出虚拟内存实际上可以比物理内存大。当访问虚拟内存时会访问MMU内存管理单元去匹配对应的物理地址比如图5的012。如果虚拟内存的页并不存在于物理内存中如图5的3,4会产生缺页中断从磁盘中取得缺的页放入内存如果内存已满还会根据某种算法将磁盘中的页换出。
* 页面置换算法
* FIFO先进先出算法在操作系统中经常被用到比如作业调度主要实现简单很容易想到
* LRULeast recently use最近最少使用算法根据使用时间到现在的长短来判断
* LFULeast frequently use最少使用次数算法根据使用次数来判断
* OPTOptimal replacement最优置换算法理论的最优理论就是要保证置换出去的是不再被使用的页或者是在实际内存中最晚使用的算法。
* 虚拟内存应用及优点
* 虚拟内存很适合在多道程序设计系统中使用许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时可以把CPU交给另一个进程使用。好处
* 在内存中可以保留多个进程,系统并发度提高。
* 解除了用户与内存之间的紧密约束,进程可以比内存的全部空间还大
### 20 颠簸
* 颠簸本质上是指频繁的页调度行为,具体来讲,进程发生缺页中断,这时,必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此,会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。
* 解决策略包括:
* 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题;
* 如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量;
* 否则,还剩下两个办法:终止该进程或增加物理内存容量。
### 21 局部性原理
* 时间上的局部性:最近被访问的页在不久的将来还会被访问;
* 空间上的局部性:内存中被访问的页周围的页也很可能被访问。

View File

@@ -0,0 +1,337 @@
# Socket
<!-- GFM-TOC -->
* [Socket](#socket)
* [一、I/O 模型](#一io-模型)
* [阻塞式 I/O](#阻塞式-io)
* [非阻塞式 I/O](#非阻塞式-io)
* [I/O 复用](#io-复用)
* [信号驱动 I/O](#信号驱动-io)
* [异步 I/O](#异步-io)
* [五大 I/O 模型比较](#五大-io-模型比较)
* [二、I/O 复用](#二io-复用)
* [select](#select)
* [poll](#poll)
* [比较](#比较)
* [epoll](#epoll)
* [工作模式](#工作模式)
* [应用场景](#应用场景)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
## 一、I/O 模型
一个输入操作通常包括两个阶段:
- 等待数据准备好
- 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
Unix 有五种 I/O 模型:
- 阻塞式 I/O
- 非阻塞式 I/O
- I/O 复用select 和 poll
- 信号驱动式 I/OSIGIO
- 异步 I/OAIO
### 阻塞式 I/O
应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。
应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。
下图中recvfrom() 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。
```c
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
```
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492928416812_4.png"/> </div><br>
### 非阻塞式 I/O
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成这种方式称为轮询polling
由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492929000361_5.png"/> </div><br>
### I/O 复用
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接那么就需要创建相同数量的线程。相比于多进程和多线程技术I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492929444818_6.png"/> </div><br>
### 信号驱动 I/O
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。
相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492929553651_7.png"/> </div><br>
### 异步 I/O
应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492930243286_8.png"/> </div><br>
### 五大 I/O 模型比较
- 同步 I/O将数据从内核缓冲区复制到应用进程缓冲区的阶段第二阶段应用进程会阻塞。
- 异步 I/O第二阶段应用进程不会阻塞。
同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一个阶段。
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1492928105791_3.png"/> </div><br>
## 二、I/O 复用
select/poll/epoll 都是 I/O 多路复用的具体实现select 出现的最早,之后是 poll再是 epoll。
### select
```c
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
```
select 允许应用程序监视一组文件描述符,等待一个或者多个描述符成为就绪状态,从而完成 I/O 操作。
- fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义,所以只能监听少于 FD_SETSIZE 数量的描述符。有三种类型的描述符类型readset、writeset、exceptset分别对应读、写、异常条件的描述符集合。
- timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。
- 成功调用返回结果大于 0出错返回结果为 -1超时返回结果为 0。
```c
fd_set fd_in, fd_out;
struct timeval tv;
// Reset the sets
FD_ZERO( &fd_in );
FD_ZERO( &fd_out );
// Monitor sock1 for input events
FD_SET( sock1, &fd_in );
// Monitor sock2 for output events
FD_SET( sock2, &fd_out );
// Find out which socket has the largest numeric value as select requires it
int largest_sock = sock1 > sock2 ? sock1 : sock2;
// Wait up to 10 seconds
tv.tv_sec = 10;
tv.tv_usec = 0;
// Call the select
int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv );
// Check if select actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
if ( FD_ISSET( sock1, &fd_in ) )
// input event on sock1
if ( FD_ISSET( sock2, &fd_out ) )
// output event on sock2
}
```
### poll
```c
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
```
poll 的功能与 select 类似,也是等待一组描述符中的一个成为就绪状态。
poll 中的描述符是 pollfd 类型的数组pollfd 的定义如下:
```c
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
```
```c
// The structure for two events
struct pollfd fds[2];
// Monitor sock1 for input
fds[0].fd = sock1;
fds[0].events = POLLIN;
// Monitor sock2 for output
fds[1].fd = sock2;
fds[1].events = POLLOUT;
// Wait 10 seconds
int ret = poll( &fds, 2, 10000 );
// Check if poll actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
// If we detect the event, zero it out so we can reuse the structure
if ( fds[0].revents & POLLIN )
fds[0].revents = 0;
// input event on sock1
if ( fds[1].revents & POLLOUT )
fds[1].revents = 0;
// output event on sock2
}
```
### 比较
#### 1. 功能
select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。
- select 会修改描述符,而 poll 不会;
- select 的描述符类型使用数组实现FD_SETSIZE 大小默认为 1024因此默认只能监听少于 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 没有描述符数量的限制;
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
- 如果一个线程对某个描述符调用了 select 或者 poll另一个线程关闭了该描述符会导致调用结果不确定。
#### 2. 速度
select 和 poll 速度都比较慢,每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。
#### 3. 可移植性
几乎所有的系统都支持 select但是只有比较新的系统支持 poll。
### epoll
```c
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
```
epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。
从上面的描述可以看出epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。
epoll 仅适用于 Linux OS。
epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。
epoll 对多线程编程更有友好,一个线程调用了 epoll_wait() 另一个线程关闭了同一个描述符也不会产生像 select 和 poll 的不确定情况。
```c
// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets.
// The function argument is ignored (it was not before, but now it is), so put your favorite number here
int pollingfd = epoll_create( 0xCAFE );
if ( pollingfd < 0 )
// report error
// Initialize the epoll structure in case more members are added in future
struct epoll_event ev = { 0 };
// Associate the connection class instance with the event. You can associate anything
// you want, epoll does not use this information. We store a connection class pointer, pConnection1
ev.data.ptr = pConnection1;
// Monitor for input, and do not automatically rearm the descriptor after the event
ev.events = EPOLLIN | EPOLLONESHOT;
// Add the descriptor into the monitoring list. We can do it even if another thread is
// waiting in epoll_wait - the descriptor will be properly added
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, pConnection1->getSocket(), &ev ) != 0 )
// report error
// Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen)
struct epoll_event pevents[ 20 ];
// Wait for 10 seconds, and retrieve less than 20 epoll_event and store them into epoll_event array
int ready = epoll_wait( pollingfd, pevents, 20, 10000 );
// Check if epoll actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
// Check if any events detected
for ( int i = 0; i < ready; i++ )
{
if ( pevents[i].events & EPOLLIN )
{
// Get back our connection pointer
Connection * c = (Connection*) pevents[i].data.ptr;
c->handleReadEvent();
}
}
}
```
### 工作模式
epoll 的描述符事件有两种触发模式LTlevel trigger和 ETedge trigger
#### 1. LT 模式
当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。
#### 2. ET 模式
和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。
很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
### 应用场景
很容易产生一种错觉认为只要用 epoll 就可以了select 和 poll 都已经过时了,其实它们都有各自的使用场景。
#### 1. select 应用场景
select 的 timeout 参数精度为微秒,而 poll 和 epoll 为毫秒,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
select 可移植性更好,几乎被所有主流平台所支持。
#### 2. poll 应用场景
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
#### 3. epoll 应用场景
只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll因为这个应用场景下并不能体现 epoll 的优势。
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
## 参考资料
- Stevens W R, Fenner B, Rudoff A M. UNIX network programming[M]. Addison-Wesley Professional, 2004.
- http://man7.org/linux/man-pages/man2/select.2.html
- http://man7.org/linux/man-pages/man2/poll.2.html
- [Boost application performance using asynchronous I/O](https://www.ibm.com/developerworks/linux/library/l-async/)
- [Synchronous and Asynchronous I/O](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365683(v=vs.85).aspx)
- [Linux IO 模式及 select、poll、epoll 详解](https://segmentfault.com/a/1190000003063859)
- [poll vs select vs event-based](https://daniel.haxx.se/docs/poll-vs-select.html)
- [select / poll / epoll: practical difference for system architects](http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/)
- [Browse the source code of userspace/glibc/sysdeps/unix/sysv/linux/ online](https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/)

View File

@@ -0,0 +1,32 @@
## 寄存器
### 定义
* PSW是Program Status Word的缩写即程序状态字也叫程序状态寄存器可用于OS在管态系统态和目态用户态之间的转换。
* 程序状态寄存器PSW是计算机系统的核心部件——运算器的一部分PSW用来存放两类信息一类是体现当前指令执行结果的各种状态信息称为状态标志如有无借位进位CY位、有无溢出OF位、结果正负SF位、结果是否为零ZF位、奇偶标志位PF位另一类是存放控制信息称为控制状态如允许中断(IF位)跟踪标志TF位方向标志(DF)等。
### 使用
* 程序状态字用来指示处理器状态、控制指令的执行顺序并且保留和指示与运行程序有关的各种信息其主要作用是方便地实现程序状态的保护和恢复。每个正在执行的程序都有一个与其执行相关的PSW而每个处理器都设置一个程序状态字寄存器。一个程序占有处理器执行它的PSW将占有程序状态字寄存器。一般来说程序状态字寄存器包括以下几类内容
* 程序基本状态。包括:(1)程序计数器:指明下一条执行的指令地址;(2)条件码:表示指令执行的结果状态:(3)处理器状态位:指明当前的处理器状态,如目态或管态、运行或等待。
* 中断码。保存程序执行时当前发生的中断事件。
* 中断屏蔽位。指明程序执行中发生中断事件时,是否响应出现的中断事件。
* 由于不同处理器中的控制寄存器组织方式不同,所以在大多数计算机的处理器现场中可能找不到一个称为程序状态字寄存器的具体寄存器,但总是有一组控制与状态寄存器实际上起到这一作用。
## 进程通信
### 进程通信的类型
* 共享存储器系统
* 基于共享数据结构的通信方式
* 生产者和消费者
* 基于共享存储区的通信方式
* 高级通信
* 管道通信系统(pipe)
* 高级通信
* 消息传递系统
* 高级通信
* 方式分类
* 直接通信
* 间接通信
* 客服机–服务器系统

View File

@@ -10,7 +10,9 @@
1. 堆是一颗完全二叉树;
2. 堆中的某个结点的值总是大于等于(最大堆)或小于等于(最小堆)其孩子结点的值。
3. 堆中每个结点的子树都是堆树
3. 堆中每个结点的子树都是堆树
> 因为堆的第三条性质,堆的每一个子树,都是一个堆树。如果从叶节点开始进行一次向上调整。虽然能够保证最大值上浮到根节点。但是却无法保证它是一个堆树。因为进行一次向上调整。根节点的子树也许不是堆树。
![](image/2021-03-13-15-23-14.png)
@@ -74,7 +76,7 @@
> 类似于冒泡的思想,但是是一中更快的冒泡。保证最后能够建立最好的堆。
* 从根节点向叶节点开始,从后往前开始。进行多次上浮操作。
* 从根节点向叶节点开始,从前往后开始。进行多次上浮操作。
* 从叶节点向根节点开始,从后往前开始。进行多次下沉操作。

View File

@@ -143,3 +143,87 @@
|贪心算法|最优子结构性质;贪心选择性质。|
|回溯法|多米诺性质(叶子节点的解一定满足其父节点)。|
|分支限界|多米诺性质;求解最优解或一个可行解。|
## 5 简单对比
## 5.1 暴力破解法Brute Force Paradigm
* 暴力破解法简单直接根据问题声明的定义找到所有可变化的因子Divisor穷举所有可能解决问题的方法逐个尝试。所以根据暴力破解法的定义理论上讲任何问题都可以使用暴力破解法来解决只是在实际应用中算法对时间和空间的需求则无法满足。将暴力破解法应用于数据查找由于查找比较次数与给定目标的规模直接相关所以也称为线性查找Linear Search
### 线性查找有很多典型应用:
* 选择排序Selection Sort
* 冒泡排序Bubble Sort
* 顺序查找Sequential Search
* 暴力字符串匹配Naive String Match
## 5.3 分治法Divide and Conquer Paradigm
* 分治法Divide-and-Conquer即 "分而治之",是将原问题划分成 n 个规模较小而结构与原问题相似的子问题,递归地解决这些问题,然后再合并其结果,以得到原问题的解。
* 当我们遇到一个大问题时,总是习惯把问题的规模变小,这样便于分析讨论。这些规模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是一样的,本质上它还是同一个问题,规模变小后的问题其实是原问题的子问题。
### 分治模式在每一层上都有三个步骤:
* 分解Divide将原问题分解成一系列与原问题同质的子问题
* 解决Conquer递归地解决各个子问题。若子问题足够小则直接求解
* 合并Combine将子问题的结果合并成原问题的解。
### 分治法所能解决的问题一般具有以下几个特征:
* 可以将问题分解为若干个规模较小的相同问题;
* 问题的规模缩小到一定程度后就可以很容易地解决;
* 问题分解出的子问题的解可以合并为该问题的解;
* 问题所分解出的各个子问题是相互独立的,即子问题之间不再包含公共的孙问题。
> 符合 1,2,3 条特征是使用分治法的关键,而特征 4 将涉及到分治法的效率问题。而如果不符合 3,4 特征的问题可以尝试考虑使用动态规划或贪心算法来解决。
### 分治法的典型应用:
* 合并排序Merge Sort
* 快速排序Quick Sort
* 二分查找Binary Search
## 5.3 动态规划法Dynamic Programming Paradigm
* 动态规划的过程可以描述为多阶段最优化解决问题的过程,每一次的决策依赖于当前的状态,随即又引起状态的转移,以此类推在变化的状态中产生出决策序列。
* 动态规划算法通常基于一个递推公式及一个或多个初始状态,当前子问题的解将由上一次子问题的解推出。使用动态规划来解题通常只需要多项式时间复杂度,所以会比回溯法、暴力法等要快许多。
### 动态规划方法中的关键词包括:
* 阶段Stage
* 状态State
* 决策Decision
* 递推关系Recurrent Relation
### 动态规划对比
* 动态规划首先将待求解的问题分解为若干个子问题(阶段),按顺序求解子问题。前一子问题的解为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。以此类推解决各子问题,最后一个子问题的解就是初始问题的解。
* 动态规划与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子问题的求解是建立在上一个子问题的解的基础上,进行进一步的求解)。
### 动态规划所能解决的问题一般具有以下几个特征:
* 最优化原理Mathematical Optimization如果问题的最优解所包含的子问题的解也是最优的就称该问题具有最优子结构Optimal Substructure即满足最优化原理。
* 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
* 有重叠子问题Overlapping Subproblems即子问题之间是不独立的一个子问题在下一阶段决策中可能被多次使用到。
> 特征 3 并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势。
### 动态规划的基本步骤:
* 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
* 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。状态的选择要满足无后效性。
* 确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
* 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
## 5.4 贪心算法Greedy Paradigm
* 所谓贪心算法是指,在对问题求解时,总是做出在当前看来最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅是在某种意义上的局部最优解。
* 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。贪心策略适用的前提是:局部最优策略能导致产生全局最优解。所以实际上,贪心算法适用的情况很少。
* 贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
### 贪心算法的基本思路:
* 建立数学模型来描述问题。
* 把求解的问题分成若干个子问题。
* 对每一子问题求解,得到子问题的局部最优解。
* 把子问题的解局部最优解合成原来问题的一个解。
## 5.5 回溯法Backtracking Paradigm
* 回溯法描述了一种选优搜索过程,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先的选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的方法称为回溯法,而满足回溯条件的某个状态的点称为 "回溯点"。
* 回溯法可以理解为隐式的深度优先搜索算法。其在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根节点出发深度探索解空间树。当探索到某一节点时,要先判断该节点是否包含问题的解,如果包含,就从该节点出发继续探索下去,如果该节点不包含问题的解,则逐层向其祖先节点回溯。
* 许多复杂的,规模较大的问题都可以使用回溯法,有 "通用解题方法" 的美称。
### 回溯法的一般步骤:
* 针对给定问题,确定问题的解空间,问题的解空间应至少包含问题的一个(最优)解;
* 确定节点的扩展搜索规则;
* 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索;
## 5.6 分支限界法Branch and Bound Paradigm
* 类似于回溯法,分支限界法也是一种在问题的解空间树上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
* 所谓 "分支" 就是采用广度优先搜索算法,依次搜索节点的所有分支,抛弃不满足约束条件的节点,然后从余下的节点中选择一个节点作为下一个搜索节点继续搜索。
* 由于求解目标不同,导致分支限界法与回溯法在解空间树上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。
* 分支限界法的搜索策略是:在扩展节点处,先生成其所有的儿子节点(分支),然后再从当前的活节点表中选择下一个扩展节点。为了有效地选择下一扩展节点,以加速搜索的进程,在每一活节点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活节点表中选择一个最有利的节点作为扩展节点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。

View File

@@ -70,6 +70,9 @@ T(n)=\begin{cases}
\end{cases}
$$
### 递归的空间复杂度
* 当递归过深时栈空间会被耗尽这时就无法开辟新的函数会报出stack overflow这样的错误。
* 所以,在考虑空间复杂度时,递归函数的深度也是要考虑进去的。
## 2 递归的分类
### 直接递归和间接递归
* 直接递归,是指函数自己调用自己的情况。

View File

@@ -1,102 +1,13 @@
# 蛮力法
## 1 蛮力法概述
## 0 蛮力法概述
* 蛮力法是一种简单直接地解决问题的方法,常常直接 基于问题的描述和所涉及的概念定义。
## 2 排序问题
(主要描述解决问题的步骤)
### 理解问题
* 问题给定一个可排序的n个元素序列数字、字符或字符串对它们按照非降序方式重新排列。
### 选择策略
* 思想首先扫描整个序列找到其中一个最小元素然后和第一个元素交换将最小元素归位。然后从第二个元素开始扫描序列找到后n-1个元素中的一个最小元素然后和第二个元素交换将第二小元素归位。进行n-1遍扫描之后排序完成。
### 算法设计
* 算法 selectSort(A[n])
```
//用选择法对给定数组排序
//输入一个可排序数组A[0..n-1]
//输出升序排序的数组A[0..n-1]
for i←0 to n-2 do
min←i
for j=i+1 to n-1 do
if A[j] < A[min] min←j
swap A[i] and A[min]
```
### 正确性证明
### 算法分析
* 输入规模序列元素个数n
* 基本操作比较次数A[j] < A[min]
* 影响操作执行的其他因素n
* 构建基本操作的求和表达式:利用求和公式分析算法的时间复杂度:
![](image/排序算法.png)
### 程序设计
## 3 顺序查找问题
(主要是分析解决问题的步骤)
### 理解问题
* 思想:查找键与表中元素从头至尾逐个比较。
* 结果:找到 或 失败
* 限位器:把查找键添加到列表末尾—— 一定成功,避免每次循环时对检查是否越界(边界检查)
* 选择策略
### 算法设计
![](image/蛮力法-顺序查找.png)
### 正确性证明
### 算法分析
* 最佳效率Tbest (n) = 1
* 最差效率Tworst(n) = n + 1
* 问:为何定义 A 数组为 n+1 维?答:有一个位置放限位器
* 问:若输入有序,算法可改进?答:遇到 ≤ 或 ≥ 查找键元素,立即停止查找。
### 程序设计
## 4 字符串匹配问题
### 理解问题
* 问题给定一个n个字符组成的串称为文本一个mm≤n个字符组成的串称为模式从文本中寻找匹配模式的子串。
### 选择策略
* 思想将模式对准文本的前m个字符然后从左到右匹配每一对相应的字符若遇到一对不匹配字符模式向右移一位重新开始匹配若m对字符全部匹配算法可以停止。注意在文本中最后一轮子串匹配的起始位置是n-m假设文本的下标从0到n-1
### 算法设计
* 算法 bruteForceStringMatch(T[0..n-1],P[0..m-1])
```
//蛮力字符串匹配算法实现
//输入1一个n个字符的数组T[0..n-1]代表一段文本
//输入2一个m个字符的数组P[0..m-1]代表一个模式
//输出:若查找成功,返回文本第一个匹配子串中的第一个字符的位置,否则返回-1
for i←0 to n-m do
j←0
while j<m and P[j]=T[i+j]
j←j+1
if j=m return i
return -1
```
## 5 最近对问题
### 理解问题
* 找出一个包含n个点的集合中距离最近的两个点。
### 选择策略
* 分别计算每一点对之间的距离然后从中找出距离最小的那一对。为了避免同一点对计算两次可以只考虑i<j的点对(Pi, Pj)
### 算法设计
* 算法 bruteForceClosesPoints(P)
```
//蛮力法求解平面中距离最近的两点
//输入一个n(n≥2)个点的列表PP1=(x1, y1)Pn=(xn, yn)
//输出:两个最近点的下标
dmin←∞
for i←0 to n-2 do
for j←i+1 to n-1 do
d←(xi-xj)2+(yi-yj)2
if d<dmin
dmin←d; index1←i; index2←j;
return index1,index2
```
### 正确性证明
### 算法分析
$O(n^2)$
### 程序设计
## 1 [查找算法](3.1%20查找算法.md)
## 2 [搜索算法——广度优先搜索](./3.2%20搜索算法-广度优先搜索.md)
## 3 [搜索算法——深度优先搜索](./3.3%20搜索算法-深度优先搜索.md)
## 4 [排序算法——简单排序](./3.4%20排序算法-简单排序.md)
## 5 [排序算法——线性排序](./3.5%20排序算法-线性排序.md)
## 6 [线性时间选择算法](./3.6%20线性时间选择算法.md)
## 7 [字符串算法——匹配算法](./3.7%20字符串算法-匹配算法.md)
## 8 [字符串算法——其他算法](./3.8%20字符串算法-其他算法.md)
## 9 [位运算算法](./3.9%20位运算算法.md)

View File

@@ -298,61 +298,17 @@
* 最差空间复杂度 O(n),辅助空间 O(1)
### 示例代码
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 };
6
7 OptimizedBubbleSort(unsorted);
8
9 foreach (var key in unsorted)
10 {
11 Console.Write("{0} ", key);
12 }
13
14 Console.Read();
15 }
16
17 static void BubbleSort(int[] unsorted)
18 {
19 for (int i = 0; i < unsorted.Length; i++)
20 {
21 for (int j = 0; j < unsorted.Length - 1 - i; j++)
22 {
23 if (unsorted[j] > unsorted[j + 1])
24 {
25 int temp = unsorted[j];
26 unsorted[j] = unsorted[j + 1];
27 unsorted[j + 1] = temp;
28 }
29 }
30 }
31 }
32
33 static void OptimizedBubbleSort(int[] unsorted)
34 {
35 int exchange = unsorted.Length - 1;
36 while (exchange > 0)
37 {
38 int lastExchange = exchange;
39 exchange = 0;
40
41 for (int i = 0; i < lastExchange; i++)
42 {
43 if (unsorted[i] > unsorted[i + 1])
44 {
45 int temp = unsorted[i];
46 unsorted[i] = unsorted[i + 1];
47 unsorted[i + 1] = temp;
48
49 exchange = i;
50 }
51 }
52 }
53 }
54 }
```C++
vector<int> bubble_sort(vector<int> vec){
for(int i=0;i<vec.size()-1;i++){
for(int j=0;j<vec.size()-1-i;j++){
if(vec[j]>vec[j+1]){
swap(vec[j],vec[j+1]);
}
}
}
return vec;
}
```
## 2 鸡尾酒排序Cocktail Sort
@@ -615,299 +571,33 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
快速排序也会与合并排序Merge Sort竞争。合并排序的特点是最坏情况有着 O(n log n) 运行时间的优势。不像快速排序或堆排序,合并排序是一个稳定排序算法,并且非常的灵活,其设计可以应用于操作链表,或大型链式存储等,例如磁盘存储或网路附加存储等。尽管快速排序也可以被重写使用在链表上,但对于基准的选择总是个问题。合并排序的主要缺点是在最佳情况下需要 O(n) 额外的空间,而快速排序的原地分区和尾部递归仅使用 O(log n) 的空间。
### 代码示例
```
1 class Program
2 {
3 static bool isPrintArrayEnabled = false;
4
5 static void Main(string[] args)
6 {
7 int[] smallSeed = { 4, 1, 5, 2, 6, 3, 7, 9, 8, 0 };
8
9 MeasureQuickSort(smallSeed, 1000000);
10 MeasureRandomizedQuickSort(smallSeed, 1000000);
11 MeasureOptimizedQuickSorts(smallSeed, 1000000);
12
13 Console.Read();
14 }
15
16 static void MeasureQuickSort(int[] smallSeed, int arrayLength)
17 {
18 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength);
19
20 Stopwatch watch = Stopwatch.StartNew();
21
22 QuickSort(unsorted, 0, unsorted.Length - 1);
23
24 watch.Stop();
25
26 Console.WriteLine(
27 "ArrayLength[{0}], QuickSort ElapsedMilliseconds[{1}]",
28 unsorted.Length, watch.ElapsedMilliseconds);
29
30 PrintArray(unsorted);
31 }
32
33 static void MeasureRandomizedQuickSort(int[] smallSeed, int arrayLength)
34 {
35 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength);
36
37 Stopwatch watch = Stopwatch.StartNew();
38
39 RandomizedQuickSort(unsorted, 0, unsorted.Length - 1);
40
41 watch.Stop();
42
43 Console.WriteLine(
44 "ArrayLength[{0}], RandomizedQuickSort ElapsedMilliseconds[{1}]",
45 unsorted.Length, watch.ElapsedMilliseconds);
46
47 PrintArray(unsorted);
48 }
49
50 static void QuickSort(int[] unsorted, int left, int right)
51 {
52 // left 为子数列的最左边元素
53 // right 为子数列的最右边元素
54 if (!(left < right)) return;
55
56 // Partition:
57 // 所有元素比主元值小的摆放在主元的左边,
58 // 所有元素比主元值大的摆放在主元的右边
59 int pivotIndex = Partition(unsorted, left, right);
60
61 // Recursively:
62 // 分别排列小于主元的值和大于主元的值的子数列
63 // 主元无需参加下一次排序
64 QuickSort(unsorted, left, pivotIndex - 1);
65 QuickSort(unsorted, pivotIndex + 1, right);
66 }
67
68 static int Partition(int[] unsorted, int left, int right)
69 {
70 int pivotIndex = right;
71
72 // 哨兵
73 int sentinel = unsorted[right];
74
75 // 子数组长度为 right - left + 1
76 int i = left - 1;
77 for (int j = left; j <= right - 1; j++)
78 {
79 if (unsorted[j] <= sentinel)
80 {
81 i++;
82 Swap(unsorted, i, j);
83 }
84 }
85
86 Swap(unsorted, i + 1, pivotIndex);
87
88 return i + 1;
89 }
90
91 static void RandomizedQuickSort(int[] unsorted, int left, int right)
92 {
93 // left 为子数列的最左边元素
94 // right 为子数列的最右边元素
95 if (!(left < right)) return;
96
97 // Partition:
98 // 所有元素比主元值小的摆放在主元的左边,
99 // 所有元素比主元值大的摆放在主元的右边
100 int pivotIndex = RandomizedPartition(unsorted, left, right);
101
102 // Recursively:
103 // 分别排列小于主元的值和大于主元的值的子数列
104 // 主元无需参加下一次排序
105 RandomizedQuickSort(unsorted, left, pivotIndex - 1);
106 RandomizedQuickSort(unsorted, pivotIndex + 1, right);
107 }
108
109 static int RandomizedPartition(int[] unsorted, int left, int right)
110 {
111 int i = random.Next(left, right);
112 Swap(unsorted, i, right);
113 return Partition(unsorted, left, right);
114 }
115
116 static void Swap(int[] unsorted, int i, int j)
117 {
118 int temp = unsorted[i];
119 unsorted[i] = unsorted[j];
120 unsorted[j] = temp;
121 }
122
123 static void MeasureOptimizedQuickSorts(int[] smallSeed, int arrayLength)
124 {
125 foreach (var pivotSelection in
126 Enum.GetValues(typeof(QuickSortPivotSelectionType)))
127 {
128 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength);
129
130 Stopwatch watch = Stopwatch.StartNew();
131
132 OptimizedQuickSort(unsorted, 0, unsorted.Length - 1,
133 (QuickSortPivotSelectionType)pivotSelection);
134
135 watch.Stop();
136
137 Console.WriteLine(
138 "ArrayLength[{0}], "
139 + "QuickSortPivotSelectionType[{1}], "
140 + "ElapsedMilliseconds[{2}]",
141 unsorted.Length,
142 (QuickSortPivotSelectionType)pivotSelection,
143 watch.ElapsedMilliseconds);
144
145 PrintArray(unsorted);
146 }
147 }
148
149 static int[] GenerateBigUnsortedArray(int[] smallSeed, int arrayLength)
150 {
151 int[] bigSeed = new int[100];
152 for (int i = 0; i < bigSeed.Length; i++)
153 {
154 bigSeed[i] =
155 smallSeed[i % smallSeed.Length]
156 + i / smallSeed.Length * 10;
157 }
158
159 int[] unsorted = new int[arrayLength];
160 for (int i = 0; i < unsorted.Length / bigSeed.Length; i++)
161 {
162 Array.Copy(bigSeed, 0, unsorted, i * bigSeed.Length, bigSeed.Length);
163 }
164
165 return unsorted;
166 }
167
168 static void OptimizedQuickSort(int[] unsorted, int left, int right,
169 QuickSortPivotSelectionType pivotSelection)
170 {
171 // left 为子数列的最左边元素
172 // right 为子数列的最右边元素
173 if (!(left < right)) return;
174
175 // Partition:
176 // 所有元素比主元值小的摆放在主元的左边,
177 // 所有元素比主元值大的摆放在主元的右边
178 Tuple<int, int> pivotPair =
179 OptimizedPartition(unsorted, left, right, pivotSelection);
180
181 // Recursively:
182 // 分别排列小于主元的值和大于主元的值的子数列
183 // 主元无需参加下一次排序
184 OptimizedQuickSort(unsorted, left, pivotPair.Item1 - 1, pivotSelection);
185 OptimizedQuickSort(unsorted, pivotPair.Item2 + 1, right, pivotSelection);
186 }
187
188 static Tuple<int, int> OptimizedPartition(
189 int[] unsorted, int left, int right,
190 QuickSortPivotSelectionType pivotSelection)
191 {
192 int pivotIndex = SelectPivot(unsorted, left, right, pivotSelection);
193 int sentinel = unsorted[pivotIndex];
194
195 // 子数组长度为 right - left + 1
196 int i = left - 1;
197 int j = right + 1;
198 while (true)
199 {
200 while (unsorted[++i] < sentinel) ;
201 while (unsorted[--j] > sentinel) ;
202 if (i >= j) break;
203
204 // 在主元左侧找到一个大于主元值的位置 i
205 // 在主元右侧找到一个小于主元值的位置 j
206 // 交换两个值
207 Swap(unsorted, i, j);
208 }
209
210 return new Tuple<int, int>(i, j);
211 }
212
213 static int SelectPivot(int[] unsorted, int left, int right,
214 QuickSortPivotSelectionType pivotSelection)
215 {
216 switch (pivotSelection)
217 {
218 case QuickSortPivotSelectionType.FirstAsPivot:
219 return left;
220 case QuickSortPivotSelectionType.MiddleAsPivot:
221 return (left + right) / 2;
222 case QuickSortPivotSelectionType.LastAsPivot:
223 return right;
224 case QuickSortPivotSelectionType.RandomizedPivot:
225 {
226 // 在区间内随机选择位置
227 if (right - left > 1)
228 {
229 return random.Next(left, right);
230 }
231 else
232 {
233 return left;
234 }
235 }
236 case QuickSortPivotSelectionType.BalancedPivot:
237 {
238 // 选择起始、中间和结尾位置中的中位数
239 int leftValue = unsorted[left];
240 int middleValue = unsorted[(left + right) / 2];
241 int rightValue = unsorted[right];
242
243 if (leftValue < middleValue)
244 {
245 if (middleValue < rightValue)
246 {
247 return (left + right) / 2;
248 }
249 else
250 {
251 return right;
252 }
253 }
254 else
255 {
256 if (leftValue < rightValue)
257 {
258 return left;
259 }
260 else
261 {
262 return right;
263 }
264 }
265 }
266 }
267
268 return (left + right) / 2;
269 }
270
271 static void PrintArray(int[] unsorted)
272 {
273 if (!isPrintArrayEnabled) return;
274
275 foreach (var item in unsorted)
276 {
277 Console.Write("{0} ", item);
278 }
279 Console.WriteLine();
280 }
281
282 static Random random = new Random(new Guid().GetHashCode());
283
284 enum QuickSortPivotSelectionType
285 {
286 FirstAsPivot,
287 MiddleAsPivot,
288 LastAsPivot,
289 RandomizedPivot,
290 BalancedPivot,
291 }
292 }
```C++
// 快速排序
int partion(vector<int>&vec, int left, int right){
int index = right;
// 哨兵
int sentinel = vec[index];
// 子数组长度为 right - left + 1
int i = left;
// 从左到右,小的与前边交换。大的就会到右边。
for (int j = left; j <= right - 1; j++)
{
if (vec[j] <= sentinel)
{
swap(vec[i],vec[j]);
i++;
}
}
swap(vec[i],vec[index]);
return i;
}
vector<int> quick_sort(vector<int> &vec,int left,int right){
if(left>=right)return;
int index = partion(vec,left,right);
quick_sort(vec,left,index-1);
quick_sort(vec,index+1,right);
return vec;
}
```
@@ -929,54 +619,21 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
* 最差空间复杂度 О(n),辅助空间 O(1)
### 代码示例
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 };
6
7 SelectionSort(unsorted);
8
9 foreach (var key in unsorted)
10 {
11 Console.Write("{0} ", key);
12 }
13
14 Console.Read();
15 }
16
17 static void SelectionSort(int[] unsorted)
18 {
19 // advance the position through the entire array
20 // could do i < n-1 because single element is also min element
21 for (int i = 0; i < unsorted.Length - 1; i++)
22 {
23 // find the min element in the unsorted a[i .. n-1]
24 // assume the min is the first element
25 int min = i;
26
27 // test against elements after i to find the smallest
28 for (int j = i + 1; j < unsorted.Length; j++)
29 {
30 // if this element is less, then it is the new minimum
31 if (unsorted[j] < unsorted[min])
32 {
33 // found new minimum, remember its index
34 min = j;
35 }
36 }
37
38 // swap
39 if (min != i)
40 {
41 int temp = unsorted[i];
42 unsorted[i] = unsorted[min];
43 unsorted[min] = temp;
44 }
45 }
46 }
47 }
```C++
vector<int> selection_sort(vector<int> vec){
int min=INT_MAX,min_index=0;
int i=0,j=0;
for(i=0;i<vec.size()-1;i++){
min=INT_MAX;
for(j=i;j<vec.size();j++){
if(vec[j]<min){
min = vec[j];
min_index=j;
}
}
swap(vec[i],vec[j]);
}
}
```
## 6 插入排序Insertion Sort
@@ -1002,41 +659,23 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
插入排序算法的内循环是紧密的,对小规模输入来说是一个快速的原地排序算法。
### 示例代码
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 };
6
7 InsertionSort(unsorted);
8
9 foreach (var key in unsorted)
10 {
11 Console.Write("{0} ", key);
12 }
13
14 Console.Read();
15 }
16
17 static void InsertionSort(int[] unsorted)
18 {
19 for (int i = 1; i < unsorted.Length; i++)
20 {
21 if (unsorted[i - 1] > unsorted[i])
22 {
23 int key = unsorted[i];
24 int j = i;
25 while (j > 0 && unsorted[j - 1] > key)
26 {
27 unsorted[j] = unsorted[j - 1];
28 j--;
29 }
30 unsorted[j] = key;
31 }
32 }
33 }
34 }
```C++
vector<int> insertion_sort(vector<int> vec){
for(int i=1;i<vec.size();i++){
int temp = vec[i];
int j;
for(j=i;j>0;j--){
if(vec[j-1]>temp){
vec[j]=vec[j-1];
}
else{
break;
}
}
vec[j]=temp;
}
return vec;
}
```
## 7 希尔排序Shell Sort
@@ -1147,75 +786,39 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
* 合并排序有着较好的渐进运行时间 Θ(nlogn),但其中的 merge 操作不是 in-place 操作。
### 示例代码
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 };
6
7 int[] temp = new int[unsorted.Length];
8 MergeSort(unsorted, 0, unsorted.Length, temp);
9 foreach (var key in unsorted)
10 {
11 Console.Write("{0} ", key);
12 }
13
14 Console.Read();
15 }
16
17 static void MergeSort(int[] unsorted, int left, int right, int[] temp)
18 {
19 if (left + 1 < right)
20 {
21 // divide
22 int mid = (left + right) / 2;
23
24 // conquer
25 MergeSort(unsorted, left, mid, temp);
26 MergeSort(unsorted, mid, right, temp);
27
28 // combine
29 Merge(unsorted, left, mid, right, temp);
30 }
31 }
32
33 static void Merge(int[] unsorted, int left, int mid, int right, int[] temp)
34 {
35 int leftPosition = left;
36 int rightPosition = mid;
37 int numberOfElements = 0;
38
39 // merge two slots
40 while (leftPosition < mid && rightPosition < right)
41 {
42 if (unsorted[leftPosition] < unsorted[rightPosition])
43 {
44 temp[numberOfElements++] = unsorted[leftPosition++];
45 }
46 else
47 {
48 temp[numberOfElements++] = unsorted[rightPosition++];
49 }
50 }
51
52 // add remaining
53 while (leftPosition < mid)
54 {
55 temp[numberOfElements++] = unsorted[leftPosition++];
56 }
57 while (rightPosition < right)
58 {
59 temp[numberOfElements++] = unsorted[rightPosition++];
60 }
61
62 // copy back
63 for (int n = 0; n < numberOfElements; n++)
64 {
65 unsorted[left + n] = temp[n];
66 }
67 }
68 }
```C++
// 归并排序
void merge(vector<int> &vec,int left,int right,int mid){
vector<int> temp(vec);
int left_index=left;
int right_index = mid+1;
int i=left;
while(left_index<=mid && right_index<=right){
if(temp[left_index]<=temp[right_index]){
vec[i++]=temp[left_index++];
}
else{
vec[i++]=temp[right_index++];
}
}
// 处理左边没有用完的数据。(右边没有用完的可以保留在原地
while(left_index<=mid){
vec[i++]=temp[left_index++];
}
return;
}
// 头递归
void merge_sort(vector<int> &vec,int left,int right){
if(left>=right)return;
// divide
int mid = (left+right)/2;
// conquer
merge_sort(vec,left,mid);
merge_sort(vec,mid+1,right);
// combine
merge(vec,left,right,mid);
return;
}
```
## 9 堆排序Heap Sort
@@ -1226,7 +829,7 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
二叉堆有两种,最大堆和最小堆。最大堆特性是指除了根以外的每个节点 i ,有 A(Parent(i)) ≥ A[i] ,即某个节点的值至多是和其父节点的值一样大。最小堆特性是指除了根以外的每个节点 i ,有 A(Parent(i)) ≤ A[i] ,最小堆的最小元素在根部。
在堆排序算法中,我们使用的是最大堆。最小堆通常在构造有限队列时使用。
在堆排序算法中,我们使用的是最大堆。最小堆通常在构造优先队列时使用。
堆可以被看成一棵树,节点在堆中的高度定义为从本节点到叶子的最长简单下降路径上边的数目;定义堆的高度为树根的高度。因为具有 n 个元素的堆是基于一棵完全二叉树,因而其高度为 Θ(lg n) 。
@@ -1255,94 +858,48 @@ RANDOMIZED-QUICKSORT 的平均运行情况是 O(n lg n),如果在递归的每
* 最差空间复杂度 O(n),辅助空间 O(1)
### 示例代码
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 };
6
7 HeapSort(unsorted);
8
9 foreach (var key in unsorted)
10 {
11 Console.Write("{0} ", key);
12 }
13
14 Console.Read();
15 }
16
17 static void HeapSort(int[] unsorted)
18 {
19 // build the heap in array so that largest value is at the root
20 BuildMaxHeap(unsorted);
21
22 // swap root node and the last heap node
23 for (int i = unsorted.Length - 1; i >= 1; i--)
24 {
25 // array[0] is the root and largest value.
26 // the swap moves it in front of the sorted elements
27 int max = unsorted[0];
28 unsorted[0] = unsorted[i];
29 unsorted[i] = max; // now, the largest one is at the end
30
31 // the swap ruined the heap property, so restore it
32 // the heap size is reduced by one
33 MaxHeapify(unsorted, 0, i - 1);
34 }
35 }
36
37 static void BuildMaxHeap(int[] unsorted)
38 {
39 // put elements of array in heap order, in-place
40 // start is assigned the index in array of the last parent node
41 // the last element in 0-based array is at index count-1;
42 // find the parent of that element
43 for (int i = (unsorted.Length / 2) - 1; i >= 0; i--)
44 {
45 // sift down the node at index start to the proper place
46 // such that all nodes below the start index are in heap order
47 MaxHeapify(unsorted, i, unsorted.Length - 1);
48 }
49 // after sifting down the root all nodes/elements are in heap order
50 }
51
52 static void MaxHeapify(int[] unsorted, int root, int bottom)
53 {
54 int rootValue = unsorted[root];
55 int maxChild = root * 2 + 1; // start from left child
56
57 // while the root has at least one child
58 while (maxChild <= bottom)
59 {
60 // more children
61 if (maxChild < bottom)
62 {
63 // if there is a right child and that child is greater
64 if (unsorted[maxChild] < unsorted[maxChild + 1])
65 {
66 maxChild = maxChild + 1;
67 }
68 }
69
70 // compare roots and the older children
71 if (rootValue < unsorted[maxChild])
72 {
73 unsorted[root] = unsorted[maxChild];
74 root = maxChild;
75
76 // repeat to continue sifting down the child now
77 maxChild = root * 2 + 1; // continue from left child
78 }
79 else
80 {
81 maxChild = bottom + 1;
82 }
83 }
84
85 unsorted[root] = rootValue;
86 }
87 }
```C++
// 堆排序
// 向下调整
void shift_down(vector<int> &vec,int pos,int end){
// 不是最后的非叶节点,直接返回
if(pos>(end-1)/2)return;
int left = 2*pos+1;
int right = 2*pos+2;
int max;
// 超出范围,说明是最后一个非叶节点只有一个叶节点
// 找到两个节点中最大的那个
if(right>end || vec[left]>vec[right]){
max=left;
}
else{
max=right;
}
// 进行一次下降操作
if(vec[max]>vec[pos]){
swap(vec[max],vec[pos]);
}
// 下一层的下降
shift_down(vec,max,end);
}
void heap_sort(vector<int> &vec){
int end = vec.size()-1;
// 从最后一个非页节点开始向下调整。到最后一个节点
for(int i=(end-1)/2;i>=0;i--){
shift_down(vec,i,end);
}
swap(vec[0],vec[end]);
end--;
while(end>0){
// 从根节点开始向下调整到最后一个叶节点。
shift_down(vec,0,end);
// 交换根节点和最后一个叶子节点
swap(vec[0],vec[end]);
end--;
}
return;
}
```
## 9 内省排序Introspective Sort

View File

@@ -0,0 +1,175 @@
#include<iostream>
#include<vector>
using namespace std;
// 插入排序
void insertion_sort(vector<int> &vec);
// 冒泡排序
void bubble_sort(vector<int> &vec);
// 选择排序
void selection_sort(vector<int> &vec);
// 快速排序
void quick_sort(vector<int> &vec,int left,int right);
// 归并排序
void merge_sort(vector<int> &vec,int left,int right);
// 堆排序
void heap_sort(vector<int> &vec);
int main(){
vector<int> vec{6,3,1,4,9,7,8,2,5,0};
// bubble_sort(vec);
// selection_sort(vec);
// insertion_sort(vec);
// quick_sort(vec,0,vec.size()-1);
// merge_sort(vec,0,vec.size()-1);
heap_sort(vec);
for(int i=0;i<vec.size();i++){
cout<<vec[i]<<endl;
}
return 0;
}
// 三大排序思想:插入/交换/选择
// 插入排序
void insertion_sort(vector<int> &vec){
for(int i=1;i<vec.size();i++){
int temp = vec[i];
int j;
for(j=i;j>0;j--){
if(vec[j-1]>temp){
vec[j]=vec[j-1];
}
else{
break;
}
}
vec[j]=temp;
}
return ;
}
// 冒泡排序
void bubble_sort(vector<int> &vec){
for(int i=0;i<vec.size()-1;i++){
for(int j=0;j<vec.size()-1-i;j++){
if(vec[j]>vec[j+1]){
swap(vec[j],vec[j+1]);
}
}
}
return ;
}
// 选择排序
void selection_sort(vector<int> &vec){
int min=INT_MAX,min_index=0;
int i=0,j=0;
for(i=0;i<vec.size()-1;i++){
min=INT_MAX;
for(j=i;j<vec.size();j++){
if(vec[j]<=min){
min = vec[j];
min_index=j;
}
}
swap(vec[i],vec[min_index]);
}
}
// 快速排序
int partion(vector<int>&vec, int left, int right){
int index = right;
// 哨兵
int sentinel = vec[index];
// 子数组长度为 right - left + 1
int i = left;
// 从左到右,小的与前边交换。大的就会到右边。
for (int j = left; j <= right - 1; j++){
if (vec[j] <= sentinel){
swap(vec[i],vec[j]);
i++;
}
}
swap(vec[i],vec[index]);
return i;
}
// 尾递归。先头递归
void quick_sort(vector<int> &vec,int left,int right){
if(left>=right)return ;
int index = partion(vec,left,right);
quick_sort(vec,left,index-1);
quick_sort(vec,index+1,right);
return ;
}
// 归并排序
void merge(vector<int> &vec,int left,int right,int mid){
vector<int> temp(vec);
int left_index=left;
int right_index = mid+1;
int i=left;
while(left_index<=mid && right_index<=right){
if(temp[left_index]<=temp[right_index]){
vec[i++]=temp[left_index++];
}
else{
vec[i++]=temp[right_index++];
}
}
// 处理左边没有用完的数据。(右边没有用完的可以保留在原地
while(left_index<=mid){
vec[i++]=temp[left_index++];
}
return;
}
// 头递归
void merge_sort(vector<int> &vec,int left,int right){
if(left>=right)return;
// divide
int mid = (left+right)/2;
// conquer
merge_sort(vec,left,mid);
merge_sort(vec,mid+1,right);
// combine
merge(vec,left,right,mid);
return;
}
// 堆排序
// 向下调整
void shift_down(vector<int> &vec,int pos,int end){
// 不是最后的非叶节点,直接返回
if(pos>(end-1)/2)return;
int left = 2*pos+1;
int right = 2*pos+2;
int max;
// 超出范围,说明是最后一个非叶节点只有一个叶节点
// 找到两个节点中最大的那个
if(right>end || vec[left]>vec[right]){
max=left;
}
else{
max=right;
}
// 进行一次下降操作
if(vec[max]>vec[pos]){
swap(vec[max],vec[pos]);
}
// 下一层的下降
shift_down(vec,max,end);
}
void heap_sort(vector<int> &vec){
int end = vec.size()-1;
// 从最后一个非页节点开始向下调整。到最后一个节点
for(int i=(end-1)/2;i>=0;i--){
shift_down(vec,i,end);
}
swap(vec[0],vec[end]);
end--;
while(end>0){
// 从根节点开始向下调整到最后一个叶节点。
shift_down(vec,0,end);
// 交换根节点和最后一个叶子节点
swap(vec[0],vec[end]);
end--;
}
return;
}

View File

@@ -0,0 +1,56 @@
# 约瑟夫问题
### 问题描述
### 问题分析
### 算法设计
我们将上述问题建模为函数 f(n, m),该函数的返回值为最终留下的元素的序号。
首先,长度为 n 的序列会先删除第 m % n 个元素,然后剩下一个长度为 n - 1 的序列。那么,我们可以递归地求解 f(n - 1, m),就可以知道对于剩下的 n - 1 个元素,最终会留下第几个元素,我们设答案为 x = f(n - 1, m)。
由于我们删除了第 m % n 个元素,将序列的长度变为 n - 1。当我们知道了 f(n - 1, m) 对应的答案 x 之后,我们也就可以知道,长度为 n 的序列最后一个删除的元素,应当是从 m % n 开始数的第 x 个元素。因此有 f(n, m) = (m % n + x) % n = (m + x) % n。
![](image/2021-03-29-10-18-28.png)
### 算法分析
### 算法实现
* 递归
```
class Solution {
int f(int n, int m) {
if (n == 1) {
return 0;
}
int x = f(n - 1, m);
return (m + x) % n;
}
public:
int lastRemaining(int n, int m) {
return f(n, m);
}
};
```
* 迭代
```
class Solution {
public:
int lastRemaining(int n, int m) {
int f = 0;
for (int i = 2; i != n + 1; ++i) {
f = (m + f) % i;
}
return f;
}
};
```

View File

@@ -47,13 +47,13 @@ $$
4. 根据计算最优值时的到的信息,**构造最有解**。
### 动态规划算法的步骤
1. 正确选择**状态变量**xk, 使它既能描述过程的状态,又要满足无后效性。动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
1. **选择状态变量$x_k$**描述过程的状态,又要满足无后效性。动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
* 要能够用来描述受控过程的演变特征。
* 要满足无后效性。 所谓无后效性是指:如果某段状态给定,则在这段以后过程的发展不受前面各阶段状态的影响。
* 可知性。即是规定的各段状态变量的值,由直接或间接都是可以知道的。
2. 确定**决策变量**uk及每段的允许决策集合Dk(xk)={uk}
3. 写出**状态转移方程**如果给定第k段状态变量xk的值则该段的决策变量uk一经确定第k+1段状态变量xk+1的值也就完全确定。
4. 列出**指标函数**Vk,n 关系,并要满足递推性。
2. **确定决策变量$u_k$** 及每段的允许决策集合$D_k(x_k)=\{u_k\}$
3. **确定状态转移方程** 如果给定第k段状态变量$x_k$的值则该段的决策变量uk一经确定第k+1段状态变量$x_{k+1}$的值也就完全确定。
4. **确定指标函数$v_k$**:写出$v_k$和$n$关系,并要满足递推性。
### 动态规划最关键的部分
* 确定规模的增长方向。一般在动态规划问题中。规模可变的并不只有一个。比如在正则表达式与字符串的匹配问题中。字符串的规模可以变化,正则表达式的规模也可以变化。

View File

@@ -0,0 +1,104 @@
# n个骰子的点数
## 1 n个骰子的点数
### 问题描述
* 把n个骰子扔在地上所有骰子朝上一面的点数之和为s。输入n打印出s的所有可能的值出现的概率。你需要用一个浮点数数组返回答案其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
* 示例 1:
```
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
```
### 问题分析
### 策略选择
### 算法设计
* 使用动态规划解决问题一般分为三步:
1. 确定状态变量
2. 确定状态转移方程
3. 边界处理
* 下面我们一步一步分析,相信你一定会有所收获!
* 表示状态
* 分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。
* 通过题目我们知道一共投掷 n 枚骰子,那最后一个阶段很显然就是:当投掷完 n 枚骰子后,各个点数出现的次数。
* 注意,这里的点数指的是前 n 枚骰子的点数和,而不是第 n 枚骰子的点数,下文同理。找出了最后一个阶段,那状态表示就简单了。
* 首先用数组的第一维来表示阶段,也就是投掷完了几枚骰子。
* 然后用第二维来表示投掷完这些骰子后,可能出现的点数。
* 数组的值就表示,该阶段各个点数出现的次数。
* 所以状态表示就是这样的dp[i][j]dp[i][j] ,表示投掷完 ii 枚骰子后,点数 jj 的出现次数。
* 找出状态转移方程
* 找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。
* 最后一个阶段也就是投掷完 nn 枚骰子后的这个阶段,我们用 dp[n][j]来表示最后一个阶段点数 j出现的次数。
* 单单看第 n 枚骰子,它的点数可能为 1 , 2, 3, ... , 6因此投掷完 n 枚骰子后点数 j 出现的次数可以由投掷完n1 枚骰子后,对应点数 j-1, j-2, j-3, ... , j-6出现的次数之和转化过来。
```
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
dp[n][j] += dp[n-1][j - i]
}
```
* 写成数学公式是这样的:
$$
dp[n][j] = \sum_{i=1}^6 dp[n-1][j-i]
$$
* n 表示阶段jj 表示投掷完 nn 枚骰子后的点数和ii 表示第 nn 枚骰子会出现的六个点数。
* 边界处理
* 这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。
* 我们可以直接知道的状态是啥,就是第一阶段的状态:投掷完 11 枚骰子后,它的可能点数分别为 1, 2, 3, ... , 61,2,3,...,6 ,并且每个点数出现的次数都是 11 .
```
for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}
```
![](image/2021-03-29-09-47-50.png)
### 算法分析
* 时间复杂度 $O(n ^ 2)$
* 空间复杂度 $O(n)$
### 算法实现
```C++
class Solution {
public:
vector<double> twoSum(int n) {
int dp[15][70];
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}
for (int i = 2; i <= n; i ++) {
for (int j = i; j <= 6*i; j ++) {
for (int cur = 1; cur <= 6; cur ++) {
if (j - cur <= 0) {
break;
}
dp[i][j] += dp[i-1][j-cur];
}
}
}
int all = pow(6, n);
vector<double> ret;
for (int i = n; i <= 6 * n; i ++) {
ret.push_back(dp[n][i] * 1.0 / all);
}
return ret;
}
};
```

View File

@@ -21,7 +21,7 @@
### 算法设计
* 设s的长度为n pp 的长度为 m ;将 s 的第 i 个字符记为 s_ip 的第 j个字符记为 p_j ,将 s 的前 i 个字符组成的子字符串记为s[:i] , 同理将 p 的前 j 个字符组成的子字符串记为 p[:j]p[:j] 。
* 设s的长度为n p 的长度为 m ;将 s 的第 i 个字符记为$s_i$p 的第 j个字符记为 $p_j$ ,将 s 的前 i 个字符组成的子字符串记为s[:i] , 同理将 p 的前 j 个字符组成的子字符串记为 p[:j]p[:j] 。
* 因此本题可转化为求s[:n] 是否能和p[:m] 匹配。
@@ -29,7 +29,7 @@
1. 添加一个字符 $s_{i+1}$后是否能匹配?
2. 添加字符 $p_{j+1}$后是否能匹配?
* 本题的状态共有 m \times nm×n 种,应定义状态矩阵 dpdp dp[i][j]代表 s[:i]与 p[:j]是否可以匹配。做好状态定义,接下来就是根据 「普通字符」 , 「.」 , 「*」三种字符的功能定义,分析出动态规划的转移方程。
* 本题的状态共有 m \times nm×n 种应定义状态矩阵dp dp[i][j]代表 s[:i]与 p[:j]是否可以匹配。做好状态定义,接下来就是根据 「普通字符」 , 「.」 , 「*」三种字符的功能定义,分析出动态规划的转移方程。
1. **状态定义** 设动态规划矩阵 dp dp[i][j] 代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。
2. **转移方程** 需要注意,由于 dp[0][0] 代表的是空字符的状态, 因此 dp[i][j] 对应的添加字符是 s[i - 1] 和 p[j - 1] 。

View File

@@ -14,7 +14,7 @@
* 初始状态: dp[0] = nums[0]dp[0]=nums[0],即以 nums[0]nums[0] 结尾的连续子数组最大和为 nums[0]nums[0] 。
* 返回值: 返回 dpdp 列表中的最大值,代表全局最大值。
![](image/2021-03-19-00-24-38.png)
![](image/2021-03-29-09-50-50.png)
### 算法分析

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,24 @@
## 最近对问题
### 理解问题
* 找出一个包含n个点的集合中距离最近的两个点。
### 选择策略
* 分别计算每一点对之间的距离然后从中找出距离最小的那一对。为了避免同一点对计算两次可以只考虑i<j的点对(Pi, Pj)
### 算法设计
* 算法 bruteForceClosesPoints(P)
```
//蛮力法求解平面中距离最近的两点
//输入一个n(n≥2)个点的列表PP1=(x1, y1)Pn=(xn, yn)
//输出:两个最近点的下标
dmin←∞
for i←0 to n-2 do
for j←i+1 to n-1 do
d←(xi-xj)2+(yi-yj)2
if d<dmin
dmin←d; index1←i; index2←j;
return index1,index2
```
### 正确性证明
### 算法分析
$O(n^2)$
### 程序设计

View File

@@ -0,0 +1,2 @@
## 幻方问题