# 2.2 处理机调度

## 2.2.1 调度的概念、层次

### 一、调度的概念

{% hint style="success" %}
在处理机调度是对处理机进行分配，即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行，以实现进程并发地执行。
{% endhint %}

**处理机调度是多道程序操作系统的基础，是操作系统设计的核心问题。**

### 二、调度的层次

#### 1、作业调度（高级调度）

{% hint style="info" %}
按照一定原则从外村上处于后备队列的作业中选择一个（或多个），给它分配内存等必要资源，并**建立相应的进程（建立PCB）**，以使它获得**竞争处理机资源的权利**。
{% endhint %}

作业调度是外存和内存之间的调度。每个作业只调入一次、调出一次。作业调度时建立相应的PCB；作业调出时撤销相应的PCB。

#### 2、内存调度（中级调度）

{% hint style="info" %}
可以将暂时不能运行的进程调值外存等待。这些进程会进入“**挂起状态**”，其PCB仍然**常驻在内存**，被放入到挂起队列中。其目的是**提高内存利用率和系统吞吐量**。
{% endhint %}

**通过中级调度来决定将哪个处于挂起状态的进程重新调入内存中**。

#### 3、进程调度（低级调度）

{% hint style="info" %}
按照某种方法和策略从就绪队列中选择一个进程，将处理机分配给它。
{% endhint %}

进程调度是操作系统中**最基本的一种调度**。其**频率很高**，一般几十毫秒一次。

|      |    调度发生在    |       进程状态变化      |
| :--: | :---------: | :---------------: |
| 高级调度 | 外存→内存（面向作业） |     无→创建态→就绪态     |
| 中级调度 | 外存→内存（面向进程） | 挂起态→就绪态（阻塞挂起→阻塞态） |
| 低级调度 |    内存→CPU   |      就绪态→挂起态      |

## 2.2.2 调度的时机、切换与过程

### 一、调度器/调度程序

就绪态与运行态之间的相互切换由调度程序引起并决定。

{% hint style="info" %}
* 让谁运行：调度程序
* 运行多久：时间片大小
{% endhint %}

### 二、进程调度的时机

#### 需要进行进程调度的情况

* 当前进程**主动**放弃处理机资源
  * 进程正常中止
  * 进程出现异常终止
  * 进程主动请求阻塞
* 当前进程**被动**放弃处理机资源
  * 分配的时间片用完
  * 有更紧急的事件要处理（如I/O中断）
  * 有更高优先级的进程进入就绪队列

#### 不能进行进程调度与切换的情况

* 在处理中断的过程中
* 进程处于**操作系统内核程序临界区**中
* 在进行原子操作的过程中（**原语**）。

{% hint style="info" %}
**临界资源**：一个时间段内只允许一个进程使用的资源，各进程需要**互斥地**访问临界资源。

**临界区**：访问临界资源的那段代码。

**内核程序临界区**：一般是用于访问**内核数据结构**的，例如进程的就绪队列。
{% endhint %}

### 三、进程调度的方式

#### 非剥夺调度方式（非抢占方式）

只允许进程主动放弃处理机资源。即便有更高优先级的任务到达，也要等待当前进程主动终止或进入阻塞态。

* 实现简单，系统开销小
* 无法及时处理紧急任务

#### 剥夺调度方式（抢占方式）

当有更重要的任务需要使用处理机时，立即暂停当前正在执行的进程，将处理机资源给更紧迫的任务。

* 可以优先处理更紧急的任务
* 可以让各进程按照时间片轮流执行
* 适用于分时操作系统、实时操作系统

### 三、进程的切换与过程

#### 狭义的进程调度与进程切换

**狭义的进程调度**：从就绪队列中**选中一个要运行的进程**。这个进程可以是刚刚被暂停执行的进程，也可以是**另一个进程**，后者就需要**进程切换**。

**进程切换**：一个进程让出处理机，由另一个进程占用处理机的过程。

**广义的进程调度**包含了选择进程和进程切换两个步骤。

#### 进程切换中实现了什么

* 对原来进程的各种数据进行保存
* 对新的进程进行各种数据的恢复

**进程的切换是有代价的**，过于频繁的进行进程的调度、切换会使得操作系统的效率降低。

### 四、闲逛进程

用于占位

* 优先级最低
* 是0地址指令，在指令周期末尾检查中断

## 2.2.3 进程调度的基本准则

### 一、CPU利用率

$$
利用率 = \frac{忙碌的时间}{总时间}
$$

设备利用率同理

### 二、系统吞吐量

$$
系统吞吐量 = \frac{完成的作业数}{总计时间}
$$

