操作系统完成

This commit is contained in:
yinkanglong_lab
2021-03-30 22:30:46 +08:00
parent 83527d1a86
commit b003f28e59
102 changed files with 2396 additions and 3210 deletions

View File

@@ -35,7 +35,13 @@
- [ ] 计算机网络
- [ ] 数据库
- [ ] 操作系统
- [ ] Linux 与网络编程
- [ ] 进程管理
- [ ] 处理机管理
- [ ] 设备管理
- [ ] 文件管理
- [ ] 系统调用
- [ ] 网络编程socket网络编程
- [ ] 并行编程(多线程并发编程)
- 刷题
- 力扣(学习、题库、讨论。侧重于刷算法类型的题目和相关讨论)

View File

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

View File

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

View File

@@ -2,8 +2,6 @@
> 计算机操作系统的组成
## 1 操作系统引论
1. 操作系统的目标和作用
@@ -59,4 +57,11 @@
4. 目录管理
5. 文件存储空间管理
6. 文件共享与文件保护
7. 数据一致性控制
7. 数据一致性控制
## 7 操作系统接口
1. 联机用户接口
2. shell命令语言
3. 系统调用
4. 图形用户界面

View File

@@ -14,21 +14,130 @@
* 操作系统Operating SystemOS是计算机系统最基础的系统软件管理软硬件资源、控制程序执行改善人机界面合理组织计算机工作流程为用户使用计算机提供良好运行环境。
### 目标
* 有效性。提高系统资源利用率,提高系统吞吐量。
* 方便性
* 可扩充性
* 开放性
### 作用
### 分类
* OS 作为用户与计算机硬件系统之间的接口。有三种接口方式:命令方式、系统调用方式、图形窗口方式。
* OS 作为计算机系统资源的管理者。资源主要包括:处理器、存储器、设备以及信息(数据和程序)等资源。
* OS 实现了对计算机资源的抽象。
1. 操作控制方式
1. 多道批处理操作系统,脱机控制方式
2. 分时操作系统,交互式控制方式
3. 实时操作系统
2. 应用领域
1. 服务器操作系统、并行操作系统
2. 网络操作系统、分布式操作系统
3. 个人机操作系统、手机操作系统
4. 嵌入式操作系统、传感器操作系统
## 2 操作系统的发展过程
### OS发展
1. 单道批处理系统。自动性、顺序性、单道性。
1. 多道批处理操作系统,脱机控制方式。资源利用率高,系统吞吐量大,平均运转周期长,无交互能力。
2. 分时操作系统,交互式控制方式。人机交互,共享主机。
3. 实时操作系统
### OS分类
* 根据应用领域可以分为
1. 服务器操作系统、并行操作系统
2. 网络操作系统、分布式操作系统
3. 个人机操作系统、手机操作系统
4. 嵌入式操作系统、传感器操作系统
## 3 基本特征
### 3.1 并发性
* 并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。
* 并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。
* 操作系统通过引入进程和线程,使得程序能够并发运行。
### 3.2 共享性
* 共享是指系统中的资源可以被多个并发进程共同使用。
* 主要由两种共享方式:互斥共享和同步共享
* **互斥共享**。互斥共享的资源称为临界资源,例如打印机等,在同一时刻只允许一个进程访问,需要用同步机制来实现互斥访问。
* **同时共享**。允许在一段时间内由多个进程“同时”对它们进行访问
### 3.3 虚拟性
* 虚拟技术把一个物理实体转换为多个逻辑实体。
* 主要有两种虚拟技术:时(时间)分复用技术和空(空间)分复用技术。
* **时分复用技术**:多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。
* **空分复用技术**:虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
### 2.4 异步性
* 异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。
## 4 基本功能
### 4.1 进程管理功能(属于处理机管理的一部分)
* 进程控制、进程同步、进程通信
### 4.2 处理机管理功能
* 死锁处理、处理机调度(作业调度和进程调度)等。
### 4.3 存储器管理功能
* 内存分配、地址映射、内存保护与共享、虚拟内存等。
### 4.4 设备管理功能
* 完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。
* 主要包括缓冲管理、设备分配、设备处理、虛拟设备等。
### 4.5 文件管理功能
* 文件存储空间的管理、目录管理、文件读写管理和保护等。
### 4.6 接口功能
* 提供给用户的接口
* 图形界面
* 命令行接口
* 提供给程序的接口
* 系统调用接口
## 5 OS结构设计
### 传统操作系统结构
* 无结构OS
* 模块化结构OS。高内聚低耦合。
![](image/2021-03-30-09-58-03.png)
* 分层式结构OS。易于保证正确性。易于维护。
### 客户端服务器模式
* 组成:客户端、服务器、网络系统
* 流程:客户端发送消息、服务器接收消息、服务器会送消息、客户端接收消息。
* 优缺点:数据分布式存储和处理、便于集中管理、灵活性和可扩充性、易于改变应用软件。
### 面向对象程序设计
* 关键概念:对象、对象类、继承
* 优缺点:重用提高代码复用率、易修改性和易扩展性、易于保证可靠性和正确性。
### 宏内核与微内核OS结构
* 宏内核概念:宏内核是将操作系统功能作为一个紧密结合的整体放到内核。由于各模块共享信息,因此有很高的性能。
* 微内核概念:由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。
* 微内核特点:足够小的内核、基于客户服务器模式、应用“机制与策略分离”的原理、采用面向对象技术。
* 微内核优缺点:可扩展性、可靠性、可移植性、分布式系统支持、面向对象技术、频繁地在用户态和核心态之间进行切换造成性能损失。
![](image/2021-03-30-10-07-32.png)
![](image/2021-03-30-10-07-14.png)
## 6 操作系统的资源
### 资源分类
1. 硬件资源
@@ -52,113 +161,9 @@
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. 运算逻辑部件:一个或多个运算器
@@ -174,7 +179,7 @@
7. Cache
8. IOAR/IODR
### 存储器
### 存储器资源
1. 存储器的组织层次(由上到下,容量更大、速度更慢、价格更低,单位都是字节)
1. L0寄存器
@@ -186,7 +191,7 @@
7. L6本地外存储器本地硬盘
8. L7远程外存储器分布式文件系统、Web服务器
### 外围设备
### IO设备资源
1. 类型
1. 输入设备

View File

@@ -1,130 +0,0 @@
# 中断
## 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

@@ -1,836 +1,8 @@
# 进程管理
## 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
> ## 目录
> 1. 进程的基本概念
> 2. 进程控制
> 3. 进程同步
> 4. 进程通信
> 5. 线程

View File

