现代操作系统-原理与实现上银杏书-读书笔记
目录
现代操作系统-原理与实现(上)【银杏书-读书笔记】
看看多久才会读完—买于20年双十一
【来自未来的更新】已于2021年5月16日看完!!!!
本篇为上集,戳这里
**目录**
[第1章-操作系统概述](#%E7%AC%AC1%E7%AB%A0-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%A6%82%E8%BF%B0)
[第2章-硬件结构](#%E7%AC%AC2%E7%AB%A0-%E7%A1%AC%E4%BB%B6%E7%BB%93%E6%9E%84)
[第3章-操作系统结构](#%E7%AC%AC3%E7%AB%A0-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84)
[第4章-内存管理](#%E7%AC%AC4%E7%AB%A0-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86)
[第5章-进程与线程](#%E7%AC%AC%E4%BA%94%E7%AB%A0-%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)
[进程的状态](#%E8%BF%9B%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81)
[进程的内存空间布局](#%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%E5%B8%83%E5%B1%80)
[进程控制块和内存上下文切换](#%E8%BF%9B%E7%A8%8B%E6%8E%A7%E5%88%B6%E5%9D%97%E5%92%8C%E5%86%85%E5%AD%98%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2)
[线程](#%E7%BA%BF%E7%A8%8B)
[线程的由来](#%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%B1%E6%9D%A5)
[线程的定义](#%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%AE%9A%E4%B9%89)
[多线程的地址空间布局](#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E5%B8%83%E5%B1%80)
[线程控制块和线程本地存储](#%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6%E5%9D%97%E5%92%8C%E7%BA%BF%E7%A8%8B%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8)
[线程的基本接口-POSIX线程库](#%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%8E%A5%E5%8F%A3-POSIX%E7%BA%BF%E7%A8%8B%E5%BA%93)
[线程创建](#%E7%BA%BF%E7%A8%8B%E5%88%9B%E5%BB%BA)
[线程退出](#%E7%BA%BF%E7%A8%8B%E9%80%80%E5%87%BA)
[出让资源](#%E5%87%BA%E8%AE%A9%E8%B5%84%E6%BA%90)
[合并操作](#%E5%90%88%E5%B9%B6%E6%93%8D%E4%BD%9C)
[挂起与唤醒](#%E6%8C%82%E8%B5%B7%E4%B8%8E%E5%94%A4%E9%86%92)
[进程的执行](#%E8%BF%9B%E7%A8%8B%E7%9A%84%E6%89%A7%E8%A1%8C)
[执行进程](#%E6%89%A7%E8%A1%8C%E8%BF%9B%E7%A8%8B)
[进程管理](#%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86)
[进程间监控](#%E8%BF%9B%E7%A8%8B%E9%97%B4%E7%9B%91%E6%8E%A7)
[进程组和会话组](#%E8%BF%9B%E7%A8%8B%E7%BB%84%E5%92%8C%E4%BC%9A%E8%AF%9D%E7%BB%84)
[Fork的优点](#Fork%E7%9A%84%E4%BC%98%E7%82%B9)
[Fork的缺点](#Fork%E7%9A%84%E7%BC%BA%E7%82%B9)
[Fork的继任者们](#Fork%E7%9A%84%E7%BB%A7%E4%BB%BB%E8%80%85%E4%BB%AC)
[第6章-操作系统调度](#%E7%AC%AC6%E7%AB%A0-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%B0%83%E5%BA%A6)
[调度机制](#%E8%B0%83%E5%BA%A6%E6%9C%BA%E5%88%B6)
[长期调度](#%E9%95%BF%E6%9C%9F%E8%B0%83%E5%BA%A6)
[中期调度](#%E4%B8%AD%E6%9C%9F%E8%B0%83%E5%BA%A6)
[短期调度](#%E7%9F%AD%E6%9C%9F%E8%B0%83%E5%BA%A6)
[进程的分类](#%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%88%86%E7%B1%BB)
[经典调度](#%E7%BB%8F%E5%85%B8%E8%B0%83%E5%BA%A6)
[先到先得FIFO](#%E5%85%88%E5%88%B0%E5%85%88%E5%BE%97FIFO)
[最短任务优先](#%E6%9C%80%E7%9F%AD%E4%BB%BB%E5%8A%A1%E4%BC%98%E5%85%88)
[最短完成时间任务优先](#%E6%9C%80%E7%9F%AD%E5%AE%8C%E6%88%90%E6%97%B6%E9%97%B4%E4%BB%BB%E5%8A%A1%E4%BC%98%E5%85%88)
[时间片轮转](#%E6%97%B6%E9%97%B4%E7%89%87%E8%BD%AE%E8%BD%AC)
[优先级调度](#%E4%BC%98%E5%85%88%E7%BA%A7%E8%B0%83%E5%BA%A6)
[多级队列](#%E5%A4%9A%E7%BA%A7%E9%98%9F%E5%88%97)
[多级反馈队列](#%E5%A4%9A%E7%BA%A7%E5%8F%8D%E9%A6%88%E9%98%9F%E5%88%97)
[公平共享调度](#%E5%85%AC%E5%B9%B3%E5%85%B1%E4%BA%AB%E8%B0%83%E5%BA%A6)
---
## 第1章-操作系统概述
**从硬件角度**
对硬件进行管理,处理各种错误
对硬件进行抽象,形成不依赖硬件的资源
**从应用角度**
提供不同的接口,满足不同类型的访问控制,应用间交互等服务
进行资源分配与管理
操作系统提供不同层次的接口
1. 系统调用接口,例如printf【printf 从应用程序-》libc-》下陷处理-》系统调用处理】[从应用程序-》libc-》是在用户态] [从下陷处理-》系统调用处理 是在内核态]
2. POSIX接口【可移植操作系统接口】,例如glibc
3. 领域应用接口,例如AUTOSAR,这种算框架了
## 第2章-硬件结构
冯诺依曼-机,包含
1. 中央处理器
2. 存储器
3. 输入输出

**指令集**
是ISA【指令集架构】的重要组成部分,AArch64属于RISC【精简指令集计算机】
**特权级**
在AArch64中叫做异常级别,包括
1. EL0 应用层跑在这【用户态】
2. EL1 操作系统跑在这【内核态】
3. EL2 虚拟化在这里【虚拟机场景用】
4. EL3 TrustZone相关【负责切换安全世界和普通世界】
**何时从EL0切换到EL1**
1. 应用层程序用系统调用
2. CPU收到中断
3. 应用层触发了异常
其中1和2为同步的CPU特权级切换
3为异步的CPU特权级切换
**从EL0切换到EL1**
1. 触发异常的指令地址【PC】保存到ELR_EL1
2. 异常原因保存到ESR_EL1
3. CPU将栈指针从SP_EL0切换到SP_EL1,在异常向量表中选择对应的异常处理函数
4. CPU还要保存一些状态
寄存器是ISA的重要组成,包括
1. **通用寄存器**
2. **栈指针寄存器**
3. **链接指针**
EL1下有两个
**页表基地址寄存器**
,这个和虚拟内存有关系
**Cache**
这个是为了加快CPU访问数据的速度,包括:
若干个缓冲行,每一个行包括
**一个有效位**
**一个标记地址**
为了通过物理地址找到对应的缓存,物理地址在逻辑上分为Tag,Set以及Offset三段
物理地址中Set段能够表示的最大数目叫做
**组**
支持的最大Tag数叫做
**路**
**缓存结构和缓存寻址的图请看书**
## **第3章-操作系统结构**
设计操作系统的原则【机制与策略分离】
> 策略:做什么【输入处理,启动加载....】
>
> 机制:如何做【调度方法...】
管理复杂系统的方法
1. **模块化**
2. **抽象**
3. **分层**
4. **层级**
> 模块化:分而治之,将复杂系统分解成一系列的模块,保证模块之间的界限,高耦合低内聚,使之有独立性
>
> 抽象:接口与实现分离,无需关心各个模块之间的内部实现
>
> 分层:将模块按一定的层次划分,约束内部模块之间的交互方式
>
> 层级:将功能相近的模块划分在一个子系统
操作系统的内核架构
1. **简要结构**
2. **宏内核**
3. **微内核**
4. **外核**
5. **多内核**
> 简要结构:应用程序和操作系统在同一个地址空间,没有虚拟内存管理,特权级隔离等功能,任何一个模块出问题,系统就崩溃了
>
> 宏内核:所有的操作系统模块运行在内核态
>
> 微内核:将单个的内核功能拆分,作为服务部署在用户态,仅有很小部分的内核运行在内核态,服务提供进程间通信的功能使之互相协作
>
> 外核:操作系统与应用程序挂钩,应用程序要啥就装对应的功能,内核只负责对操作系统在多个操作系统之间的多路复用
>
> 多内核:通过多内核管理异构多核设备
## 第4章-内存管理
应用程序是面向虚拟内存编写的,CPU会翻译地址到物理地址,操作系统来管理虚拟地址和物理地址的映射
设计时的三个目标
1. 高效性【不应占用过多的物理内存资源】
2. 安全性【使不同应用程序互相隔离】
3. 透明性【应用层编程时感觉不到内存的抽象】
CPU通过MMU进行地址翻译,为了加速翻译,现代CPU都有
**TLB**
【转址旁路缓存】
有两种机制
1. 分段机制
2. 分页机制
分段机制不多写了,目前用的多的是分页机制的
**分页机制**
一个虚拟地址将被划分成两部分
1. 虚拟页号
2. 页内偏移量
多级页表也是一样的结构,多级页表允许部分空洞,单级页表则需要每一项都真实存在
**AArch64 下的多级页表**
虚拟地址低48位参与地址翻译,页表级数为4,虚拟页大小4KB
物理地址划分为连续的4KB大小物理页,一个虚拟页映射为一个物理页,低12位对应页内偏移量
整个页表的起始地址放在
**页表基地址寄存器**
中,对应的就是第0级的页表页
每一个页表页占用物理内存的一个物理页[4KB]
每一个页表项占用8字节,存访问权限。
因此,一个页表页包含512个页表项[4K/8],虚拟地址中对应于每一级页表的索引都是9位【2^9】
> 63-48位: 全0或者全1
>
> 47-39位:0级页表索引值
>
> 38-30位:1级页表索引值
>
> 29-21位:2级页表索引值
>
> 20-12位:3级页表索引值
>
> 11-0位: 页内偏移量
**如何翻译呢?**
1. **先根据页表基地址寄存器找到第0级页表页**
2. **用0级页表索引值作为页表项索引,读取第0级页表页中对应的页表项,这个页表项又对应下一级的页表页物理地址**
3. **以此类推,最后结合页内偏移量就可以得到最终的物理地址**
**TLB【**
转址旁路缓存
**】**
**多级页表的出现使得MMU翻译地址的过程要查找多个页表页中的页表项**
**为了减少次数,加入TLB加速翻译**
> **TLB缓存了虚拟页号到物理页号的映射关系**
>
> **MMU先把虚拟页号作为键值去查询TLB的缓存项**
>
> **找到【TLB命中】就直接得到物理页号,否则【TLB未命中】就要查页表**
由于这个TLB是CPU的一部分,所以它的大小是有限的,它是由硬件直接进行管理的,这样才能高效利用
TLB未命中,就去查页表,然后填进TLB
如果已满,按照预定的方式替换掉某一项
如果再次翻译一样的页号,那么就可以马上得到页表了。
TLB与当前页表不一致是需要刷新的,如何最小化刷新对应用程序带来的影响,则是操作系统与CPU一起需要处理的。
**换页和缺页**
> **换页:当物理内存不足时,操作系统把物理页的数据写到磁盘等容量更大的设备中,然后就可以回收物理页回来了,此时的物理页处于【已分配但未映射到物理内存】的状态**
>
> **缺页:当应用程序访问了处于【已分配但未映射到物理内存】的状态的物理页,那么就会触发缺页异常,操作系统会调用预先设置的处理函数,然后找到一个空虚的物理页,将之前的数据重新写回到该物理页上,并且在页表上填写该虚拟地址到这一个物理页的映射。**
由于换页涉及到磁盘操作,所以操作系统会引入预取机制优化。
当应用层申请虚拟内存的时候,操作系统可以把新分配的虚拟页标记为已分配但未映射至物理内存的状态,不需要为这个虚拟页分配对应的物理页。只有要访问时才会触发缺页异常,真正为虚拟页分配物理页,并在页表里填写映射。
初次访问时产生的缺页异常会导致访问延迟的增加,可以利用应用程序访问时的时空局限性改善。
虚拟页处于
**未分配**
或者
**已分配但未映射至物理内存状态**
时,应用程序访问该虚拟页会触发缺页异常。
Linux中应用程序的虚拟地址空间被实现由多个虚拟内存区域VMA组成
> 当应用程序发生缺页异常时,操作系统通过判断虚拟页是否属于该应用程序的某个虚拟内存区域区分该页所处的分配状态
>
> **若属于,则该页处于已分配但未映射至物理内存的状态**
>
> **否则,该页处于未分配状态。**
**页替换策略**
> **如果空闲的物理页已经用完或者小于某个阈值,策略便会选择某些物理页换出到磁盘,让出空间,最小化缺页异常发生的次数进而提升性能**
常见的页替换策略有:
1. MIN策略【优先选择未来不会再用的页】
2. FIFO策略【优先选择最先换入的页】
3. SECOND CHANCE策略【每一个物理页号维护一个访问标志位,如果没有置位,则换出该页,反之对应页挪动到队尾,从新的队头重新开始找换出的内存页】
4. LRU策略【优先选择最久未被访问的页】
5. MRU策略【优先选择最近访问的页】
6. 时钟算法策略【换入物理内存的页号排列成一个时钟的形状,该时钟有一个针臂,指向新换入内存的页号的后一个。每次需要选择换出页号时,从针臂所指的页号开始检查】
**共享内存**
> **允许同一个物理页再不同的应用程序间共享**
>
> **基本用途是可以让不同应用程序之间互相通信**
>
> **基于此,操作系统又衍生出写时拷贝和内存去重**
**写时拷贝**
**页表项中有部分位用来标识属性,包括标识虚拟页的权限**
**写时拷贝技术允许应用程序A和B以只读方式共享同一段物理内存**
> **当应用程序对该内存区域进行修改时,便会触发缺页异常,在异常处理函数之中系统会发现由于应用程序写了只读内存,对应区域被标记为写时拷贝。于是操作系统会把这种异常的物理页重新拷贝一份,并将新拷贝的物理页以可读可写的方式重新映射给应用程序。**
**内存去重**
> **操作系统定时在内存中扫描相同的物理页,找到映射这些物理页的虚拟页,只保留其中一个物理页,并将具有相同内容的其他虚拟页都用写时拷贝方式映射到这个物理页上,然后释放掉给其他的物理页用。**
**内存压缩**
> **操作系统还会引入压缩算法对内存数据进行压缩,特别是资源不足时,把不太会使用的一部分数据压缩,节约出更多的内存空间**
**大页**
> **解决TLB缓存项不够的问题,大页的大小可以到2M甚至1G,相比原来4K的大小,大幅度减小TLB的占用量**
>
> **Linux还提供透明大页的机制,能够自动地降连续的4K页合并成2M的内存页.**
**物理内存分配机制**
**内存碎片**
> **无法被利用的内存,直接导致内存的利用率下降**
>
> **分为内部碎片和外部碎片**
>
> **外部碎片:经过多次回收和分配之后,物理内存高度分散,导致空闲内存足够但是无法满足申请需求**
>
> **内部碎片:当分配内存空间大于实际分配请求所需要的空间时,就会造成部分的内存浪费**
## 第5章-进程与线程
> **上下文切换:通过保存和恢复进程在运行过程中的状态【上下文】,使进程可以暂停,切换和护肤,从而实现了CPU的资源调度。**
### 进程的状态
1. 新生状态
2. 预备状态
3. 运行状态
4. 阻塞状态
5. 终止状态
### 进程的内存空间布局
用户栈: 栈保存了进程需要使用的各种临时数据,一般是自顶而下的,栈底在高地址,栈顶在低地址
代码库:进程运行依赖的共享代码库,标记为只读
用户堆:管理的是进程动态分配的内存,堆的扩展方向是自底向上。
数据与代码段:数据段主要保存全局变量的值,代码段保存的是进程执行所需的代码
内核部分:当进程在用户态运行时,内核内存对其不可见。只有进程进入内核态时,才能访问内存。
### 进程控制块和内存上下文切换
在内核中,每一个进程都通过一个数据结构来存它的相关状态,譬如进程标识符【PID】,进程状态,虚拟内存状态等,这种数据结构叫做进程控制块【PCB】
进程的上下文包括运行时的寄存器状态,它可以用于保存和恢复上一个进程在处理器运行的状态。使用上下文切换机制,就可以把前一个进程的寄存器保存到PCB,然后将下一个进程保存的状态写入寄存器,切换到它运行。
上下文切换一般是在内核态中运行的。
### 线程
一个进程至少有一个线程,多个线程可以共享进程的资源。进程是在内存中运行的一段程序,而线程是这一段程序中的其中一个执行单元。
在linux中,进程一般是调用fork从进程中分裂出来的
一个进程调用fork后,会为该进程创建一个子进程。而调用fork的进程称作父进程。
之后便会形成两个完全独立的两个进程,将拥有不同的PID与虚拟内存空间。
对于父进程,返回值是子进程的PID,对子进程来说,返回值是0
> 注意调度器眼中,子进程和父进程是两个独立的个体,执行顺序不定,完全取决于调度器的决策
对每一个进程来说,运行过程中都会维护一个已打开文件的文件描述符【fd表】文件描述符会使用偏移量记录当前进程读取到某一个文件的位置,因为文件结构可能根据不同文件系统而变化,这样做有利于操作系统进行管理。
子进程和父进程拥有一样的fd表,因此在read操作的时候会对文件加锁,父子进程不可能读到完全一样的字符串。
操作系统的第一个进程是由操作系统创建的,特定而唯一,其他进程就像树一样派生出来。
### 线程的由来
1. 创建进程的开销比较大,需要独立的地址空间
2. 进程之间的通信和同步比较麻烦
### 线程的定义
> 线程之间共享地址空间,但又各自保存运行时所需要的状态【上下文】
>
> 它是操作系统种调度管理的最小单位
### 多线程的地址空间布局
地址空间布局
| |
| --- |
| 内核代码与数据 |
| 内核栈1 |
| 内核栈2 |
| 内核栈3 |
| 线程栈1 |
| 线程栈2 |
| 线程栈3 |
| 代码库 |
| 用户堆 |
| 数据 |
| 代码 |
> 两个重要特征
>
> 1. 分离的内核栈和用户栈
>
> 2. 共享的其他区域
内核栈由系统内核创建,用户栈不可见,直接受操作系统调度器管理。
为了实现内核栈和用户栈协作,操作系统会在两者之间维护一个关系,叫做
**多线程模型**
1. **多对一模型【每次只有一个用户态线程进入内核】**
2. **一对一模型【每一个线程对应一个内核态线程,但是这种需要限制线程总数,太多了处理不了】【Linux和Windows采用】**
3. **多对多模型【既解决了内核态线程少而阻塞的问题,也解决了一对一模型线程有限制的问题】【Mac OS采用】**
### 线程控制块和线程本地存储
类似于进程,线程也有自己的控制块,存储一些信息
对linux来说,用户态的
**线程控制块TCB**
可以认为是内核态的扩展,用来存储更多与用户态相关的信息,其中最重要的就是
**线程本地存储TLS**
**在多线程编程中,不同线程使用全局变量的时候,实际上访问的是该变量的不同拷贝,不会对其他线程产生影响。**
**线程库位每一个线程创建完全相同的TLS,保存在内存的不同地址之上,每一个全局变量的拷贝相对于TLS起始位置的偏移量都是一样的**
**由于TLS的结构相似性,对于TLS的寻址也比较特殊**
**不同线程的段寄存器FS保存着不同的TLS起始地址,当不同的线程访问同名的TLS时,最终访问了不同的地址**
### 线程的基本接口-POSIX线程库
#### 线程创建
pthread_create
#### 线程退出
pthread_exit
#### 出让资源
pthread_yield
#### 合并操作
pthread_join
#### 挂起与唤醒
sleep
pthread_cond_wait
### 进程的执行
#### 执行进程
通过调用execve函数执行进程
> * 将可执行文件的数据段和代码段载入当前进程的地址空间
> * 重新初始化堆和栈
> * 将PC寄存器设置到可执行文件代码段定义的入口点,该入口点最终会调用main函数
> * 在需要设定环境变量时,main函数也可以扩展写成int main(int argc,char * argv[],char *envp[]),使得程序能够直接访问envp来获取到环境变量
#### 进程管理
Linux中,进程都是通过fork生成出来的
每一个进程都会记录自己的父进程和子进程,这样进程之间就构成了树关系
**处于树根部的是init进程**
#### 进程间监控
在linux中,进程可以使用wait来对其子进程进行监控。
> 父进程调用waitpid堆子进程进行监控,如果子进程已经退出,那么waitpid就会立即返回,并设置状态值,否则会阻塞等待子进程退出,当waitpid退出后,父进程可以访问状态值变量来获取子进程的状态
> 僵尸进程: 终止了却没有释放对应资源的子进程
#### 进程组和会话组
> **进程组**
> 是进程的集合,父进程和子进程属于同一个进程组
>
> **会话**
> 是
> **进程组**
> 的集合,可以分为前台进程组和后台进程组,控制终端进程组等
>
> 每一个进程都有自己的进程id【PID】,进程组id【GID】和会话id【SID】
>
> **对于init进程来说,这三个值都是1**
### Fork的优点
> fork和exec的组合可以认为是将进程的创建的进一步解耦
>
> fork强调进程之间的联系,如果父子进程之间有较强的联系的话就适合用fork
### Fork的缺点
> 过于复杂,接口简洁,但是内部已经随着时代的变迁变得异常复杂
>
> 性能不佳,写时拷贝技术对于今天的应用来说需要耗费太多的时间
>
> 有安全漏洞
### Fork的继任者们
1. posix_spawn【将fork和exec合二为一】
2. vfork【限定场景,只适合进程创建后立即使用exec】
3. rfork/clone【精密控制,提供更多的控制选项】
## 第6章-操作系统调度
### 调度机制
把调度分成
1. 长期调度
2. 中期调度
3. 短期调度
### 长期调度
用于限制系统中真正被短期调度管理的进程数量,避免短期调度开销过大。
它的触发时间长,粗粒度的决定是不是要将一个新进程纳入调度管理,负责增加系统中可被调度的进程的数量
### 中期调度
将内存使用的情况也考虑进来,避免内存使用过多,实际上算作换页机制的一部分,它会挂起系统中被短期调度管理的进程。
触发相对频繁,辅助换页机制,负责限制系统中可被调度的进程数量
### 短期调度
是实际来做出调度决策的,负责进程状态的转换。
触发最为频繁,细粒度的负责进程的执行,做出对应的调度决策。

### 进程的分类
1. 计算密集型【使用CPU进行长时间运算】
2. IO密集型【等待IO请求会占用大量的时间】
### 经典调度
#### 先到先得FIFO
【优点】:简单直观,不需要预知任务的信息
【缺点】:
1. 在长短任务混合的场景下,对短任务不友好,短任务要等很久。
2. 对IO密集型进程不友好,用IO很久,占CPU很短,但是计算密集型一直占着CPU,CPU处理不了IO请求,就对IO密集型进程很不公平。
#### 最短任务优先
【优点】:选择运行时间最短的任务执行,短任务就可以不用等很久了
【缺点】:
1. 必须预知任务的运行时间,某些场景很难预知任务的时长
2. 严重依赖任务到达的时间点,一旦调度器没有发现短任务到达,错过了就会做出错误的决策了
#### 最短完成时间任务优先
调度器必须等一个任务执行完或者主动退出执行才能开始下一个调度,这种就是
**非抢占式调度**
。
对于最短完成时间任务优先来说,在任务达到的时候也会调度,还有可能打断目前正在执行的任务,这种就是
**抢占式调度。**
【缺点】:
1. 需要预知任务运行的时间
2. 长任务会饥饿,短任务多,长任务少,因为短任务执行完的快,所以长任务就很吃亏
#### 时间片轮转
限定每一个任务的运行时间,完成以后就执行运行队列中的下一个任务
【优点】:不需要预知任务运行的时间,也不会出现长任务很吃亏的问题。
【缺点】:
1. 过小的时间片就会造成调度开销很大
2. 虽然保证了每个任务之间的公平性,良好的响应时间,但是在任务运行时间相似的场景下平均周转时间高。
### 优先级调度
#### 多级队列
每一个任务都会被分配预先设置好的优先级
每一个优先级对应一个队列,任务就被存放在这些队列里。
多个任务同时预备,那么调度器就会选择优先级高的队列中的任务执行,在同一优先级下,策略又不一样,可以采取不同的策略,譬如FIFO或者时间片。
在这种调度策略下
**需要预知任务的运行时间**
**设置优先级需要提高IO密集型任务的优先级,因为IO密集型并不占用CPU太久。**
【缺点】:
1. 低优先级任务饥饿,高优先级的任务特别多,低优先级的任务根本轮不上,需要另外加机制【监测等待时间】,等待太久直接上了。
2. 优先级反转,这个弄过操作系统的都知道,解决的方法就是优先级继承【互斥信号量】
#### 多级反馈队列
类似多级队列,但是它实现了优先级的动态设置。
【优点】:
1. 短任务的优先级更高,对IO密集型任务就很有利了,降低交互式任务的响应时间。
2. 统计运行任务的时间长度,判断是长任务还是短任务,定义最大运行时间,超过了的任务优先级自动降低,实现动态评估任务优先级。
3. 低优先级的任务采用更长的时间片,但是有优先级抢占所以不怕占时间长
4. 定时将所有任务优先级改到最高,保证不会有任务饥饿
#### 公平共享调度
会量化任务对系统资源的占用比例,从而实现资源公平调度,以份额量化每一个任务对CPU的使用。
实际中将任务分组,以组为单位分配份额,任务在组内继续分配份额。
优先级调度为了优化任务的周转时间,响应时间,而份额式的公平共享调度式为了让每个任务都可以得到对应的资源。
##
[下篇链接戳这里](https://blog.csdn.net/tpoem/article/details/114744720)