### 三、周转时间

周转时间，是指从**作业提交给系统开始**，到**作业完成**这段时间的时间间隔。包括作业等待、在就绪队列中排队、在处理机上运行、进行输入/输出操作所所花费时间的总和。

$$
（作业的）周转时间 = 作业完成时间 - 作业提交时间
$$

$$
平均周转时间 = \frac{各作业周转时间和}{作业数}
$$

$$
带权周转时间 = \frac{作业周转时间}{作业实际运行时间}
$$

* 带权周转时间必然≥1
* 带权周转时间越小越好

$$
平均带权周转时间 = \frac{各作业带权周转时间和}{作业数}
$$

### 四、等待时间

等待时间，指的是进程/作业**处于等待处理机状态时间之和**，等待时间越长，用户满意度越低。

{% hint style="danger" %}
作业等待I/O设备的时间进程也在被服务，不计入等待时间。

还要加上作业在外存后备队列中等待被调度的时间。
{% endhint %}

### 五、响应时间

响应时间，指从用户**提交请求**到**首次响应**所用的时间。

## 2.2.4 典型的调度算法

{% hint style="info" %}
**饥饿**：某个进程/作业长期得不到服务
{% endhint %}

### 一、先来先服务调度算法（FCFS，First Come First Serve)

#### 算法思想

主要从“公平”的角度考虑

#### 作业规则

按照作业/进程到达的先后顺序进行服务，即**等待时间**越久的进程/作业越优先得到服务

#### 用于作业/进程调度

用于作业调度时，考虑的是哪个作业**先到达后备队列**；

用于进程调度时，考虑的是哪个进程**先到达就绪队列**

#### 是否可抢占

非抢占式算法

#### 优缺点

* 优点
  * 公平
  * 算法实现简单
* 缺点
  * 排在长作业后的短作业需要等待很长的时间，**带权周转时间**很大

即：_**FCFS对长作业有利，对短作业不利**_

#### 是否会导致饥饿

不会

### 二、短作业优先调度算法（SJF，Shortest Job First)

#### 算法思想

追求最少的**平均等待时间**，最少的**平均周转时间**、最少的**平均带权周转时间**

#### 算法规则

最短的作业/进程优先得到服务

#### 用于作业/进程调度

既可用于作业调度，也可用于进程调度。用于进程调度时称为**短进程优先（SPF, Shortest Process First）算法**

#### 是否可抢占

SJF和SPF是非抢占式的算法，同时也有抢占式的**最短剩余时间优先**调度算法（SRTN，Shortest Reamaining Time First Next）

#### 优缺点

* 优点
  * “最短的”平均等待时间、平均周转时间
* 缺点
  * 不公平
  * 对短作业有利，对长作业不利
  * 可能产生饥饿现象
  * 作业/进程的运行时间是由用户提供的，不一定能做到真正的短作业优先

#### 是否会导致饥饿

会

{% hint style="danger" %}
1. 题目中未特别说明，短作业/进程优先算法默认是**非抢占式**的
2.
   * SJF调度算法的平均等待时间、平均周转时间最少 ❌
3. 在所有进程**同时到达**，采用SJF调度算法的平均等待时间、平均周转时间最少 ✔
4. **抢占式的**短作业/进程优先调度算法（最短剩余时间优先，SRNT算法）的平均等待时间、平均周转时间最少 ✔
5. 虽然严格来说，SJF的平均等待时间、平均周转时间并不一定最少，但相比于其他算法(如FCFS)，SJF依然可以获得**较少**的平均等待时间、平均周转时间
6. 如果选择题中遇到 “SJF算法的平均等待时间、平均周转时间**最**少” 的选项，那最好判断其他选项是不是有很明显的错误，如果没有更合适的选项，那也应该选择该选项
{% endhint %}

### 三、高响应比优先

#### 算法思想

要综合考虑作业/进程的等待时间和要求服务的时间

#### 算法规则

在每次调度时先计算各个作业/进程的**响应比**，选择**响应比最高**的作业/进程为其服务

$$
响应比 = \frac{等待时间+要求服务时间}{要求服务时间}
$$

#### 是否可抢占

非抢占式的算法。只有当前运行的作业/进程主动放弃处理机时，才需要调度，才需要计算响应比

#### 优缺点

* 综合考虑了等待时间和运行时间（要求服务时间）
  * 等待时间相同时，要求服务时间短的优先
  * 要求服务时间相同时，等待时间长的优先
* 对于长作业而言，等待时间越长响应比越高，避免了饥饿问题

#### 是否会导致饥饿

不会

### 四、时间片轮转调度算法（RR，Round-Robin）