@@ -0,0 +1,136 @@
# 2.1 进程的基本概念
> 关系概述
> * 程序不能独立运行。只能作为进程执行。程序是静态的,进程是动态的。
> * 程序有两种执行方式:程序的顺序执行和程序的并发执行。并发执行通过多进程实现。
## 1 程序的顺序执行
### 程序顺序执行时的特征
1. 顺序性。严格按照程序规定的特征执行
2. 封闭性。程序运行时独占全机资源。程序一旦开始,其结果不受外界影响。
3. 可再现性。只要程序运行的环境和初始条件相同,结果相同
## 2 前驱图
### 定义
* 前趋图(Precedence Graph)是一个有向无环图,记为 DAG(Directed Acyclic Graph),用于描述进程之间执行的前后关系。图中的每个结点可用于描述一个程序段或进程,乃至一条语句;结点间的有向边则用于表示两个结点之间存在的偏序(Partial Order亦称偏序关系)或前趋关系(Precedence Relation)“→”。
![](image/2021-03-30-12-10-58.png)
## 3 程序的并发执行
### 定义
* 程序并发执行的前驱图表示
![](image/2021-03-30-12-12-52.png)
### 程序并发执行时的特征
1. 间断性。执行-暂停-执行,间断性活动。
2. 失去封闭性。多个程序共享系统中的各个资源。
3. 不可再现性。其计算结果与并发执行的进程有关。相互影响。
## 4 进程的定义和特征
### 进程的定义
* 操作系统用来实现程序并发执行的实体。
* 作为操作系统**资源分配**和**独立运行**的基本单位。
### 进程的特征
* 结构特征:由程序段、数据段和 PCB 三部分便构成了进程实体。
* 动态性:进程的实质是进程实体的一次执行过程,包含创建、调度、撤销。进程具有一定的声明周期。程序是一组指令的集合,是静态的。
* 并发性:这是指多个进程实体同存于内存中,且能在一段时间内同时运行。
* 独立性:独立性是指进程实体是一个能独立运行、独立分配资源和独立接受调度的基本单位。
* 异步性:这是指进程按各自独立的、 不可预知的速度向前推进,或说进程实体按异步方式运行。
### 进程的构成
1. OS管理程序的数据结构PCP
2. 运行程序的内存代码段Code
3. 运行程序的内存数据段Data
4. 运行程序的通用寄存器信息Register
5. OS控制程序的程序状态字信息PSW
## 5 进程的状态
### 就绪状态、执行状态、阻塞状态
1. 就绪(Ready)状态:当进程已分配到除 CPU 以外的所有必要资源后,只要再获得 CPU便可立即执行进程这时的状态称为就绪状态。就绪队列管理就绪状态的进程。
2. 执行状态:进程已获得 CPU其程序正在执行。在单处理机系统中只有一个进程处于执行状态在多处理机系统中则有多个进程处于执行状态。
3. 阻塞状态:正在执行的进程由于发生某事件而暂时无法继续执行时,放弃处理机而处于暂停状态,把这种暂停状态称为阻塞状态。
### 3态模型的转换
![](image/2021-03-30-12-27-36.png)
1. 运行态→等待态等待资源、I/O、信号
2. 等待态→就绪态资源满足、I/O结束、信号完成
3. 就绪态→运行态;处理器空闲时选择更高优先权进程抢占
4. 运行态→就绪态:运行时间片刻、有更高优先权进程
### 挂起状态
* 由于终端用户的请求、父进程请求、负荷调节的需要、操作系统的需要。程序需要暂时挂起。不再接受操作系统的调度。
### 5态模型的转换挂起
![](image/2021-03-30-12-34-04.png)
1. 活动就绪→静止就绪。当进程处于未被挂起的就绪状态时,称此为活动就绪状态,表示为 Readya。当用挂起原语 Suspend 将该进程挂起后,该进程便转变为静止就绪状态,表示为 Readys处于 Readys 状态的进程不再被调度执行。
2. 活动阻塞→静止阻塞。当进程处于未被挂起的阻塞状态时,称它是处于活动阻塞状态,表示为 Blockeda。当用 Suspend 原语将它挂起后进程便转变为静止阻塞状态表示为Blockeds。
3. 静止就绪→活动就绪。处于 Readys 状态的进程,若用激活原语 Active 激活后,该进程将转变为 Readya 状态。
4. 静止阻塞→活动阻塞。处于 Blockeds 状态的进程,若用激活原语 Active 激活后,该进程将转变为 Blockeda 状态。
### 创建状态、终止状态
1. 创建状态:创建一个进程一般要通过两个步骤:首先,为一个新进程创建 PCB并填写必要的管理信息其次把该进程转入就绪状态并插入就绪队列之中。
2. 终止状态;进程的终止也要通过两个步骤:首先等待操作系统进行善后处理;然后将其 PCB 清零,并将 PCB 空间返还系统。
### 5态模型的转换创建、终止
![](image/2021-03-30-12-42-15.png)
### 7态模型的转换
![](image/2021-03-30-12-42-58.png)
## 6 进程控制块
### 进程控制块定义
* PCBProcess Control Block记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
* OS用于管理进程物理实体刻画进程的执行现状控制进程的执行。
### 进程控制块信息
* 进程标识信息(用于唯一的标识进程)
1. 系统进程标识符(操作系统分配的进程标识符)
2. 用户进程标识符(用户自定义的进程名、进程组名)
* 进程状态信息(用于存放该进程运行时的处理器现场信息)
1. 寄存器内容
1. 用户可见寄存器内容:数据寄存器、地址寄存器
2. 控制与状态寄存器内容程序计数器PC、指令寄存器IR、程序状态字PSW
2. 核心栈与用户栈指针内容
* 进程控制信息:用于存放与管理、调度进程相关的信息
1. 进程组成信息:代码/数据地址、外存映像地址
2. 进程通信信息:消息队列、信号量、锁
3. 进程权限信息:内存访问权限、处理器特权
4. 处理器使用信息:占用的处理器、时间片、处理器使用事件/已执行总时间、记账信息
5. 资源清单信息:正占用的资源、已使用的资源
* 进程调度信息
1. 进程状态,指明进程的当前状态,作为进程调度和对换时的依据;
2. 进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级高的进程应优先获得处理机;
3. 进程调度所需的其它信息,它们与所采用的进程调度算法有关,比如,进程已等待 CPU 的时间总和、进程已执行的时间总和等;
4. 事件,指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
### 进程控制块的组织方式
1. 链接方式这是把具有同一状态的 PCB用其中的链接字链接成一个队列。这样可以形成就绪队列、若干个阻塞队列和空白队列等。
![](image/2021-03-30-12-57-26.png)
2. 索引方式系统根据所有进程的状态建立几张索引表。例如,就绪索引表、阻塞索引表等。
![](image/2021-03-30-12-58-03.png)

View File

@@ -0,0 +1,91 @@
# 2.2 进程控制
> 上一节主要从状态的角度介绍了静态的状态有哪些。这一节朱啊哟介绍状态之间如何切换,引起切换的原因,和切换的步骤。
> 7中状态主要包括创建、终止、就绪、阻塞、执行、静态阻塞、静态就绪。
> 状态转换主要包括:创建、终止(释放)、阻塞、唤醒、调度、挂起、激活。
![](image/2021-03-29-22-54-26.png)
## 1 进程的创建
### 进程创建示意图
* 进程图是用于描述一个进程的家族关系的有向树。为了标识进程之间的家族关系,在 PCB 中都设置了家族关系表项,以标明自己的父进程及所有的子进程。
![](image/2021-03-30-13-01-17.png)
### 引起进程创建的事件
* 用户登录
* 作业调度
* 提供服务
* 应用请求
### 进程创建过程
1. 申请空白的PCB。获得操作系统唯一的表示信息。
2. 为进程分配资源。程序和数据以及用户栈分配必要的内存空间。
3. 初始化进程控制块。包括标识信息、处理机状态信息、处理机控制信息。
4. 将新进程插入就绪队列。
## 2 进程的终止
### 引起进程终止的事件
* 正常结束。一个用于表示进程已经运行完成的指示,产生一个中断,去通知 OS 进程已运行完毕。
* 异常结束。在进程运行期间,由于出现某些错误和故障而迫使进程终止:越界、非法访问。
* 外界干预。指进程应外界的请求而终止运行。
### 进程终止过程
1. 根据被终止进程的标识符,从 PCB 集合中检索出该进程的 PCB从中读出该进程的状态。
2. 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度。
3. 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防它们成为不可控的进程。
4. 将被终止进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
5. 将被终止进程(PCB)从所在队列(或链表)中移出,等待其他程序来搜集信息。
## 3 进程的阻塞与唤醒
### 引起进程阻塞与唤醒的事件
* 请求系统服务。
* 资源未被满足。
* 没有任务可做。
### 进程阻塞过程
1. 进程无法继续执行,于是进程便通过调用阻塞原语 block 把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。
2. 把进程控制块中的现行状态由“执行”改为“阻塞”,并将 PCB 插入阻塞队列。
### 进程唤醒过程
1. 当被阻塞进程所期待的事件出现时,如 I/O 完成或其所期待的数据已经到达,则由有关进程(比如用完并释放了该 I/O 设备的进程)调用唤醒原语 wakeup( ),将等待该事件的进程唤醒。
2. 将其PCB 中的现行状态由阻塞改为就绪,然后再将该 PCB 插入到就绪队列中。
## 4 进程的挂起与激活
### 引起进程挂起和激活的原因
* 用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起,系统将利用挂起原语 suspend( )将指定进程或处于阻塞状态的进程挂起。
* 当发生激活进程的事件时,系统将利用激活原语 active( )将指定进程激活。
### 进程挂起的过程
1. 首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪; 对于活动阻塞状态的进程,则将之改为静止阻塞。为
2. 了方便用户或父进程考查该进程的运行情况而把该进程的 PCB 复制到某指定的内存区域。
3. 最后,若被挂起的进程正在执行,则转向调度程序重新调度
### 进程激活的过程
1. 激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞,便将之改为活动阻塞。
## 5 注意事项
- 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。

View File

