1. 进程(Process)
定义:进程是程序的执行实例,是系统资源分配的基本单位。每个进程有独立的地址空间、代码段、数据段和堆栈。
状态:
- 就绪(Ready):等待调度器分配CPU
- 运行(Running):正在被CPU执行
- 阻塞(Blocked):等待I/O等事件完成
典型操作:
- 创建:使用
fork()
系统调用生成子进程 - 终止:
exit()
结束进程 - 切换:由操作系统调度器实现上下文切换
2. 线程(Thread)
定义:线程是CPU调度和分派的基本单位,是进程中的执行流。多个线程共享同一进程的资源(如内存空间、打开的文件)。
多线程的优点:
- 响应性强,资源开销小
- 适合并行处理
- 线程间通信更高效(共享变量,无需IPC)
示例代码(C++11 std::thread):
#include <iostream>
#include <thread>
void task(int id) {
std::cout << "Thread " << id << " is running\n";
}
int main() {
std::thread t1(task, 1);
std::thread t2(task, 2);
t1.join();
t2.join();
return 0;
}
3. 调度算法
所有调度模拟均基于如下进程结构体:
struct Process {
int pid;
int arrival_time;
int burst_time;
int priority;
int remaining_time; // 仅对抢占式或RR有用
};
FCFS(First-Come, First-Served)调度算法详解
一、调度原理
FCFS(先来先服务)是一种最基本、最简单的进程调度算法。其核心思想是:按照进程到达的先后顺序依次执行进程,一个进程执行完毕后,才调度下一个进程执行。
具体特点:
- 非抢占式:一旦某个进程被调度执行,将一直占用CPU直到完成。
- 简单公平:谁先到谁先服务。
- 缺点明显:可能导致“长作业堵塞短作业”(即Convoy Effect,护航效应)。
二、调度流程
- 将所有进程按
arrival_time
(到达时间)从小到大排序; - 遍历排好序的进程队列,依次执行每个进程;
- 若某进程到达时间晚于当前时间,CPU空闲,等待进程到来;
- 累加每个进程的
burst_time
(执行时间)以计算调度起点和终点。
三、举例说明
进程列表:
PID | 到达时间(arrival_time) | 执行时间(burst_time) |
---|---|---|
P1 | 0 | 4 |
P2 | 1 | 3 |
P3 | 2 | 2 |
P4 | 3 | 1 |
执行过程(按到达顺序):
排序结果为:
P1 (0), P2 (1), P3 (2), P4 (3)
执行步骤:
- 时间0:P1 到达并执行,运行时间4 → 结束于时间4
- 时间4:P2 已经到达 → 执行,运行时间3 → 结束于时间7
- 时间7:P3 已经到达 → 执行,运行时间2 → 结束于时间9
- 时间9:P4 已经到达 → 执行,运行时间1 → 结束于时间10
四、代码实现解释(C++)
void FCFS(std::vector<Process>& plist) {
std::sort(plist.begin(), plist.end(), [](Process a, Process b) {
return a.arrival_time < b.arrival_time;
});
int current_time = 0;
for (auto& p : plist) {
if (current_time < p.arrival_time)
current_time = p.arrival_time; // 如果CPU空闲,则等待该进程到达
std::cout << "Process " << p.pid << " starts at " << current_time << "\n";
current_time += p.burst_time;
}
}
说明:
std::sort
:确保进程按到达时间先后排列;current_time < p.arrival_time
:处理进程到达前CPU空闲的情形;current_time += burst_time
:模拟进程完整运行后累加时间。
五、评价
优点 | 缺点 |
---|---|
实现简单,顺序清晰 | 长进程阻塞短进程,平均等待时间高 |
没有上下文切换开销 | 不适合实时、交互式应用场景 |
SJF(Shortest Job First,非抢占)
原理:从就绪队列中选择运行时间最短的进程。
void SJF(std::vector<Process> plist) {
int time = 0;
std::vector<Process> ready;
while (!plist.empty() || !ready.empty()) {
for (auto it = plist.begin(); it != plist.end();) {
if (it->arrival_time <= time) {
ready.push_back(*it);
it = plist.erase(it);
} else ++it;
}
if (ready.empty()) {
time++;
continue;
}
auto it = std::min_element(ready.begin(), ready.end(),
[](Process a, Process b) { return a.burst_time < b.burst_time; });
std::cout << "Process " << it->pid << " runs at " << time << "\n";
time += it->burst_time;
ready.erase(it);
}
}
SJF(Shortest Job First)调度算法
一、调度原理
SJF(Shortest Job First),即“短作业优先调度算法”,其核心思想是:
从就绪队列中选择“执行时间(burst_time)最短”的进程先执行。
根据是否抢占,分为两种:
- 非抢占式 SJF(本节重点):当前运行的进程一旦开始,必须运行完才能调度下一个。
- 抢占式 SJF(又称 SRTF):如果有更短作业到达,会抢占当前执行进程。
二、调度策略说明
调度器会在每一个时刻,检查就绪队列中哪些进程已到达,然后从中选出 burst_time
最短的进程执行。
三、举例说明(非抢占 SJF)
进程列表:
PID | 到达时间 | 执行时间(burst_time) |
---|---|---|
P1 | 0 | 8 |
P2 | 1 | 4 |
P3 | 2 | 2 |
P4 | 3 | 1 |
执行流程:
- 时间 0:P1 到达,开始执行,持续到时间 8
- 时间 1-3:P2、P3、P4 陆续到达
- 时间 8:选出 P4(执行时间1)→ 执行至 9
- 时间 9:剩余 P3(2)与 P2(4),选 P3 → 执行至 11
- 时间 11:P2 执行至 15
四、C++ 实现代码(非抢占式 SJF)
#include <iostream>
#include <vector>
#include <algorithm>
struct Process {
int pid;
int arrival_time;
int burst_time;
};
void SJF(std::vector<Process> plist) {
int time = 0;
std::vector<Process> ready;
while (!plist.empty() || !ready.empty()) {
for (auto it = plist.begin(); it != plist.end();) {
if (it->arrival_time <= time) {
ready.push_back(*it);
it = plist.erase(it);
} else ++it;
}
if (ready.empty()) {
time++;
continue;
}
auto it = std::min_element(ready.begin(), ready.end(),
[](Process a, Process b) { return a.burst_time < b.burst_time; });
std::cout << "Process " << it->pid << " runs at " << time << "\n";
time += it->burst_time;
ready.erase(it);
}
}
五、关键点解释
plist
存放所有尚未到达的进程- 每次循环把“到达时间 <= 当前时间”的进程转入
ready
队列 - 在
ready
队列中选择burst_time
最短的进程执行 - 若
ready
队列为空,表示 CPU 空闲,时间time++
跳过等待
六、优缺点分析
优点 | 缺点 |
---|---|
能最小化平均等待时间 | 需要提前知道每个进程的执行时间 |
相对公平:短进程不再被长进程阻塞 | 容易造成长进程“饥饿”(starvation) |
提高系统吞吐率 | 不适用于实时系统,响应时间不可控 |
RR(Round Robin,时间片轮转)调度算法
一、调度原理
时间片轮转调度(Round Robin, RR) 是一种抢占式调度算法,特别适用于多用户交互式系统。
核心思想是:为每个进程分配固定长度的 时间片(Time Quantum),按到达顺序循环执行。若进程在时间片内未完成,则被抢占并重新排入队尾。
二、调度机制
- 所有到达的进程按照顺序进入一个队列(就绪队列)
- CPU 从队列头部取出一个进程,执行时间片长度
- 若进程执行完毕,终止;否则,放回队尾
- 周而复始直到所有进程完成
三、示例说明(时间片 = 2)
进程列表:
PID | 到达时间 | 执行时间(burst_time) |
---|---|---|
P1 | 0 | 5 |
P2 | 1 | 3 |
P3 | 2 | 1 |
执行过程:
- 时间 0:P1 执行 2 → 剩余 3 → 入队
- 时间 2:P2 执行 2 → 剩余 1 → 入队
- 时间 4:P3 执行 1(完成)
- 时间 5:P1 执行 2 → 剩余 1 → 入队
- 时间 7:P2 执行 1(完成)
- 时间 8:P1 执行 1(完成)
四、C++ 实现代码(带时间片控制)
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
struct Process {
int pid;
int arrival_time;
int burst_time;
int remaining_time;
};
void RR(std::vector<Process> plist, int quantum = 2) {
int time = 0;
std::queue<Process> q;
// 初始化剩余时间
for (auto& p : plist)
p.remaining_time = p.burst_time;
// 按到达时间排序
std::sort(plist.begin(), plist.end(), [](Process a, Process b) {
return a.arrival_time < b.arrival_time;
});
int index = 0;
while (index < plist.size() || !q.empty()) {
// 入队当前时刻到达的进程
while (index < plist.size() && plist[index].arrival_time <= time) {
q.push(plist[index]);
index++;
}
if (q.empty()) {
time++; // CPU 空闲
continue;
}
Process p = q.front(); q.pop();
int run_time = std::min(quantum, p.remaining_time);
std::cout << "Process " << p.pid << " runs from " << time
<< " to " << time + run_time << "\n";
time += run_time;
p.remaining_time -= run_time;
// 新到进程再次入队
while (index < plist.size() && plist[index].arrival_time <= time) {
q.push(plist[index]);
index++;
}
if (p.remaining_time > 0)
q.push(p); // 没运行完就重新入队
}
}
五、优缺点分析
优点 | 缺点 |
---|---|
所有进程公平获得 CPU 使用权 | 时间片过小:频繁切换,系统开销大 |
响应时间短,适合交互式应用 | 时间片过大:退化为 FCFS |
实现简单,易于硬件支持 | 无法动态识别进程重要性(例如高优先级任务) |
六、关键控制参数
- 时间片长度是该算法的关键性能参数:
- 过短 → 上下文切换频繁,浪费 CPU
- 过长 → 响应延迟上升,退化为 FCFS
优先级调度(Priority Scheduling)
一、调度原理
优先级调度算法根据进程的**优先级(Priority)**来选择哪个进程先执行:
- 优先级数值越小,优先级越高。
- 可以是非抢占式(本节重点),也可以是抢占式。
- 每次调度时,从就绪队列中选出当前已到达、优先级最高的进程。
二、优先级来源
优先级可以由系统分配,也可以由用户设定,通常基于以下标准:
- 进程类型(I/O密集型 vs 计算密集型)
- 等待时间(可用于动态调整优先级)
- 用户级别(管理员进程可优先)
- 任务重要性(实时任务优先)
三、调度过程举例(非抢占式)
进程列表:
PID | 到达时间 | 执行时间 | 优先级 |
---|---|---|---|
P1 | 0 | 4 | 2 |
P2 | 1 | 3 | 1 |
P3 | 2 | 1 | 3 |
P4 | 3 | 2 | 2 |
执行步骤(非抢占式):
- 时间 0:P1 到达,P1 执行
- 时间 4:P2、P3、P4 已到达,优先级最高的是 P2 → 执行
- 时间 7:P3 和 P4 在队列中,P4 优先级较高 → 执行
- 时间 9:P3 → 执行
四、C++ 实现代码(非抢占式)
#include <iostream>
#include <vector>
#include <algorithm>
struct Process {
int pid;
int arrival_time;
int burst_time;
int priority;
};
void PrioritySchedule(std::vector<Process> plist) {
int time = 0;
std::vector<Process> ready;
while (!plist.empty() || !ready.empty()) {
// 将到达的进程加入就绪队列
for (auto it = plist.begin(); it != plist.end();) {
if (it->arrival_time <= time) {
ready.push_back(*it);
it = plist.erase(it);
} else {
++it;
}
}
// 若无进程可调度,CPU空闲
if (ready.empty()) {
time++;
continue;
}
// 从就绪队列中选出优先级最高(数值最小)进程
auto it = std::min_element(ready.begin(), ready.end(),
[](Process a, Process b) {
return a.priority < b.priority;
});
std::cout << "Process " << it->pid << " (priority " << it->priority
<< ") runs from " << time << " to " << time + it->burst_time << "\n";
time += it->burst_time;
ready.erase(it);
}
}
五、优缺点分析
优点 | 缺点 |
---|---|
控制灵活:可人为设定优先级实现任务重要性调度 | 可能导致低优先级进程长期得不到执行(饥饿) |
支持抢占与非抢占多种策略 | 不公平:未考虑等待时间、到达顺序 |
易与其他策略结合:如优先级 + 时间片 | 需要附加机制防止优先级反转(priority inversion)问题 |
六、典型优化机制(了解即可)
- 优先级提升机制:等待时间越久,进程优先级逐步提高
- 优先级继承机制:低优先级进程持有关键资源时临时“继承”高优先级
七、适用场景
- 实时系统(如实时音视频流处理)
- 后台任务调度(如守护进程 vs 用户进程)
多级反馈队列(Multilevel Feedback Queue,MFQ)调度算法
一、调度原理
多级反馈队列调度是一种结合多种策略的复杂调度机制,具有以下特点:
- 存在多个优先级队列(例如Q1、Q2、Q3…)
- 每个队列有不同的时间片长度(高优先级队列时间片短,低优先级时间片长)
- 所有新到达的进程从最高优先级队列开始
- 若进程在某队列的时间片内未完成,将被“降级”到下一级队列
- 低级队列的进程执行前,必须所有高级队列为空
二、调度机制
- 结合了:FCFS + RR + 动态优先级调整
- 是现代系统(如 Linux CFS, Windows)调度器的重要基础模型
三、流程图(示意)
新进程 → Q1 → Q2 → Q3
↑ ↑ ↑
| | |
完成 | 未完成 | 未完成
within time slice → 降级 → 降级
四、示例(简化3进程,2个队列,时间片分别为2/4)
进程列表:
PID | 到达时间 | 执行时间(burst_time) |
---|---|---|
P1 | 0 | 5 |
P2 | 1 | 3 |
P3 | 2 | 6 |
执行规则:
- Q1:高优先级队列,时间片 2
- Q2:低优先级队列,时间片 4
执行过程:
- 时间 0:P1 到达 → Q1 执行 2 → 剩余 3 → 降至 Q2
- 时间 2:P2 到达 → Q1 执行 2 → 剩余 1 → 降至 Q2
- 时间 4:P3 到达 → Q1 空 → Q2 中 P1 → 执行 3 → 完成
- 时间 7:Q2 中 P2 → 执行 1 → 完成
- 时间 8:Q2 中 P3 → 执行 4 → 剩余 2 → 重新排入Q2
- 时间 12:P3 → 执行 2 → 完成
五、C++ 实现代码(双队列简化版)
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
struct Process {
int pid;
int arrival_time;
int burst_time;
int remaining_time;
};
void MFQ(std::vector<Process> plist) {
std::queue<Process> q1, q2;
int time = 0;
// 初始化剩余时间
for (auto& p : plist)
p.remaining_time = p.burst_time;
// 按到达时间排序
std::sort(plist.begin(), plist.end(), [](Process a, Process b) {
return a.arrival_time < b.arrival_time;
});
int index = 0;
while (index < plist.size() || !q1.empty() || !q2.empty()) {
// 将到达的进程加入Q1
while (index < plist.size() && plist[index].arrival_time <= time) {
q1.push(plist[index]);
index++;
}
if (!q1.empty()) {
Process p = q1.front(); q1.pop();
int run = std::min(2, p.remaining_time);
std::cout << "Q1: Process " << p.pid << " runs from " << time << " to " << time + run << "\n";
time += run;
p.remaining_time -= run;
while (index < plist.size() && plist[index].arrival_time <= time) {
q1.push(plist[index]);
index++;
}
if (p.remaining_time > 0)
q2.push(p);
} else if (!q2.empty()) {
Process p = q2.front(); q2.pop();
int run = std::min(4, p.remaining_time);
std::cout << "Q2: Process " << p.pid << " runs from " << time << " to " << time + run << "\n";
time += run;
p.remaining_time -= run;
while (index < plist.size() && plist[index].arrival_time <= time) {
q1.push(plist[index]);
index++;
}
if (p.remaining_time > 0)
q2.push(p);
} else {
time++; // CPU 空闲
}
}
}
六、优缺点分析
优点 | 缺点 |
---|---|
综合考虑响应时间、公平性、吞吐量等性能指标 | 实现复杂,需要动态管理多级队列 |
高优先级进程响应快 | 低优先级进程可能饿死(可用优先级提升机制) |
能模拟各种调度策略的组合形式 | 系统开销较大 |
七、现代系统应用
- **Windows、Linux调度器(CFS)**等都使用类似的多级反馈思想
- 实际实现中可能有几十个调度等级 + 优先级动态调整机制
主函数统一调用示例:
int main() {
std::vector<Process> plist = {
{1, 0, 5, 2, 0},
{2, 1, 3, 1, 0},
{3, 2, 8, 3, 0},
{4, 3, 6, 2, 0}
};
std::cout << "--- FCFS ---\n";
FCFS(plist);
std::cout << "\n--- SJF ---\n";
SJF(plist);
std::cout << "\n--- RR ---\n";
RR(plist);
std::cout << "\n--- Priority ---\n";
PrioritySchedule(plist);
std::cout << "\n--- MFQ ---\n";
MFQ(plist);
return 0;
}
4.调度算法总览
算法 | 抢占性 | 核心原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|
FCFS | 否 | 按到达顺序排队,先来先服务 | 简单易实现 | 长作业可能堵塞短作业(护航效应) | 批处理系统 |
SJF | 否 | 每次选择执行时间最短的进程 | 最短平均等待时间 | 需提前知道执行时间,易造成饥饿 | 知道作业长度的系统 |
RR | 是 | 每个进程轮流执行,时间片控制 | 公平性好,响应快 | 时间片过短或过长都会带来效率问题 | 多用户交互系统 |
Priority | 可选 | 选择优先级最高(数值最低)的进程执行 | 控制灵活,可引入动态调度策略 | 饥饿现象明显,需补充优先级提升机制 | 实时系统、关键任务优先场景 |
MFQ | 是 | 多级队列 + 动态降级 + 可调时间片 | 综合性强,可模拟实际操作系统策略 | 实现复杂,调参困难,开销较大 | 操作系统核心调度机制 |
调度算法的比较图(逻辑角度)
简单性: FCFS > RR > Priority > SJF > MFQ
公平性: RR > MFQ > FCFS > Priority > SJF
响应时间短: RR ≈ MFQ > Priority > FCFS > SJF
吞吐率高: SJF ≈ MFQ > Priority > FCFS > RR
实际应用性: MFQ > RR ≈ Priority > FCFS > SJF
C++实现特性小结
算法 | 代码复杂度 | 用到的数据结构 | 实现模块特点 |
---|---|---|---|
FCFS | 低 | vector + sort | 到达时间排序后顺序遍历 |
SJF | 中 | vector + min_element | 就绪队列中每次选 burst_time 最小者 |
RR | 中 | queue + round robin | 时间片控制,剩余时间循环入队 |
Priority | 中 | vector + min_element | 每次选出优先级最小者 |
MFQ | 高 | 多个 queue | 多级队列+不同时间片+降级策略 |
调度算法设计核心要点
- 是否支持抢占:RR、MFQ、抢占式 Priority 是抢占式算法
- 是否需要 burst_time 信息:SJF 依赖精确时间
- 是否有优先级策略:Priority、MFQ 支持优先级
- 是否考虑公平性:RR、MFQ设计中具备公平性
- 是否容易造成饥饿:SJF、Priority 需通过策略弥补
如何选择调度算法(场景导向)
场景 | 建议算法 |
---|---|
后台大批量处理任务 | FCFS / SJF |
需要交互响应迅速 | RR / MFQ |
需要保证实时或关键任务优先 | Priority / MFQ |
系统负载变化大,希望智能调整调度策略 | MFQ(+动态优先级) |