#### 算法思想

公平地、轮流地为各个进程服务，让每个进程在一定时间间隔内都可以得到响应。

#### 算法规则

按照各进程到达就绪队列的顺序，轮流让各个进程执行一个**时间片**。若进程未在一个时间片内执行完， 则剥夺处理机，将进程放到就绪队列队尾重新排队。

#### 用于作业/进程调度

用于**进程调度**（只有作业放入内存建立了相应的进程后，才能被分配处理机时间片)

#### 是否可抢占

若进程未能在时间片内运行完，将被强行剥夺处理机使用 权，因此时间片轮转调度算法**属于抢占式的算法**。由时钟装置发出时钟中断来通知CPU时间片已到。

#### 优缺点

* 优点
  * 公平
  * 响应快
  * 适用于分时操作系统
* 缺点
  * 高频率的进程调度会有一定的开销
  * 不能区分任务的紧急程度

#### 是否会导致饥饿

不会

{% hint style="info" %}
时间片太大：每个进程在一个时间片内完成，退化成先来先服务调度算法，增大进程响应时间

时间片太小：频繁的进程切换会造成较大的系统开销，导致实际用于进程执行的时间减少
{% endhint %}

### 五、优先级调度算法

#### 算法思想

随着计算机的发展，特别是实时操作系统的出现，越来越多的应用场景需要根据**任务的紧急程度**来决定处理顺序。

#### 算法规则

每个作业/进程有各自的优先级，调度时选择优先级最高的作业/进程。

#### 用于作业/进程调度

既可用于作业调度，也可用于进程调度，还可用于I/O调度中。

#### 是否可抢占

* 非剥夺式优先级调度算法：在进程主动放弃处理机时进行调度（非抢占的）
* 剥夺式优先级调度算法：还需要在就绪队列变化时，检查是否会发生抢占（抢占的）

#### 优缺点

* 优点
  * 用优先级区分紧急程度、重要程度
  * 适用于实时操作系统
  * 可以灵活的调整各种作业、进程的偏好程度
* 缺点
  * 若不断地有高优先级进程到来，可能会导致饥饿

#### 是否会导致饥饿

会

{% hint style="info" %}
静态优先级：进程创建时确定优先级，一直不变

动态优先级：创建进程时有一个初始值，之后视情况动态的调整优先级

* 系统进程优先级高于用户进程
* 前台进程优先级高于后台进程
* 操作系统更偏好I/O型进程
{% endhint %}

### 六、多级队列调度算法

#### 算法规则：

* 将进程划分为多个队列，例如系统进程、交互式进程、批处理进程......
* 进程创建成功后插入到某个队列中
* **队列之间**
  * 固定优先级：高优先级队列空时低优先级才能被调度
  * 时间片划分：各自分配不同百分比的时间片
* **队列内部**：不同的队列可以采取不同的调度策略

### 七、多级反馈队列调度算法

#### 算法思想

对其他调度算法的折中权衡

#### 算法规则

1. 设置多级就绪队列，各级队列优先级从高到低，时间片从小到大
2. 新进程到达时先进入第1级队列，按FCFS原则排队等待被分配时间片，若用完时间片进程还未结束，则进程进入下一级队列队尾。如果此时已经是在最下级的队列，则重新放回该队列队尾
3. 只有第k级队列为空时，才会为k+1级队头的进程分配时间片

#### 用于作业/进程调度

用于进程调度

#### 是否可抢占

**抢占式的算法**。在k级队列的进程运行过程中，若更上级的队列 (1\~k-1级)中进入了一个新进程，则由于新进程处于优先级更高的队列中，因此新进程会抢占处理机，原来运行的进程放回k级队列队尾。

#### 优缺点

* 对各类型进程相对公平（FCFS的优点）；
* 每个新到达的进程都可以很快就得到响应（RR的优点）；
* 短进程只用较少的时间就可完成非（SPF的优点）；
* 不必实现估计进程的运行时间（避免用户作假）；
* 可以灵活的调整对各类进程的偏好程度

#### 是否会导致饥饿

会

## 2.2.5 上下文及其切换机制

![](https://github.com/lijisheng27/option-system/blob/main/.gitbook/assets/%E4%B8%8A%E4%B8%8B%E6%96%87.png)

<mark style="color:red;">**红色部分**</mark>是进程/线程切换时需要保存/恢复的上下文

### 进程的上下文切换

进程切换导致的地址空间代价巨大：

* 保存/恢复页表寄存器
* TLB全部失效
* Cache全部失效，有可能需要Cache写回
* 新进程运行初期可能缺页率高，需要I/O操作