@@ -0,0 +1,320 @@
## 2.3 进程同步
## 1 进程同步的基本概念
### 两种制约关系
> 针对两种制约关系,合作制约关系和互斥制约关系,需要通过同步机制实现。
- 直接制约关系(合作)。由于多个进程相互合作产生,使得进程有一定的先后执行关系。
- 间接制约关系(互斥)。由于多个进程资源共享产生,多个进程在同一时刻只有一个进程能进入临界区。
### 临界资源和临界区
* 共享的互斥资源称为**临界资源**。
* 对临界资源进行访问的那段代码称为**临界区**。
* 为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。
```
repeat
entry section
critical section;//临界区
exit section
until false
```
### 同步机制的规则
1. **空闲让进**。当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效地利用临界资源。
2. **忙则等待**。当已有进程进入临界区时,表明临界资源正在被访问,因而其它试图进入临界区的进程必须等待,以保证对临界资源的互斥访问。
3. **有限等待**。对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”状态。
4. **让权等待**。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。
## 2 信号量机制
### 信号量
* 信号量Semaphore是一个整型变量可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
- **down/wait** :如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0进程睡眠等待信号量大于 0
- **up/signal** :对信号量执行 +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);
}
```
## 3 管程
### 管程定义
* 使用信号量机制实现同步机制,使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
* 利用共享数据结构抽象地表示系统中的共享资源,而把对该共享数据结构实施的操作定义为一组过程,如资源的请求和释放过程 request 和 release。进程对共享资源的申请、释放和其它操作都是通过这组过程对共享数据结构的操作来实现的这组过程还可以根据资源的情况或接受或阻塞进程的访问确保每次仅有一个进程使用共享资源这样就可以统一管理对共享资源的所有访问实现进程互斥。
### 管程组成
* 管程的名称;
* 局部于管程内部的共享数据结构说明;
* 对该数据结构进行操作的一组过程;
* 对局部于管程内部的共享数据设置初始值的语句。
![](image/2021-03-30-13-44-03.png)
### 管程实现
* 管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
* 管程引入了条件变量**condition**以及相关的操作**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。
* c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。
```pascal
monitor ProducerConsumer
integer i;
condition c;
procedure insert();
begin
// ...
end;
procedure remove();
begin
// ...
end;
end monitor;
```
### 管程与线程对比
1. 虽然二者都定义了数据结构,但进程定义的是私有数据结构 PCB管程定义的是公共数据结构如消息队列等
2. 二者都存在对各自数据结构上的操作,但进程是由顺序程序执行有关的操作,而管程主要是进行同步操作和初始化操作;
3. 设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题;
4. 进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程则为主动工作方式;
5. 进程之间能并发执行,而管程则不能与其调用者并发;
6. 进程具有动态性,由“创建”而诞生,由“撤销”而消亡,而管程则是操作系统中的一个资源管理模块,供进程调用。
### 使用管程实现生产者-消费者问题
```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;
```
## 5.1 经典同步问题——生产者消费者问题
### 问题描述
* 使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。
### 问题分析
* 因为缓冲区属于临界资源,因此需要使用一个互斥量 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);
}
}
```
## 5.2 经典同步问题——哲学家进餐问题
### 问题描述
* 五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。
![](image/2021-03-30-08-48-07.png)
### 问题分析
* 下面是一种错误的解法,如果所有哲学家同时拿起左手边的筷子,那么所有哲学家都在等待其它哲学家吃完并释放自己手中的筷子,导致死锁。
```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]);
}
}
```
## 5.3 经典同步问题——读者-写者问题
### 问题描述
* 允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。
### 问题分析
* 一个整型变量 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);
}
}
```

View File

@@ -0,0 +1,159 @@
# 2.4 进程通信
## 0 进程通信的定义
### 进程通信和进程同步的区别
* 主要的区别在于:进程同步控制多个进程按一定顺序执行;进程通信,实现进程间信息交换。
* 进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。
* 信号量机制既能作为一种同步工具,也能做为一种通信工具。信号量机制作为同步工具是卓有成效的,但作为通信工具,则不够理想,主要表现在下述两方面:
* 效率低,生产者每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;
* 通信对用户不透明。
## 1 进程通信的类型
### 进程通信的原理划分
* 低级进程通信机制:由于进程的互斥和同步,需要在进程间交换一定的信息,故不少学者将它们也归为进程通信。只能传递状态和整数值(控制信息)。特点:传送信息量小,效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。编程复杂:用户直接实现通信的细节,容易出错。
* 高级进程通信机制。提高信号通信的效率,传递大量数据,减轻程序编制的复杂度。
* 共享内存通信
* 消息传递通信
* 共享文件通信
### 进程通信的实现划分
* 基础进程通信机制
* 条件变量、信号量、管程
* 共享内存通信机制
* 通过信号量控制共享内存。
* 消息传递通信机制
* IPC消息队列
* 管道文件通信机制(文件进程通信机制)
* PIPE管道
* FIFO命名管道
* 网络进程通信机制
* socket
![](image/2021-03-30-15-24-40.png)
## 2 共享内存通信
### 类型
* **共享存储器系统(Shared-Memory System)** 相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。据此,又可把它们分成以下两种类型:
* **基于共享数据结构的通信方式**。在这种通信方式中,要求诸进程公用某些数据结构。借以实现诸进程间的信息交换。如在生产者—消费者问题中,就是用有界缓冲区这种数据结构来实现通信的。
* **基于共享存储区的通信方式**。为了传输大量数据,在存储器中划出了一块共享存储区,诸进程可通过对共享存储区中数据的读或写来实现通信。
## 3 消息传递通信
### 定义
* **消息传递系统(Message passing system)** 是当前应用最为广泛的一种进程间的通信机制。在该机制中,进程间的数据交换是以格式化的消息(message)为单位的;
* 在计算机网络中,又把 message 称为报文。程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且还隐藏了通信的实现细节,使通信过程对用户是透明的,从而大大减化了通信程序编制的复杂性,因而获得了广泛的应用。
* 消息传递系统的通信方式属于高级通信方式。又因其实现方式的不同而进一步分成**直接通信方式**和**间接通信方式**两种。
> 有博客说明。消息传递直接通信包括PIPE、FIFO、IPC三种通信方式。分别是管道通信、命名管道通信、消息队列通信。
### 直接通信方式
* 发送进程利用 OS 所提供的发送命令,直接把消息发送给目标进程。
```
Send(Receivermessage) 发送一个消息给接收进程;
Receive(Sendermessage) 接收 Sender 发来的消息;
```
### 间接通信方式
* 间接通信方式指进程之间的通信需要通过作为共享数据结构的实体。该实体用来暂存发送进程发送给目标进程的消息;接收进程则从该实体中取出对方发送给自己的消息。
* 中间实体称为信箱。消息在信箱中可以安全地保存,只允许核准的目标用户随时读取。因此,利用信箱通信方式,既可实现实时通信,又可实现非实时通信。
* 信箱的创建和撤消。进程可利用信箱创建原语来建立一个新信箱。创建者进程应给出信箱名字、信箱属性(公用、私用或共享);对于共享信箱,还应给出共享者的名字。当进程不再需要读信箱时,可用信箱撤消原语将之撤消。
* 消息的发送和接收。当进程之间要利用信箱进行通信时,必须使用共享信箱,并利用系统提供的下述通信原语进行通信:
```
Send(mailboxmessage) 将一个消息发送到指定信箱;
Receive(mailboxmessage) 从指定信箱中接收一个消息;
```
### 间接通信信箱
* 私用信箱。用户进程可为自己建立一个新信箱,并作为该进程的一部分。信箱的拥有者有权从信箱中读取消息,其他用户则只能将自己构成的消息发送到该信箱中。
* 公用信箱。它由操作系统创建,并提供给系统中的所有核准进程使用。核准进程既可把消息发送到该信箱中,也可从信箱中读取发送给自己的消息。
* 共享信箱它由某进程创建,在创建时或创建后指明它是可共享的,同时须指出共享进程(用户)的名字
### 消息传递系统中实现的若干问题。
1. 通信链路。为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路(communication link)。单向通信链路、双向通信链路。点对点通信链路、多点通信链路。
2. 消息的格式.在消息传递系统中所传递的消息,必须具有一定的消息格式。
3. 进程同步方式。在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程,还是接收进程,在完成消息的发送或接收后,都存在两种可能性,继续或阻塞。
1. 发送进程阻塞,接收进程阻塞。这种情况主要用于进程之间紧密同步(tightsynchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合(rendezrous)。
2. 发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标; 而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒。在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,处于阻塞收状态。
3. 发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能接纳 n 个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进程也可以连续地从消息队列中取得消息,也不必等待。
## 4 消息传递通信机制的实例——IPC消息队列通信
### 实现方式
* 属于消息通信的直接通信方式。
![](image/2021-03-30-14-44-16.png)
* 消息缓冲队列通信机制中的数据结构消息缓冲区在消息缓冲队列通信方式中主要利用的数据结构是消息缓冲区PCB 中有关通信的数据项。
* 发送进程在利用发送原语发送消息之前,应先在自己的内存空间设置一发送区。把待发送的消息正文、发送进程标识符、消息长度等信息填入其中,然后调用发送原语,把消息发送给目标(接收)进程。
* 接收原语。接收进程调用接收原语 receive(b),从自己的消息缓冲队列 mq 中摘下第一个消息缓冲区 i并将其中的数据复制到以 b 为首址的指定消息接收区内。
### 相比于 FIFO消息队列具有以下优点
- 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
## 5 管道通信机制的实例——PIPE管道通信
### 管道定义
* **“管道”** 是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名 pipe 文件,基于文件的通信机制。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。由于发送进程和接收进程是利用管道进行通信的,故又称为管道通信。
* 有部分博客说。管道通信也属于消息通信中的直接通信的一种。
### 管道实现
* 管道是通过调用 pipe 函数创建的fd[0] 用于读fd[1] 用于写。
```c
#include <unistd.h>
int pipe(int fd[2]);
```
* 它具有以下限制:
- 只支持半双工通信(单向交替传输);
- 只能在父子进程或者兄弟进程中使用。
![](image/2021-03-30-08-58-55.png)
## 6 管道通信机制的实例——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 用作汇聚点,在客户进程和服务器进程之间传递数据。
![](image/2021-03-30-08-59-57.png)
### 6.2 消息队列
### 6.3 Socket套接字
* 与其它通信机制不同的是,它可用于不同机器间的进程通信。

View File

@@ -0,0 +1,62 @@
## 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. 拥有资源。进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
4. 系统开销。由于创建或撤销进程时系统都要为之分配或回收资源如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
5. 通信方面。线程间可以通过直接读写同一进程中的数据进行通信但是进程通信需要借助IPC。
## 2 线程属性
### 属性
1. 轻型实体
2. 独立调度和分派的基本单位
3. 可并发执行
4. 共享进程资源
### 状态参数
* 寄存器状态
* 堆栈状态
* 线程运行状态
* 优先级
* 信号屏蔽
### 运行状态
* 执行状态
* 就绪状态
* 阻塞状态
* 创建状态
* 终止状态
## 3 线程的同步和通信
### 互斥锁
### 条件变量
### 信号量
## 4 线程的实现方式
### 内核线程
### 用户线程

View File

@@ -1,32 +1,57 @@
# 处理器
## 处理器调度的层次
### 高级调度(长程调度,作业调度):决定能否加入到执行的进程池中
> ## 目录
> 1. 处理机调度的层次
> 2. 调度队列模型和调度准则
> 3. 调度算法
> 4. 实时调度
> 5. 产生死锁
> 6. 预防死锁
> 7. 死锁的检测与解除
1. 分时OS中高级调度决定
1. 是否接受一个终端用户的连接
2. 命令能否被系统接纳并构成进程
3. 新建态进程是否加入就绪进程队列
2. 批处理OS中功能是按照某种原则从后备作业队列中选取作业进入主存并为作业做好运行前的准备工作和完成后的善后工作
### 中级调度(平衡负载调度):决定主存中的可用进程集合
1. 引起中级调度是为了提高内存利用率和作业吞吐量
2. 中级调度决定哪些进程被运行驻留在主存中参与竞争处理器及其他资源,起到短期调整系统负荷的作业
3. 中级调度把一些进程换出内存,从而实质进入“挂起”状态,不参与进程调度,以平顺系统的负载
## 1 处理器调度的层次
### 高级调度(长程调度,作业调度)
### 低级调度(短程调度,进程调度,处理器调度):决定哪个可用进程占用处理器执行
* **主要功能**。是根据某种算法,把外存上处于后备队列中的那些作业调入内存,也就是说,它的调度对象是作业。
* **作业(Job)** 。作业是一个比程序更为广泛的概念,它不仅包含了通常的程序和数据,而且还应配有一份作业说明书,系统根据该说明书来对程序的运行进行控制。在批处理系统中,是以作业为基本单位从外存调入内存的。
* **作业步(Job Step)** 。作业部类似于操作系统命令脚本。在作业运行期间,每个作业都必须经过若干个相对独立,又相互关联的顺序加工步骤才能得到结果,我们把其中的每一个加工步骤称为一个作业步。例如编译执行过程:
1. “编译”作业步,通过执行编译程序对源程序进行编译,产生若干个目标程序段;② “连结装配”作业步,将“编译”作业步所产生的若干个目标程序段装配成可执行的目标程序;
2. “运行”作业步,将可执行的目标程序
3. 读入内存并控制其运行。
* **作业控制块 JCB(Job Control Block)** 。为了管理和调度作业,在多道批处理系统中为每个作业设置了一个作业控制块。
* **作业调度**。作业调度的主要功能是根据作业控制块中的信息,审查系统能否满足用户作业的资源需求,以及按照一定的算法,从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。
1. 按照某种原则把处理器分配给就绪态进程或KLT
2. 进程调度程序(分派程序),操作系统中实现处理器调度的程序,是操作系统的最核心部分
3. 处理器调度策略的优劣直接影响到整个系统的性能
4. 功能:
1. 记住进程或内核级线程的状态
2. 决定某个进程或KLT什么时候获得处理器以及占用多长时间
3. 把处理器分配给进程或KLT
4. 收回处理器
### 中级调度(平衡负载调度):
## 2 选择处理器调度算法的原则
* **主要功能** 。引起中级调度是为了提高内存利用率和作业吞吐量
* 中级调度决定哪些进程被运行驻留在主存中参与竞争处理器及其他资源,起到短期调整系统负荷的作业
* 中级调度把一些进程换出内存,从而实质进入“挂起”状态,不参与进程调度,以平顺系统的负载
### 低级调度(短程调度,进程调度,处理器调度)
* **主要功能** 通常也把低级调度(Low Level Scheduling) 称为 进程调度或短程调度(ShortTerm Scheduling),它所调度的对象是进程(或内核级线程)。保存处理机的现场信息、按某种算法选取进程、把处理器分配给进程。
* 基本机制:
* **排队器**。为了提高进程调度的效率,应事先将系统中所有的就绪进程按照一定的方式排成一个或多个队列,以便调度程序能最快地找到它。
* **分派器(分派程序)**。分派器把由进程调度程序所选定的进程,从就绪队列中取出该进程,然后进行上下文切换,将处理机分配给它。
* **上下文切换机制** 。当对处理机进行切换时,会发生两对上下文切换操作。
* 进程调度的方式
* **非抢占方式(Nonpreemptive Mode)** 。决不会因为时钟中断等原因而抢占正在运行进程的处理机。
* **抢占方式(Preemptive Mode)**。这种调度方式允许调度程序根据某种原则去暂停某个正在执行的进程,将已分配给该进程的处理机重新分配给另一进程。优先权原则、短作业优先原则、时间片原则。
## 2 调度队列模型和调度准则
### 调度队列模型
1. 仅有低级调度的队列模型
![](image/2021-03-30-16-01-47.png)
2. 有高级调度和低级调度的队列模型
![](image/2021-03-30-16-02-04.png)
3. 同时具有三级调度的队列模型
![](image/2021-03-30-16-02-17.png)
### 调度准则
1. 资源利用率使得CPU或其他资源的使用率尽可能高且能够并行工作
2. 响应时间:使交互式用户的响应时间尽可能小,或尽快处理实时任务
@@ -34,35 +59,39 @@
4. 吞吐量:单位时间处理的进程数尽可能多
5. 公平性确保每个用户每个进程获得合理的CPU份额或其他资源份额
## 3 优先数调度算法
### 根据分配给进程的优先数决定运行进程
## 3.1 调度算法
1. 抢占式优先数调度算法
2. 非抢占式优先数调度算法
### 先来先服务调度算法FCFS
### 优先数的确定准则
1. 进程负担任务的紧迫程度
2. 进程的交互性
3. 进程使用外设的频度
4. 进程进入系统的时间长短
* 每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。
* FCFS 算法比较有利于长作业(进程),而不利于短作业(进程)。
### 与进入系统时间相关的优先数
### 短作业优先调度算法SJB
* 短作业(进程)优先调度算法 SJ(P)F是指对短作业或短进程优先调度的算法。短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。
* SJF 调度算法能有效地降低作业的平均等待时间,提高系统吞吐量。
1. 计算时间短(作业/进程)优先
2. 剩余计算时间短进程优先
3. 响应比高者(作业/进程)优先
1. 响应比=等待时间/进入时间
4. 先来先服务:先进入先被选择
1. 多用于高级调度;低级调度中,以计算为主的进程过于优越
### 优先权调度算法
* 根据分配给进程优先数决定运行进程。可以分为以下两种方式:
1. 抢占式优先数调度算法
2. 非抢占式优先数调度算法
* 优先数的确定准则
1. 进程负担任务的紧迫程度
2. 进程的交互性
3. 进程使用外设的频度
4. 进程进入系统的时间长短
* 与进入系统时间相关的优先权
1. 计算时间短(作业/进程)优先
2. 剩余计算时间短进程优先
3. 响应比高者(作业/进程)优先
4. 先来先服务:先进入先被选择
## 4 其他算法
### 时间片轮转调度算法
1. 根据各个进程进入就绪队列的时间先后轮流占用CPU一个时间片
2. 时间片中断
3. 时间片的确定:选择长短合适的时间片,过长则退化为先来先服务算法,过短则调度开销大
4. 单时间片,多时间片和动态时间片
1. 根据各个进程进入就绪队列的时间先后轮流占用CPU一个时间片.在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,并令其执行一个时间片。
### 分级调度算法(多队列策略,反馈循环队列)
@@ -82,3 +111,177 @@
1. 为进程发放针对系统各种资源如CPU时间的彩票当调度程序需要做出决策时随机选择一张彩票持有该彩票的进程将获得系统资源
2. 合作进程之间的彩票交换
## 3.2 进程调度算法
* 不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。
### 批处理系统
* 批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。
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 实时调度
* 实时系统要求一个请求在一个确定时间内得到响应。分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。
### 实现实时调度的基本条件
* 提供必要的信息:就绪时间、开始截止时间、处理时间、资源要求、优先级。
* 系统处理能力强
* 采用抢占式调度算法
* 具有快速切换机制。
### 具体算法
1. 最早截止时间优先即 EDF(Earliest Deadline First)算法
2. 最低松弛度优先即 LLF(Least Laxity First)算法
## 5 死锁
### 必要条件
![](image/2021-03-30-16-16-44.png)
- **互斥条件**:每个资源要么已经分配给了一个进程,要么就是可用的。
- **占有等待条件**:已经得到了某个资源的进程可以再请求新的资源。
- **不可抢占条件**:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
- **环路等待条件**:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
### 处理方法
- 预防死锁
- 检测死锁
- 避免死锁
- 解除死锁
## 6 预防死锁
> 在程序运行之前预防发生死锁。
### 破坏互斥条件
* 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
### 破坏占有和等待条件
* 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
### 破坏不可抢占条件
### 破坏环路等待
* 给资源统一编号,进程只能按编号顺序来请求资源。
## 7 死锁避免
> 在程序运行时避免发生死锁。
### 安全状态
![](image/2021-03-30-16-21-40.png)
* 图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b运行结束后释放 B此时 Free 变为 5图 c接着以同样的方式运行 C 和 A使得所有进程都能成功运行因此可以称图 a 所示的状态时安全的。
* 定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
* 安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
### 单个资源的银行家算法
* 一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
![](image/2021-03-30-16-22-05.png)
* 上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
### 多个资源的银行家算法
![](image/2021-03-30-16-22-26.png)
* 上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
* 检查一个状态是否安全的算法如下:
- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行那么系统将会发生死锁状态是不安全的。
- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
如果一个状态不是安全的,需要拒绝进入这个状态。
## 8 检测与解除死锁
> 不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
### 每种类型一个资源的死锁检测
![](image/2021-03-30-16-23-07.png)
* 上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
* 图 a 可以抽取出环,如图 b它满足了环路等待条件因此会发生死锁。
* 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
### 每种类型多个资源的死锁检测
![](image/2021-03-30-16-23-30.png)
* 上图中,有三个进程四个资源,每个数据代表的含义如下:
- 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. 每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。
1. 寻找一个没有标记的进程 P<sub>i</sub>,它所请求的资源小于等于 A。
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。
### 死锁恢复
- 利用抢占恢复
- 利用回滚恢复
- 通过杀死进程恢复

View File

@@ -1,390 +1,15 @@
# 存储管理
## 1 逻辑地址(相对地址):用户编程所使用的地址空间
### 逻辑地址从0开始编号两种形式
1. 一维逻辑地址(地址)
2. 二维逻辑地址(段号:段内地址)
> ## 目录
>
> 1. 存储器的层次结构
> 2. 程序的装载和链接
> 3. 连续分配方式
> 4. 分页存储管理方式
> 5. 分段存储管理方式
> 6. 虚拟存储器的基本概念
> 7. 请求分页存储管理的方式
> 8. 页面置换算法
> 9. 请求分段存储管理的方式
### 段式程序设计
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,135 @@
## 1 存储器的层次结构
### 存储器的层次结构
![](image/2021-03-30-18-48-38.png)
### 寄存器
* 寄存器访问速度最快,完全能与 CPU 协调工作。寄存器用于加速存储器的访问速度,如用寄存器存放操作数,或用作地址寄存器加快地址转换速度等。
### 高速缓存
1. Cache是介于CPU和主存储器间的高速小容量存储器由静态存储芯片SRAM组成容量较小但比主存DRAM技术更加昂贵而快速接近于CPU的速度
2. CPU往往需要重复读取同样的数据库Cache的引入与缓存容量的增大可以大幅提升CPU内部读取数据的命中率从而提高系统性能
3. 分级由于CPU芯片面积和成本Cache很小。根据成本控制划分L1L2L3三级。
1. L1 Cache分为数据缓存和指令缓存内置成本最高对CPU的性能影响最大通常在32KB-256KB之间
2. L2 Cache分内置和外置两种后者性能低一些通常在512KB-8MB之间
3. L3 Cache多为外置在游戏和服务器领域有效但对很多应用来说总线改善比设置L3更加有利于提升系统性能
### 主存储器
* 主存储器(简称内存或主存)是计算机系统中一个主要部件,用于保存进程运行时的程序和数据,也称可执行存储器。数据能够从主存储器读取并将它们装入到寄存器中,或者从寄存器存入到主存储器。
### 磁盘缓存
* 由于目前磁盘的 I/O 速度远低于对主存的访问速度,因此将频繁使用的一部分磁盘数据和信息,暂时存放在磁盘缓存中,可减少访问磁盘的次数。
### 磁盘
* 大容量存储设备
* 断电数据保留
### 可移动介质
* 便携式存储设备
## 2 程序的装入和链接
### 程序执行的步骤
1. 首先是要**编译**,由编译程序(Compiler)将用户源代码编译成若干个目标模块(Object Module)
2. 其次是**链接**,由链接程序(Linker)将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load Module)
3. 最后是**装入**,由装入程序(Loader)将装入模块装入内存。然后执行
![](image/2021-03-30-18-57-02.png)
## 2.1 装入
### 逻辑地址(相对地址)
* 从0开始编号在编译生成可执行文件的时候确定。包括以下两种形式
* 一维逻辑地址(地址)
* 二维逻辑地址(段号:段内地址)
### 物理地址(绝对地址)
* 程序执行所使用的主存地址空间。处理器执行指令时按照物理地址进行
### 地址转换(重定位)
* 把逻辑地址转换成物理地址。包括两种方式:
* 静态重定位在程序装入内存时进行地址转换。由装入程序执行早期小型OS使用
* 动态重定位在CPU执行程序时进行地址转换。从效率出发依赖硬件地址转换机构
### 分类
* 根据地址转换的方式,可以将程序的装入分为三种方式。
1. **绝对装入方式**。在编译时,如果知道程序将驻留在内存的什么位置,那么,编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。装入模块被装入内存后,由于程序中的逻辑地址与实际内存地址完全相同,故不须对程序和数据的地址进行修改。程序中所使用的绝对地址,既可在编译或汇编时给出,也可由程序员直接赋予。
2. **静态重定位装入方式**。在装入时对目标程序中指令和数据的修改过程称为重定位。又因为地址变换通常是在装入时一次完成的,以后不再改变,故称为静态重定位。
3. **动态重定位装入方式**。动态运行时的装入程序在把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是相对地址。需要一个重定位寄存器的支持。
## 2.2 链接
### 编译系统
* 以下是一个 hello.c 程序:
```c
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
```
* 在 Unix 系统上,由编译器把源文件转换为目标文件。
```bash
gcc -o hello hello.c
```
![](image/2021-03-30-18-59-26.png)
1. 预处理阶段:处理以 # 开头的预处理命令;
1. 编译阶段:翻译成汇编文件;
1. 汇编阶段:将汇编文件翻译成**可重定位目标文件**
2. 链接阶段将可重定位目标文件和printf.o 等单独预编译好的目标文件进行合并,得到最终的**可执行目标文件**。
### 目标文件
- **可执行目标文件**:可以直接在内存中执行;
- **可重定位目标文件**:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件;
- **共享目标文件**:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接;
### 分类
* 源程序经过编译后,可得到一组目标模块,再利用链接程序将这组目标模块链接,形成装入模块。根据链接时间的不同,可把链接分成如下三种:
1. 静态链接。在程序运行之前,先将各目标模块及它们所需的库函数,链接成一个完整的装配模块,以后不再拆开。我们把这种事先进行链接的方式称为静态链接方式。
2. 装入时动态链接。这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的链接方式。
3. 运行时动态链接。这是指对某些目标模块的链接,是在程序执行中需要该(目标)模块时,才对它进行的链接。
![](image/2021-03-30-19-20-08.png)
### 静态链接
* 静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
1. 符号解析:符号解析的目的是将每个符号引用与一个符号定义关联起来。每个符号对应于一个函数、一个全局变量或一个静态变量。
2. 重定位:链接器通过把每个符号定义与一个内存位置关联起来。然后修改所有对这些符号的引用,使得它们指向这个内存位置。
![](image/2021-03-30-19-00-50.png)
### 动态链接
* 静态库有以下两个问题:
- 当静态库更新时那么整个程序都要重新进行链接;
- 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。
* 共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示Windows 系统上它们被称为dll。它具有以下特点
- 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
- 在内存中,一个共享库的字节码(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。
![](image/2021-03-30-19-01-15.png)

View File

@@ -0,0 +1,47 @@
# 4.2 连续分配方式存储管理
> 存储管理方式。内存的分配方式.
### 概念
* 每个进程占用一个物理上完全连续的存储空间(区域)
## 1 单一连续分配
* 只能用于单用户、单任务的操作系统中。
* 把内存分为系统区和用户区两部分.系统区仅提供给 OS 使用,通常是放在内存的低址部分;用户区是指除系统区以外的全部内存空间,提供给用户使用。
* 通常采用静态重定位的方式装入程序。
## 2 固定分区分配
* 这是将内存用户空间划分为若干个固定大小的区域,在每个分区中只装入一道作业,这样,把用户空间划分为几个分区,便允许有几道作业并发运行。
## 3 动态分区分配
> 分区分配中所用的**数据结构**、**分区分配算法**和**分区的分配与回收操作**这样三个问题。
### 数据结构:
* 空闲分区表。在系统中设置一张空闲分区表,用于记录每个空闲分区的情况。每个空闲分区占一个表目,表目中包括分区序号、分区始址及分区的大小等数据项。
* 空闲分区链。为了实现对空闲分区的分配和链接,在每个分区的起始部分,设置一些用于控制分区分配的信息,以及用于链接各分区所用的前向指针;在分区尾部则设置一后向指针,通过前、后向链接指针,可将所有的空闲分区链接成一个双向链,
### 分区分配算法
* 首次适应算法(first fit) 。FF 算法要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;然后再按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。
* 循环首次适应算法(next fit) 是从上次找到的空闲分区的下一个空闲分区开始查找,直至找到一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。
* 最佳适应算法(best fit) 总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。
* 最坏适应算法(worst fit)最坏适应分配算法要扫描整个空闲分区表或链表,总是挑选一个最大的空闲区分割给作业使用,其优点是可使剩下的空闲区不至于太小,产生碎片的几率最小,对中、小作业有利,同时最坏适应分配算法查找效率很高。
* 快速适应算法(quick fit)该算法又称为分类搜索法,是将空闲分区根据其容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这样,系统中存在多个空闲分区链表,同时在内存中设立一张管理索引表,该表的每一个表项对应了一种空闲分区类型,并记录了该类型空闲分区链表表头的指针。
### 分区的分配与回收操作
* 分配内存。系统应利用某种分配算法,从空闲分区链(表)中找到所需大小的分区。
![](image/2021-03-30-20-03-39.png)
* 回收内存。当进程运行完毕释放内存时,系统根据回收区的首址,从空闲区链(表)中找到相应的插入点,并与相邻空闲分区合并。
## 4 可重定位的分区分配
### 动态重定位的引入。
* 在连续分配方式中,必须把一个系统或用户程序装入一连续的内存空间。
* 将内存中的所有作业进行移动,使它们全都相邻接,这样,即可把原来分散的多个小分区拼接成一个大分区,这时就可把作业装入该区。这种通过移动内存中作业的位置,以把原来多个分散的小分区拼接成一个大分区的方法,称为“拼接”或“紧凑”,需要对其中的程序进行重定位。
### 动态重定位的实现
* 增设一个重定位寄存器,用它来存放程序(数据)在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。
![](image/2021-03-30-20-08-54.png)

View File

@@ -0,0 +1,201 @@
# 4.2 分页存储管理方式
## 1 页面与页表
### 页面和物理块
* 将一个进程的逻辑地址空间分成若干个大小相等的片,称为**页面或页**,为各页加以编号。
* 把内存空间分成与页面相同大小的若干个存储块,称为 **(物理)块或页框(frame)** ,也同样为它们加以编号。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。
* 由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“**页内碎片**”。
* 页面若太小,一方面虽然可使内存碎片减小,从而减少了内存碎片的总空间,有利于提高内存利用率,但另一方面也会使每个进程占用较多的页面,从而导致进程的页表过长,占用大量内存;此外,还会降低页面换进换出的效率。然而,如果选择的页面较大,虽然可以减少页表的长度,提高页面换进换出的速度,但却又会使页内碎片增大。
### 地址结构
* 前一部分为页号 P后一部分为位移量 W(或称为页内地址)。
![](image/2021-03-30-20-26-23.png)
### 页表
* 系统又为每个进程建立了一张页面映像表,简称页表。在进程地址空间内的所有页(0n),依次在页表中有一页表项,其中记录了相应页在内存中对应的物理块号。在配置了页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号。页表的作用是实现从**页号**到**物理块号**的**地址映射**。
![](image/2021-03-30-20-28-43.png)
## 2 地址变换结构
### 基本的地址变换结构
* 页表大多驻留在内存中。在系统中只设置一个页表寄存器 PTR(Page-Table Register),在其中存放页表在内存的始址和页表的长度。
* 进程未执行时,页表的始址和页表长度存放在本进程的 PCB 中。当调度程序调度到某进程时,才将这两个数据装入页表寄存器中。
![](image/2021-03-30-20-30-42.png)
### 具有快表的地址变换结构
* 地址变换机构中增设一个具有并行查寻能力的特殊高速缓冲寄存器,又称为“联想寄存器”(Associative Memory),或称为“快表”,
* 快表表项包括:页号,页架号。这种高速存储器是联想存储器,即按照内容寻址,而非按照地址访问。
* 在 CPU 给出有效地址后,由地址变换机构自动地将页号 P 送入高速缓冲寄存器,并将此页号与高速缓存中的所有页号进行比较,若其中有与此相匹配的页号,便表示所要访问的页表项在快表中。于是,可直接从快表中读出该页所对应的物理块号,并送到物理地址寄存器中。如在块表中未找到对应的页表项,则还须再访问内存中的页表,找到后,把从页表项中读出的物理块号送地址寄存器;同时,再将此页表项存入快表的一个寄存器单元中,亦即,重新修改快表。但如果联想寄存器已满,则 OS 必须找到一个老的且已被认为不再需要的页表项,将它换出。
![](image/2021-03-30-20-32-25.png)
### 基于快表的地址变换流程
1. 按逻辑地址中的页号查快表
2. 若该页已在快表中,则由物理块号和单元号形成绝对地址
3. 若该页不在快表中,则再查主存页表形成绝对地址,同时将该页登记到快表中
4. 当快表填满后,又要登记新页时,则需在快表中按一定策略淘汰一个旧登记项
### 地址转换代价
1. 页表放在主存:每次地址转换必须访问两次主存
1. 按页号读出页表中的相应物理块号
2. 按计算出来的绝对地址进行读写
2. 存在问题:降低了存取速度
3. 解决办法利用Cache存放部分页表
## 3 两级页表和多级页表
、、、、、、
## 4 页面置换算法
### 概述
1. 当主存空间已满而又需要装入新页时,页式虚拟存储管理必须按照一定的算法把已在主存的一些页调出去
2. 选择淘汰页的工作称为页面调度
3. 选择淘汰页的算法称为页面调度算法
4. 页面调度算法设计不当,会出现(刚被淘汰的页面立即又要调入,并如此反复)这种现象称为抖动或颠簸。
### 先进先出FIFO页面调度算法
1. 总是淘汰最先调入主存的那一页,或者说主存驻留时间最长的那一页(常驻的除外)
2. 模拟的是程序执行的顺序性,有一定合理性
### 最近最少用LRU页面调度算法
1. 淘汰最近一段时间较久未被访问的那一页,即那些刚被使用过的页面,可能马上还要被使用到
1. OPT页面调度算法
1. 当要调入新页面时,首先淘汰以后不再访问的页,然后选择距现在最长时间后再访问的页
2. 该算法由Belady提出称为Belady算法又称最佳算法OPTOptimal page replacement
3. OPT只可模拟不可实现
2. 模拟了程序执行的局部属性,既考虑了循环性又兼顾了顺序性
3. 严格实现的代价大(需要维持特殊队列)
4. 模拟实现:
1. 每页建一个引用标志,供硬件使用
2. 设置一个时间间隔中断中断时页引用标志置0
3. 地址转换时页引用标志置1
4. 淘汰页面时从页引用标志为0z的页中间随机选择
5. 时间间隔多长是个难点
### 最不常用LFU页面调度算法
1. 淘汰最近一段时间内访问次数较少的页面对OPT的模拟性比LRU更好
2. 基于时间间隔中断,并给每一页设置一个计数器
3. 时间间隔中断发生后所有计数器清0
4. 每访问页1次就给计数器加1
5. 选择计数值最小的页面淘汰
### 时钟CLOCK页面调度算法
1. 采用循环队列机制构造页面队列,形成了一个类似于钟表面的环形表
2. 队列指针则相当于钟表面上的表针,指向可能要淘汰的页面
3. 使用页引用标志位
4. 工作流程:
1. 页面调入主存时其引用标志位置1
2. 访问主存页面时其引用标志位置1
3. 淘汰页面时,从指针当前指向的页面开始扫描循环队列
1. 把所遇到的引用标志位是1的页面的引用标志位清0并跳过
2. 把所遇到的引用标志位是0的页面淘汰指针推进一步
## 5 反置页表
### 提出:
1. 页面及相关硬件机制在地址转换、存储保护、虚拟地址访问中发挥了关键作用
2. 为页式存储管理设置专门硬件机构
3. 内存管理单元MMUCPU管理虚拟/物理存储器的控制线路,把虚拟地址映射为物理地址,并提供存储保护,必要时确定淘汰页面
4. 反置页表IPTMMU用的数据结构
### 基本设计思想
1. 针对内存中的每个页架建立一个页表,按照块号排序
2. 表项包含:正在访问改页框的进程标识、页号及特征位和哈希链指针等
3. 用来完成内存页架到访问进程页号的对应,即物理地址到逻辑地址的转换
### 页表项
1. 页号:虚拟地址页号
2. 进程标志符:使用该页的进程号(页号和进程标志符结合起来标志一个特定进程的虚拟地址空间的一页)
3. 标志位:有效、引用、修改、保护的锁定等标志信息
4. 链指针:哈希链
### 基于反置页表的地址转换过程
1. MMU通过哈希表把进程标识和虚页号转换成一个哈希值指向IPT的一个表目
2. MMU遍历哈希链找到所需进程的虚页号该项的索引就是页架号通过拼接位移便可生成物理地址
3. 若遍历整个反置页表中未能找到匹配页表项,说明该页不在内存,产生缺页中断,请求操作系统调入
## 页面置换算法
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。
页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
### 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>

View File

@@ -0,0 +1,82 @@
# 4.4 分段存储管理方式
## 1 分段存储管理
### 引入
* 虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。
* 下图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。
![](image/2021-03-30-20-45-54.png)
* 分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。
![](image/2021-03-30-20-46-11.png)
### 概念
1. 每个程序可由若干段组成每一段都可以从“0”开始编址段内的地址是连续的
2. 分段存储器的逻辑地址由两部分组成,段号、单元号
### 基本思想
1. 段式存储管理基于可变分区存储管理实现,一个进程要占用多个分区
2. 硬件需要增加一组用户可见的段地址寄存器(代码段、数据段、堆栈段、附加段),供地址转换使用
3. 存储管理需要增加设置一个段表,每个段占用一个段表项,包括:段始址、段限长,以及存储保护、可移动、可扩充等标志位
## 2 分段管理基本原理
### 分段地址结构
* 地址结构
![](image/2021-03-30-20-47-34.png)
### 段表
* 每个分段分配一个连续的分区,而进程中的各个段可以离散地移入内存中不同的分区中。为使程序能正常运行,亦即,能从物理内存中找出每个逻辑段所对应的位置,应像分页系统那样,在系统中为每个进程建立一张段映射表,简称“段表”
* 每个段在表中占有一个表项,其中记录了该段在内存中的起始地址(又称为“基址”)和段的长度,如图 4-17 所示。段表可以存放在一组寄存器中,这样有利于提高地址转换速度,但更常见的是将段表放在内存中。
![](image/2021-03-30-20-49-06.png)
### 地址变换结构
* 为了实现从进程的逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表始址和段表长度 TL。在进行地址变换时系统将逻辑地址中的段号与段表长度TL 进行比较。
![](image/2021-03-30-20-50-13.png)
### 分页和分段的比较
* 相同点:
* 两者都采用离散分配方式,且都要通过地址映射机构来实现地址变换。
* 不同点
* 实现上:页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。或者说,分页仅仅是由于系统管理的需要而不是用户的需要。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。
* 页的大小固定且由系统决定,由系统把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而在系统中只能有一种大小的页面;而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。
* 分页的作业地址空间是一维的,即单一的线性地址空间,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间则是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
* 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。
### 段的共享
1. 通过不同进程段表中的项指向同一个段基址来实现
2. 对共享段的信息必须进行保护,如规定只能独处不能写入,不满足保护条件则产生保护中断
## 3 段页式存储管理
### 基本原理
1. 段式存储管理可以基于页式存储管理实现。是分段和分页原理的结合。
2. 每一段不必占据连续的存储空间,可存放在不连续的主存物理块中
3. 能够扩充为段页式虚拟存储管理
4. 装入部分段,或者装入段中部分页面
### 地址变换结构
* 先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。图 4-21 示出了一个作业地址空间的结构。该作业有三个段,页面大小为 4 KB。在段页式系统中其地址结构由段号、段内页号及页内地址三部分所组成。
![](image/2021-03-30-21-00-58.png)
### 地址变换过程
1. 首先利用段号 S将它与段表长 TL 进行比较。若 $S<TL$表示未越界,于是利用段表始址和段号来求出该段所对应的段表项在段表中的位置,从中得到该段的页表始址
2. 并利用逻辑地址中的段内页号 P 来获得对应页的页表项位置,从中读出该页所在的物理块号 b
3. 再利用块号 b 和页内地址来构成物理地址。
![](image/2021-03-30-21-08-09.png)
### 地址变换的代价
* 为了获得一条指令或数据,须三次访问内存。第一次访问是访问内存中的段表,从中取得页表始址;
* 第二次访问是访问内存中的页表,从中取出该页所在的物理块号,并将该块号与页内地址一起形成指令或数据的物理地址;
* 第三次访问才是真正从第二次访问所得的地址中,取出指令或数据。

View File

@@ -0,0 +1,41 @@
# 4.5 虚拟存储器的基本概念
## 1 虚拟存储器
### 引入
* 虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
* 为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
* 从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
![](image/2021-03-30-21-09-50.png)
### 概念
* 没有必要全部装入内存,仅须将那些当前要运行的少数页面或段先装入内存便可运行,其余部分暂留在盘上。
* 程序在运行时,如果它所要访问的页(段)已调入内存,便可继续执行下去;
* 但如果程序所要访问的页(段)尚未调入内存(称为缺页或缺段),此时程序应利用 OS 所提供的请求调页(段)功能,将它们调入内存,以使进程能继续执行下去。
* 如果此时内存已满,无法再装入新的页(段),则还须再利用页(段)的置换功能,将内存中暂时不用的页(段)调至盘上,腾出足够的内存空间后,再将要访问的页(段)调入内存,使程序继续执行下去。
* 虚拟存储器,是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储器系统。
## 2 虚拟存储器的实现
### 分页请求系统
* 增加了**请求调页功能**和**页面置换功**能所形成的页式虚拟存储系统。它允许只装入少数页面的程序(及数据),便启动运行。以后,再通过调页功能及页面置换功能,陆续地把即将要运行的页面调入内存,同时把暂不运行的页面换出到外存上。
* 请求分页
### 请求分段系统
* 这是在分段系统的基础上,增加了**请求调段**及**分段置换功能**后所形成的段式虚拟存储系统。它允许只装入少数段(而非所有的段)的用户程序和数据,即可启动运行。以后再通过调段功能和段的置换功能将暂不运行的段调出,同时调入即将运行的段。置换是以段为单位进行的。
### 虚拟存储器的特征
* 多次性
* 兑换性
* 虚拟性
## 3 请求分页功能
<!-- 用来实现虚拟内存的功能 -->
## 4 请求分段功能
<!-- 用来实现虚拟内存的功能 -->

View File

@@ -1,10 +1,21 @@
# I/O存储管理
# 设备管理
> ## 目录
>
> 1. IO系统
> 2. IO控制方式
> 3. 缓冲管理
> 4. IO软件
> 5. 设备分配
> 6. 磁盘存储器管理
## 1.1 IO系统
## 1 I/O设备外围设备外部设备外设
### 定义
1. 现代计算机系统通常配备大量的I/O设备用于计算机系统与外部世界(如用户、其他计算机或电子设备等)进行信息交换或存储
2. I/O操作内存和I/O设备之间的信息传送操作
1. 不仅影响计算机的通用性和可扩充性,也是计算机系统综合处理能力及性价比的重要因素
* IO设备用于计算机系统与外部世界进行信息交换或存储
* IO操作内存和IO设备之间的信息传送操作。不仅影响计算机的通用性和可扩充性,也是计算机系统综合处理能力及性价比的重要因素。
### 分类
1. 按信息传输方向划分
@@ -20,11 +31,12 @@
2. 块设备:以固定大小的数据块(块是存储介质上联系信息组成的一个区域)进行信息交换(磁盘驱动器等)
3. 网络设备:用于与远程设备通信的设备(网卡等,可以抽象为传送字符流的特殊字符设备,也可以抽象为传送连续小块数据的块设备)
### 设备管理目标
1. 克服设备和CPU速度的不匹配所引起的问题使主机和设备并行工作提高设备使用效率
2. 对设备进行抽象,屏蔽设备的物理细节和操作过程,配置驱动程序,提供统一界面,供用户或高层软件使用
1. 抽象为文件系统中的节点,统一管理
2. 裸设备不被操作系统直接管理由应用程序读写I/O效率更高
* 抽象为文件系统中的节点,统一管理
* 裸设备不被操作系统直接管理由应用程序读写I/O效率更高
### 设备管理功能
1. 设备中断处理
@@ -42,11 +54,10 @@
1. 系统I/O软件
2. 用户空间I/O软件
## 2 设备控制器设备适配器、I/O控制器、I/O控制接口、I/O模块或I/O接口
## 1.2 设备控制器
### 定义
1. 为达到模块化和通用性的设计目标通常将I/O设备中的机械部件和电子部件分开处理
2. 其中,电子部件称为设备控制器
3. 操作系统与控制器交互,而非与设备交互
1. 为达到模块化和通用性的设计目标通常将I/O设备中的机械部件和电子部件分开处理其中,电子部件称为设备控制器。
2. 操作系统与控制器交互,而非与设备交互
### 功能CPU与设备之间的接口
1. 接收和识别CPU或通道发来的命令
@@ -59,11 +70,43 @@
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.3 IO通道
### 概念
* 使一些原来由 CPU 处理的 I/O 任务转由通道来承担,从而把 CPU 从繁杂的 I/O 任务中解脱出来。在设置了通道后CPU 只需向通道发送一条 I/O指令。通道在收到该指令后便从内存中取出本次要执行的通道程序然后执行该通道程序仅当通道完成了规定的 I/O 任务后,才向 CPU 发中断信号。
* I/O 通道是一种特殊的处理机,它具有执行 I/O 指令的能力,并通过执行通道(I/O)程序来控制 I/O 操作。
### 通道类型
* 字节多路通道(Byte Multiplexor Channel)
* 数组选择通道(Block Selector Channel)
* 数组多路通道(Block Multiplexor Channel)
## 1.4 总线系统
### 单总线
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控制器和设备之间的数据传送
## 2 IO控制方式
### 轮询方式
1. 流程:
@@ -77,6 +120,7 @@
5. CPU和设备只能串行工作效率低下
### 中断方式
![](image/2021-03-30-21-49-45.png)
1. 流程:
1. 处理器向控制器发出一个I/O命令然后继续执行后续指令
1. 如果该进程不需要等待I/O完成后续指令可以仍是该进程中的指令
@@ -91,6 +135,7 @@
5. CPU和设备部分并行操作效率有所提高
### 直接存储器访问DMA
![](image/2021-03-30-21-49-25.png)
1. DMA模块模仿处理器来控制主存和设备控制器之间的数据交换
2. 流程:
1. 处理器向DMA模块发出I/O命令
@@ -107,46 +152,53 @@
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 缓冲管理
## 3 I/O软件
### 目的
* 解决CPU与设备之间速度不匹配的矛盾协调逻辑记录大小和物理记录大小不一致的问题提高CPU和设备的并行性减少I/O操作对CPU的中断次数放宽对CPU中断响应时间的要求
* 缓冲区在内存中开辟的存储区专门用于临时存放I/O操作的数据
### 操作
1. 写操作:将数据送至缓冲区,直到装满,进程继续计算,同时系统将缓冲区的内容写到设备上
2. 读操作:系统将设备上的物理记录读至缓冲区,根据要求将当前所需要的数据从缓冲区中读出并传送给进程
### 单缓冲
![](image/2021-03-30-21-50-56.png)
* 操作系统在主存的系统区中开设一个缓冲区
1. 输入:将数据读至缓冲区,系统将缓冲区数据送至用户区,应用程序对数据进行处理,同时系统读入接下来的数据
2. 输出:把数据从用户区复制到缓冲区,系统将数据输出后,应用程序继续请求输出
### 双缓冲
![](image/2021-03-30-21-51-12.png)
* 使用两个缓冲区
1. 输入设备先将数据输入缓冲区1系统从缓冲区1把数据传到用户区供应用程序处理同时设备将数据传送到缓冲区2
2. 输出应用程序将数据从用户传送到缓冲区1系统将数据传送到设备同时应用程序将数据传送到缓冲区2
### 循环缓冲
![](image/2021-03-30-21-51-30.png)
* 操作系统分配一组缓冲区,每个缓冲区都有指向下一个缓冲区的链接指针,构成循环缓冲
1. 解决设备和进程速度不匹配的问题
2. 为系统公共资源,供进程共享并由系统统一分配和管理
### 缓冲池
* 缓冲池(Buffer Pool),在池中设置了多个可供若干个进程共享的缓冲区。包括以下三种内容
* 空闲缓冲区。空缓冲队列
* 装满输入数据缓冲区。输入队列
* 装满输出数据缓冲区。输出队列
* 两个操作
* getbuf
* putbuf
## 4 IO软件
### 设计目标
1. 高效率改善设备效率尤其是磁盘I/O操作的效率
2. 通用性:用统一的标准来管理所有设备
@@ -160,7 +212,15 @@
3. 同步/异步传输:支持阻塞和中断驱动两种工作方式
4. 缓冲技术:建立数据缓冲区,提高吞吐率
## 4 I/O中断处理程序
### 用户空间的I/O软件
* 库函数
1. 一小部分I/O软件不在操作系统中是与应用程序链接在一起的库函数甚至完全由运行于用户态的程序组成
2. 系统调用通常由库函数封装后供用户使用封装函数只是将系统调用所用的参数放在合适位置然后执行访管指令来陷入内核再由内核函数实现真正的IO操作
* SPOOLing软件
1. 在内核外运行的系统I/O软件采用预输入、缓输出和井管理技术通过创建守护进程和特殊目录解决独占型设备的空占问题
### I/O中断处理程序
![](image/2021-03-30-21-55-04.png)
1. 位于操作系统底层,与硬件设备密切相关,与系统其余部分尽可能少地发生联系
2. 进程请求I/O操作时通常被挂起直到数据传输结束后并产生I/O中断时操作系统接管CPU后转向中断处理程序
3. 当设备向CPU提出中断请求时CPU响应请求并转入中断处理程序
@@ -170,7 +230,17 @@
2. 如果正常结束,唤醒等待传输的过程,使其转换为就绪态
3. 如果有等待传输的I/O命令通知相关软件启动下一个I/O请求
## 5 设备驱动程序
### 独立于设备的I/O软件
1. 执行适用于所有设备的常用I/O功能并向用户层软件提供一致性接口
2. 功能:
1. 设备命名:通过路径名寻址设备
2. 设备保护:检查用户是否有权访问所申请设备
3. 提供与设备无关的数据单位:字符数量,块尺寸
4. 缓冲技术:传输速率,时间约束,不能直接送达目的地
5. 设备分配和状态跟踪:分配到不同类型的设备
6. 错误处理和报告:驱动程序无法处理的错误
### 设备驱动程序
1. 包括与设备密切相关的所有代码
2. 从独立于设备的软件中接收并执行I/O请求
1. 把用户提交的逻辑I/O请求转化为物理I/O操作的启动和执行
@@ -190,43 +260,9 @@
1. 适用于功能简单的驱动程序,效率较高,但较难迁移
2. 分层驱动程序将驱动程序分成多层放在栈中系统接到I/O请求时先调用栈顶的驱动程序栈顶的驱动程序可以直接处理请求或向下调用更底层的驱动程序直至请求被处理
1. 适用于功能复杂,重用性要求较高的驱动程序,结构清晰且便于移植,但会增加一部分系统开销
## 5 设备分配
## 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. 用户通常不指定物理设备,而是指定逻辑设备,使得用户作业和物理设备分离开来,再通过其他途径建立逻辑设备和物理设备之间的映射
@@ -237,34 +273,38 @@
2. 易于应对I/O设备故障提高系统可靠性
3. 增加设备分配的灵活性,更有效地利用设备资源,实现多道程序设计
## 10 设备分配方式
### 独占设备
1. 只能由一个进程独占式使用
2. 可以让多个进程同时使用的设备称为"共享设备“,其管理工作主要是驱动调度和实施驱动,一般不必分配
### 设备分配方式
* 独占设备
1. 只能由一个进程独占式使用
2. 可以让多个进程同时使用的设备称为"共享设备“,其管理工作主要是驱动调度和实施驱动,一般不必分配
### 分配方式
1. 静态分配:实现简单,能够防止系统发生死锁,但会降低设备利用率
2. 动态分配:提高设备利用率
* 分配方式
1. 静态分配:实现简单,能够防止系统发生死锁,但会降低设备利用率
2. 动态分配:提高设备利用率
## 11 设备分配的数据结构
### 设备类表
1. 每类设备对应于设备类表的中一栏
2. 包括:设备类,总台数,空闲台数,设备表起始地址等
3. 支持设备独立性时才会使用
### 设备分配的数据结构
* 设备类表
1. 每类设备对应于设备类表的中一栏
2. 包括:设备类,总台数,空闲台数,设备表起始地址等
3. 支持设备独立性时才会使用
![](image/2021-03-30-21-55-46.png)
* 设备表
1. 每类设备都有各自的设备表,用来登记这类设备中的每台物理设备
2. 包括:物理设备名(号),逻辑设备名(号),占有设备的进程号,是否分配,好/坏标志等
### 设备表
1. 每类设备都有各自的设备表,用来登记这类设备中的每台物理设备
2. 包括:物理设备名(号),逻辑设备名(号),占有设备的进程号,是否分配,好/坏标志等
## 12 磁盘结构
### 基本概念
1. 磁盘由多个盘片组成
2. 每个盘片被划分为多个同心圆结构的磁道
1. 不同盘片上位于相同位置的磁道构成的圆柱体称为柱面
3. 每个磁道分为固定多个或不等个数的扇区
1. 为了对大量扇区寻址,操作系统将相邻的扇区组合成簇存储文件
4. 物理块(扇区)地址:(柱面号,磁头号,扇区号)
1. 区别“0面0道”中的“面”是指磁头不是柱面
## 6 磁盘存储器管理
### 磁盘结构
- 盘面Platter一个磁盘有多个盘面
- 磁道Track盘面上的圆形带状区域一个盘面可以有多个磁道
- 扇区Track Sector磁道上的一个弧段一个磁道可以有多个扇区它是最小的物理储存单位目前主要有 512 bytes 与 4 K 两种大小;
- 磁头Head与盘面非常接近能够将盘面上的磁场转换为电信号或者将电信号转换为盘面的磁场
- 制动手臂Actuator arm用于在磁道之间移动磁头
- 主轴Spindle使整个盘面转动。
![](image/2021-03-30-21-27-59.png)
### 磁盘读写数据
1. 读写数据时,磁头必须定位到指定的磁道上的指定扇区的开始处
@@ -273,55 +313,49 @@
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. 连续记录数据时,先记录在同一柱面的不同磁道上,然后再更换柱面,可以减少数据读写时的移臂操作
* 磁盘完成数据读写所需要的时间=寻道时间、旋转延迟、传送时间的总和
1. Ta存取时间
2. Ts寻道时间
3. r磁盘旋转速度单位转/秒)
4. B要传送的字节数
5. N一个磁道中的字节数
## 13 虚拟设备
### 使用一类物理设备模拟另一类物理设备的技术。比如:内存卡模拟磁盘、块设备模拟字符设备、输入输出重定向……
$$T_a=T_s+\frac{1}{2r}+\frac{b}{rN}$$
### 磁盘调度算法
* 读写一个磁盘块的时间的影响因素有一下三种。其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。
- 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上)
- 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
- 实际的数据传输时间
1. 先来先服务FCFS, First Come First Served
* 按照磁盘请求的顺序进行调度。
* 优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。
2. 最短寻道时间优先SSTF, Shortest Seek Time First
* 优先调度与当前磁头所在磁道距离最近的磁道。
* 虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两端的磁道请求更容易出现饥饿现象。
![](image/2021-03-30-21-29-12.png)
3. 电梯算法SCAN
* 电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。
* 电梯算法(扫描算法)和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。
![](image/2021-03-30-21-29-50.png)
## 7 虚拟设备
### 概念
* 使用一类物理设备模拟另一类物理设备的技术。比如:内存卡模拟磁盘、块设备模拟字符设备、输入输出重定向……
### 经典的SPOOLing系统
1. 为存放输入数据和输出数据,系统在磁盘上开辟输入井和输出井
1. 井是用作缓冲的存储区域

View File

@@ -1,4 +1,14 @@
# 文件系统
> ## 目录
>
> 1. 文件和文件系统
> 2. 文件的逻辑结构
> 3. 外存分配方式
> 4. 目录管理
> 5. 文件存储空间管理
> 6. 文件共享与文件保护
> 7. 数据一致性控制
## 1 文件
### 定义

View File

@@ -0,0 +1,34 @@
# 操作系统接口
> ## 目录
>
> 1. 联机用户接口
> 2. shell命令语言
> 3. 系统调用
> 4. 图形用户界面
## 3 系统调用
### 定义
![](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(); |

View File

@@ -1,611 +0,0 @@
# 并发程序设计
## 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. 中止一个卷入死锁的进程,以后重执行

View File

@@ -1,123 +0,0 @@
# 其他
## 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: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,141 +0,0 @@
# 计算机操作系统 - 内存管理
<!-- 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

@@ -1,144 +0,0 @@
# 计算机操作系统 - 死锁
<!-- 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

@@ -1,20 +0,0 @@
# 计算机操作系统
- [概述](计算机操作系统%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

@@ -1,61 +0,0 @@
# 计算机操作系统 - 设备管理
<!-- 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

@@ -1,68 +0,0 @@
# 计算机操作系统 - 链接
<!-- 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

@@ -1 +0,0 @@
[计算机操作系统](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.

View File

@@ -51,7 +51,7 @@
### 定义
* 内核: 是一组程序模块,作为可信软件来提供支持进程并发执行的基本功能和基本操作,通常驻留在内核空间,运行于内核态,具有直接访问硬件设备和所有内存空间的权限,是仅有的能够执行特权指令的程序。
## 内核的功能
### 内核的功能
1. 中断处理。中断处理是内核中最基本的功能,也是操作系统赖以活动的基础。
2. 时钟管理。时钟管理是内核的基本功能。
3. 短程调度。短程调度的职责是分配处理器,按照一定的策略管理处理器的转让,以及完成保护和恢复现场工作。

View File

@@ -1,337 +0,0 @@
# 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,296 @@
# Socket
<!-- GFM-TOC -->
- [Socket](#socket)
- [1 I/O 模型](#1-io-模型)
- [阻塞式 I/O](#阻塞式-io)
- [非阻塞式 I/O](#非阻塞式-io)
- [I/O 复用](#io-复用)
- [信号驱动 I/O](#信号驱动-io)
- [异步 I/O](#异步-io)
- [五大 I/O 模型比较](#五大-io-模型比较)
- [2 I/O 复用](#2-io-复用)
- [select](#select)
- [poll](#poll)
- [比较](#比较)
- [epoll](#epoll)
- [工作模式](#工作模式)
- [应用场景](#应用场景)
<!-- GFM-TOC -->
## 1 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);
```
![](image/2021-03-30-22-02-32.png)
### 非阻塞式 I/O
* 应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成这种方式称为轮询polling
* 由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
![](image/2021-03-30-22-02-42.png)
### I/O 复用
* 使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
* 它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O即事件驱动 I/O。
* 如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接那么就需要创建相同数量的线程。相比于多进程和多线程技术I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
![](image/2021-03-30-22-02-51.png)
### 信号驱动 I/O
* 应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。
* 相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。
![](image/2021-03-30-22-03-00.png)
### 异步 I/O
* 应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
* 异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
![](image/2021-03-30-22-03-09.png)
### 五大 I/O 模型比较
- 同步 I/O将数据从内核缓冲区复制到应用进程缓冲区的阶段第二阶段应用进程会阻塞。
- 异步 I/O第二阶段应用进程不会阻塞。
* 同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一个阶段。
* 非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
![](image/2021-03-30-22-04-01.png)
## 2 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 的描述符存储在内核,不容易调试。

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More