1. 项目概述:操作系统调度算法模拟器
这个项目是我在操作系统课程中完成的一个综合性实验,用C++实现了一个简化版的操作系统进程调度模拟器。核心功能是模拟时间片轮转与高优先级抢占调度算法,同时实现了基础的进程管理和资源分配机制。整个系统约2000行代码,采用面向对象设计,在Visual Studio 2019环境下开发完成。
为什么要做这个项目?在实际操作系统课程中,单纯的理论学习很难真正理解调度算法的工作机制。通过这个模拟器,可以直观观察到:
- 进程在不同优先级间的切换过程
- 时间片耗尽时的强制调度
- 高优先级进程如何抢占低优先级进程
- 资源分配与死锁预防的实际表现
2. 核心设计思路
2.1 调度算法选择依据
系统采用混合调度策略,主要基于两个关键考量:
- 优先级抢占:确保关键系统进程(如中断处理)能立即获得CPU
- 时间片轮转:保证同优先级进程的公平性
具体实现上分为三个优先级队列:
- System(优先级2):系统核心进程
- User(优先级1):普通用户进程
- Init(优先级0):后台进程
实际开发中发现,Windows线程优先级分为0-31级,Linux为0-139级。本模拟器做了简化处理,但保留了扩展接口。
2.2 进程控制块设计
PCB(Process Control Block)采用如下数据结构:
cpp复制struct PCB {
string pid; // 进程ID
int priority; // 当前优先级
PCB* parent; // 父进程指针
vector<PCB*> child; // 子进程列表
ProcessState state; // 运行状态
map<int, int> res; // 持有资源
// 其他元数据...
};
关键设计要点:
- 使用指针而非ID维护进程树关系,便于快速定位
- 资源记录采用
map<res_id, count>结构,支持多类型资源 - 状态枚举包含:就绪、运行、阻塞三种基本状态
2.3 资源管理机制
资源控制块RCB的设计:
cpp复制struct RCB {
int rid; // 资源ID
int total; // 资源总数
int available; // 可用数量
list<PCB*> waitq; // 等待队列
};
资源分配遵循银行家算法原则:
- 检查请求是否超过进程最大需求
- 检查系统剩余资源是否足够
- 尝试分配并检测系统安全性
3. 关键实现细节
3.1 调度器核心逻辑
调度函数伪代码:
cpp复制void schedule() {
// 1. 检查高优先级抢占
if (current_process->priority < getHighestReadyPriority()) {
preempt(current_process);
return;
}
// 2. 时间片处理
if (time_slice <= 0) {
current_process->state = READY;
ready_queue[current_process->priority].push_back(current_process);
reset_time_slice();
}
// 3. 选择下一个进程
PCB* next = find_next_process();
context_switch(next);
}
实测中发现几个关键点:
- 时间片长度设置为5个时间单位时,系统吞吐量最佳
- 频繁的优先级检查会导致性能下降,需要合理设置检查间隔
- 使用STL的priority_queue优化就绪队列查询效率
3.2 进程创建与撤销
进程创建时的父子关系处理:
cpp复制void create_process(string pid, int priority) {
PCB* parent = current_process;
PCB* child = new PCB{pid, priority, parent};
parent->child.push_back(child);
// 处理继承关系...
}
进程撤销时的递归释放:
cpp复制void destroy_process(PCB* proc) {
// 先递归终止所有子进程
for (PCB* child : proc->child) {
destroy_process(child);
}
// 释放持有资源
for (auto& [rid, count] : proc->res) {
release_resource(rid, count);
}
// 从进程树移除...
}
3.3 资源分配实现
资源请求的核心逻辑:
cpp复制bool request_resource(int rid, int count) {
if (current_process->res[rid] + count > max_need[rid]) {
return false; // 超过最大需求
}
if (rcb[rid].available >= count) {
// 直接分配
rcb[rid].available -= count;
current_process->res[rid] += count;
return true;
} else {
// 加入等待队列
current_process->state = BLOCKED;
rcb[rid].waitq.push_back(current_process);
return false;
}
}
4. 测试与问题排查
4.1 典型测试用例
shell复制# 创建系统进程
create sys_proc 2
# 创建用户进程
create user_proc1 1
create user_proc2 1
# 请求资源
request R1 2
# 强制时间片切换
timeout
4.2 遇到的典型问题
问题1:资源释放后未正确唤醒等待进程
现象:进程保持在阻塞状态不恢复
解决:在release_resource()中添加就绪队列检查:
cpp复制void release_resource(int rid, int count) {
rcb[rid].available += count;
// 检查等待队列
for (auto it = rcb[rid].waitq.begin(); it != rcb[rid].waitq.end();) {
if ((*it)->res[rid] <= rcb[rid].available) {
(*it)->state = READY;
ready_queue[(*it)->priority].push_back(*it);
it = rcb[rid].waitq.erase(it);
} else {
++it;
}
}
}
问题2:进程优先级反转
场景:高优先级进程等待低优先级进程持有的资源
方案:实现优先级继承协议,临时提升持有资源进程的优先级
5. 性能优化记录
5.1 数据结构改进
初始版本使用vector存储就绪队列,调度时需遍历查找,时间复杂度O(n)。优化后方案:
cpp复制// 使用优先队列优化
vector<priority_queue<PCB*>> ready_queue;
// 添加进程时自动排序
void add_to_ready(PCB* proc) {
ready_queue[proc->priority].push(proc);
}
5.2 调度算法调优
通过统计分析发现:
- 系统进程平均运行时间:8时间单位
- 用户进程平均运行时间:15时间单位
因此将默认时间片调整为:
- 优先级2:10时间单位
- 优先级1:20时间单位
- 优先级0:50时间单位
调整后上下文切换次数减少37%
6. 扩展功能实现
6.1 命令行解释器
支持的命令列表:
| 命令 | 参数 | 功能 |
|---|---|---|
| cr | 创建进程 | |
| de | 销毁进程 | |
| req | 请求资源 | |
| rel | 释放资源 | |
| to | - | 强制时间片切换 |
实现关键点:
cpp复制void execute_command(const string& cmd, vector<string>& args) {
if (cmd == "cr") {
create_process(args[0], stoi(args[1]));
}
// 其他命令处理...
}
6.2 状态可视化
开发过程中添加的状态显示函数:
cpp复制void show_status() {
cout << "Running: " << (current_process ? current_process->pid : "NULL") << endl;
for (int i = 2; i >= 0; --i) {
cout << "Priority " << i << " ready queue: ";
for (auto p : ready_queue[i]) {
cout << p->pid << " ";
}
cout << endl;
}
}
7. 项目经验总结
通过这个项目,我深刻理解了操作系统的几个关键机制:
-
调度策略选择:并非算法越复杂越好,需要权衡吞吐量、响应时间和实现复杂度。实测发现,简单的多级反馈队列(MLFQ)在多数场景下已经足够高效。
-
资源管理陷阱:
- 必须维护资源分配图来检测死锁
- 资源释放时要及时唤醒等待进程
- 避免优先级反转问题
-
调试技巧:
- 使用状态快照函数辅助调试
- 为每个操作添加日志记录
- 先测��单进程场景,再逐步增加复杂度
这个模拟器后续还可以扩展:
- 增加文件系统模拟
- 实现内存分页管理
- 添加网络协议栈模拟
- 支持多机分布式调度
