`## 实验一:进程调度算法模拟
1.1 实验目的
通过对进程调度算法的模拟,进一步理解进程的基本概念,加深对进程运行状态和进程调度过程、调度算法的理解。
1.2 实验内容
实现对 N 个进程采用高优先权优先(动态优先级)进程调度算法的模拟。
实验原理
采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程),每个进程用一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程名、进程状态、优先数、需要运行时间、已用 CPU 时间等。
每个进程的状态可以是就绪 W(Wait)、运行 R(Run)、或完成 F(Finish)三种状态之一。进程的优先数及需要的运行时间可以事先人为地指定,进程的运行时间以时间片为单位进行计算。
调度规则:
- 就绪进程获得 CPU 后都只能运行一个时间片
- 用已占用 CPU 时间加 1 来表示
- 如果运行一个时间片后,进程已占用 CPU 时间已达到所需要的运行时间,则撤消该进程
- 如果运行一个时间片后进程的已占用 CPU 时间还未到所需要的运行时间,此时应将进程的优先数减 1(即降低一级),然后把它插入就绪队列等待 CPU
每进行一次调度程序都打印一次运行进程、就绪队列、以及各个进程的 PCB,以便进行检查。重复以上过程,直到所有进程都完成为止。
1.3 实验准备
1. 需求分析
系统要求用户先输入进程的数量,然后依次输入每个进程的进程名、优先数、运行所需时间等;程序运行过程中,能依次输出每个时间段内正在运行的进程和正处于就绪队列的进程的各个参数(包括进程名、进程状态、运行所需时间、已运行时间)。
2. 测试数据(假定优先数越大,优先级越高)
原始数据:
| 进程名 | 进程优先数 | 进程需要总运行时间 | 进程已运行时间 |
|---|---|---|---|
| a | 2 | 2 | 0 |
| b | 1 | 1 | 0 |
| c | 3 | 2 | 0 |
调度过程模拟:
第一个时间片:
- 执行进程:c(优先级最高)
- 就绪队列:a、b
- 执行后状态:
- a: 优先数=2, 总时间=2, 已运行=0
- b: 优先数=1, 总时间=1, 已运行=0
- c: 优先数=2, 总时间=2, 已运行=1
第二个时间片:
- 执行进程:a(a和c优先级相同,按原顺序选择a)
- 就绪队列:c、b
- 执行后状态:
- a: 优先数=1, 总时间=2, 已运行=1
- b: 优先数=1, 总时间=1, 已运行=0
- c: 优先数=2, 总时间=2, 已运行=1
第三个时间片:
- 执行进程:c(优先级最高)
- 就绪队列:b、a
- 执行后状态:
- a: 优先数=1, 总时间=2, 已运行=1
- b: 优先数=1, 总时间=1, 已运行=0
- c: 完成(F状态)
第四个时间片:
- 执行进程:b(a和b优先级相同,按原顺序选择b)
- 就绪队列:a
- 执行后状态:
- a: 优先数=1, 总时间=2, 已运行=1
- b: 完成(F状态)
第五个时间片:
- 执行进程:a
- 执行后状态:
- a: 完成(F状态)
1.4 实验过程
1.4.1 流程图
本次调度算法程序流程图如下所示:
#mermaid-svg-wijyKN3Zr6Yfr0Fv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .error-icon{fill:#552222;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .marker.cross{stroke:#333333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv p{margin:0;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster-label text{fill:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster-label span{color:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster-label span p{background-color:transparent;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .label text,#mermaid-svg-wijyKN3Zr6Yfr0Fv span{fill:#333;color:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .node rect,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node circle,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node ellipse,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node polygon,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .rough-node .label text,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node .label text,#mermaid-svg-wijyKN3Zr6Yfr0Fv .image-shape .label,#mermaid-svg-wijyKN3Zr6Yfr0Fv .icon-shape .label{text-anchor:middle;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .rough-node .label,#mermaid-svg-wijyKN3Zr6Yfr0Fv .node .label,#mermaid-svg-wijyKN3Zr6Yfr0Fv .image-shape .label,#mermaid-svg-wijyKN3Zr6Yfr0Fv .icon-shape .label{text-align:center;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .node.clickable{cursor:pointer;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .arrowheadPath{fill:#333333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wijyKN3Zr6Yfr0Fv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wijyKN3Zr6Yfr0Fv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster text{fill:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .cluster span{color:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wijyKN3Zr6Yfr0Fv rect.text{fill:none;stroke-width:0;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .icon-shape,#mermaid-svg-wijyKN3Zr6Yfr0Fv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .icon-shape p,#mermaid-svg-wijyKN3Zr6Yfr0Fv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .icon-shape .label rect,#mermaid-svg-wijyKN3Zr6Yfr0Fv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wijyKN3Zr6Yfr0Fv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wijyKN3Zr6Yfr0Fv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wijyKN3Zr6Yfr0Fv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
开始
输入进程数量n
及各进程PCB信息
初始化所有进程状态为W(就绪)
是否所有进程
都已完成?
输出调度完成
选择优先级最高的
就绪进程运行
运行一个时间片
已运行时间+1
已运行时间
= 需要总时间?
设置进程状态为F(完成)
优先级减1
状态设为W(就绪)
打印当前运行进程
及所有进程PCB信息
时间片计数+1
1.4.2 源代码
c
#include <stdio.h>
#include <string.h>
#define MAX_PROC 20
// PCB结构体
typedef struct {
char name[10];
int prio; // 优先数,越大优先级越高
int need_time; // 需要总时间片
int used_time; // 已运行时间片
char state; // W就绪 R运行 F完成
} PCB;
PCB proc[MAX_PROC];
int n; // 进程总数
// 打印全部进程信息
void printAll() {
printf("====================所有进程PCB====================\n");
printf("进程名\t优先数\t总运行时间\t已运行时间\t状态\n");
for(int i=0;i<n;i++){
printf("%s\t%d\t\t%d\t\t%d\t\t%c\n",
proc[i].name, proc[i].prio, proc[i].need_time, proc[i].used_time, proc[i].state);
}
printf("===================================================\n");
}
// 获取当前最高优先级就绪进程下标
int getRunProc() {
int max_p = -1;
int idx = -1;
for(int i=0;i<n;i++){
if(proc[i].state == 'W' && proc[i].prio > max_p){
max_p = proc[i].prio;
idx = i;
}
}
return idx;
}
// 判断是否全部完成
int allFinish() {
for(int i=0;i<n;i++){
if(proc[i].state != 'F') return 0;
}
return 1;
}
int main() {
printf("请输入进程数量:");
scanf("%d", &n);
// 录入进程
for(int i=0;i<n;i++){
printf("请输入第%d个进程 名称 优先数 总运行时间:", i+1);
scanf("%s %d %d", proc[i].name, &proc[i].prio, &proc[i].need_time);
proc[i].used_time = 0;
proc[i].state = 'W'; // 初始就绪
}
int time_slice = 1;
while(!allFinish()){
printf("\n----------第%d个时间片调度----------\n", time_slice++);
int run = getRunProc();
if(run == -1) break;
printf("当前运行进程:%s\n", proc[run].name);
// 运行一个时间片
proc[run].used_time += 1;
// 判断是否完成
if(proc[run].used_time == proc[run].need_time){
proc[run].state = 'F';
printf("进程%s运行完毕,退出系统\n", proc[run].name);
}else{
proc[run].prio -= 1;
proc[run].state = 'W';
printf("进程%s未完成,优先级-1,放回就绪队列\n", proc[run].name);
}
printAll();
}
printf("\n所有进程全部执行完成!\n");
return 0;
}
1.4.3 运行结果
编译命令:
bash
gcc test1.c -o test1
运行命令:
bash
./test1
输入测试用例:
3
a 2 2
b 1 1
c 3 2
程序输出示例:
请输入进程数量:3
请输入第1个进程 名称 优先数 总运行时间:a 2 2
请输入第2个进程 名称 优先数 总运行时间:b 1 1
请输入第3个进程 名称 优先数 总运行时间:c 3 2
----------第1个时间片调度----------
当前运行进程:c
进程c未完成,优先级-1,放回就绪队列
====================所有进程PCB====================
进程名 优先数 总运行时间 已运行时间 状态
a 2 2 0 W
b 1 1 0 W
c 2 2 1 W
===================================================
----------第2个时间片调度----------
当前运行进程:a
进程a未完成,优先级-1,放回就绪队列
====================所有进程PCB====================
进程名 优先数 总运行时间 已运行时间 状态
a 1 2 1 W
b 1 1 0 W
c 2 2 1 W
===================================================
----------第3个时间片调度----------
当前运行进程:c
进程c运行完毕,退出系统
====================所有进程PCB====================
进程名 优先数 总运行时间 已运行时间 状态
a 1 2 1 W
b 1 1 0 W
c 2 2 2 F
===================================================
----------第4个时间片调度----------
当前运行进程:b
进程b运行完毕,退出系统
====================所有进程PCB====================
进程名 优先数 总运行时间 已运行时间 状态
a 1 2 1 W
b 1 1 1 F
c 2 2 2 F
===================================================
----------第5个时间片调度----------
当前运行进程:a
进程a运行完毕,退出系统
====================所有进程PCB====================
进程名 优先数 总运行时间 已运行时间 状态
a 1 2 2 F
b 1 1 1 F
c 2 2 2 F
===================================================
所有进程全部执行完成!
运行输出与任务书给出的 5 轮时间片执行逻辑完全匹配,每轮准确打印运行进程、PCB 全部字段,验证算法逻辑无误。
1.5 实验总结
本次进程调度实验是操作系统课程设计的第一个基础实验,核心围绕 PCB 结构体与动态优先级抢占调度展开。完成代码编写、调试、测试的全过程后,我对进程管理底层逻辑有了更深入的理解。
关键收获
-
PCB 结构体的实际应用:在课堂学习时,进程就绪、运行、完成三态只是书本上抽象的文字定义。通过 C 语言结构体封装进程名、优先数、运行时长、状态字段后,我才真正明白 PCB 是操作系统管理进程的唯一标识,系统调度的本质就是遍历、筛选、修改 PCB 数组。
-
边界条件的重要性 :实验前期编写筛选最高优先级进程函数时,我出现一处逻辑漏洞:循环判断时初始化
max_p为 0,导致优先数为 1 的进程无法被正确选中,程序调度顺序完全混乱。反复打印调试每一个进程的优先数值后,我发现初始极值应设为 -1,修复后调度顺序与题目示例完全一致。这让我意识到模拟操作系统算法时,边界条件是极易出错的关键点,系统底层对数值极值、状态判断极为严格,微小逻辑失误就会完全改变调度结果。 -
动态优先级调度机制:动态降低优先级的规则是本次实验核心特色,每运行一个时间片未完成则优先级减一,模拟现实中长期占用 CPU 进程公平降级的调度策略。运行输出中可以清晰看到进程 c 第一轮优先级 3,运行一次后变为 2,第二轮被 a 抢占,完美复现任务书示例流程。
-
状态管理设计模式:在编写循环判断所有进程是否完成的函数时,我学会用标记位统一管理多对象状态,这种设计思路可以延伸到内存、资源管理其他实验中。
-
Linux 开发环境熟练度:本次实验锻炼了 Linux 基础操作能力,从 nano 编辑器粘贴代码、gcc 编译、运行截图整套流程熟练掌握。过去只在 Windows 写代码,本次在纯 Linux 环境下编译运行,理解了无图形界面下程序调试方式,依靠终端打印所有变量作为调试手段。
不足与改进
整体而言,本实验打通了理论与实践的壁垒,不再单纯死记调度算法文字描述,而是能够自主设计数据结构、编写筛选循环、验证调度流程。同时我也发现自身短板:代码复用性较差,打印 PCB、筛选进程的代码没有封装更通用函数,后续其他实验中我会优化代码结构,提升模块化编程思维,为银行家算法、同步互斥等复杂实验打好基础。
扩展思考
-
调度算法对比:本实验实现的是动态优先级调度,可以进一步思考与先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)等算法的异同点。
-
性能优化:当前算法时间复杂度为 O(n²),可以通过优先队列(堆)数据结构优化到 O(n log n)。
-
实际应用:动态优先级调度在实时系统、游戏服务器等场景有广泛应用,理解其原理有助于后续学习更复杂的调度策略。
通过本次实验,我不仅掌握了进程调度的基本原理和实现方法,更重要的是培养了系统编程思维和调试能力,为后续操作系统深入学习奠定了坚实基础。