调度算法
一、实验内容
- 实现先来先服务调度算法(SPF)
- 实现时间片轮转调度算法(RR)
二、实验目的
- 通过对进程调度算法的设计,深入理解进程调度的原理。
- 进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
- 进程调度分配处理机,是控制协调进程对CPU的竞争,即按一定的调度算法从就绪队列中选
中一个进程,把CPU的使用权交给被选中的进程。
三、实验题目
- 先来先服务(FCFS)调度算法
- 原理:每次调度是从就绪队列中,选择一个最先进入就绪队列的进程,把处理器分配给该进程,使之得到执行。该进程一旦占有了处理器,它就一直运行下去,直到该进程完成或因发生事件而阻塞,才退出处理器。
- 将用户作业和就绪进程按提交顺序或变为就绪状态的先后排成队列,并按照先来先服务的方式进行调度处理,是一种最普遍和最简单的方法。它优先考虑在系统中等待时间最长的作业,而不管要求运行时间的长短。
- 按照就绪进程进入就绪队列的先后次序进行调度,简单易实现,利于长进程,CPU繁忙型作业,不利于短进程,排队时间相对过长
- 时间片轮转调度算法RR
- 原理:时间片轮转法主要用于进程调度。采用此算法的系统,其程序就绪队列往往按进程到达的时间来排序。进程调度按一定时间片(q)轮番运行各个进程.
- 进程按到达时间在就绪队列中排队,调度程序每次把CPU分配给就绪队列首进程使用一个时间片,运行完一个时间片释放CPU,排到就绪队列末尾参加下一轮调度,CPU分配给就绪队列的首进程。
- 固定时间片轮转法:
1 所有就绪进程按 FCFS 规则排队。
2 处理机总是分配给就绪队列的队首进程。
3 如果运行的进程用完时间片,则系统就把该
进程送回就绪队列的队尾,重新排队。
4 因等待某事件而阻塞的进程送到阻塞队列。
5 系统把被唤醒的进程送到就绪队列的队尾
四、实验设计和过程
FCFS调度算法
数据结构
struct Process //进程结构体,表示进程信息
{
string name; //进程名字
int arrive_time; //到达时间
int service_time; //进程运行时间
int wait_time; //进程等待时间
int remain_time; //进程剩余运行时间
int turnaround_time; //周转时间
int start_time; //进程开始运行时间
int end_time; //进程结束时间
//int time_slice; //进程时间片
} run_process, p[MAXname];
queue<Process> ready_queue, block_queue;
符号说明
int n; //进程数量
int current_time = 0; //当前时间
int t = 0;
函数说明
进程初始化函数
获取进程信息
- 将进程按照到达时间加入阻塞队列
void process_init() //进程初始化
{
cout << "请输入进程数量" << endl;
cin >> n;
for (int i = 0; i < n; i++)
{
cout << "请输入第" << i + 1 << "个进程的名字 到达时间 服务时间" << endl;
cin >> p[i].name >> p[i].arrive_time >> p[i].service_time;
p[i].remain_time = p[i].service_time;
}
run_process.name = "0"; //当前无进程运行
sort(p, p + n, cmp);
for (int i = 0; i < n; i++)
{
block_queue.push(p[i]); //将进程加入阻塞队列(未达到则视为阻塞)
}
}
获取下一个进程函数
这部分代码重复率较高,故抽象出来组成函数
- 当就绪队列非空时,从中取出一个进程运行,同时计算该进程的等待时间
- 否则将当前进程置为空
void nextprocess() //取下一个进程
{
if (!ready_queue.empty()) //当就绪队列非空时
{
run_process = ready_queue.front(); //取下个进程
run_process.start_time=current_time;
run_process.wait_time += current_time - run_process.arrive_time; //计算等待时间
ready_queue.pop();
}
else //否则将运行进程置为空
run_process.name = "0";
}
进程调度函数
实现进程调度
- 当就绪队列、阻塞队列、运行进程都为空时,说明进程全部运行完,结束程序
- 当阻塞队列非空且队首元素到达时间已到当前时间,则将阻塞队列队首元素加入就绪队列,并将其从阻塞队列删除
- 就绪队列非空和运行进程为空时,则从就绪队列取元素,并将其从阻塞队列删除
- 当有进程运行时,输出进程信息,同时改变当前时间等信息
- 如果当前进程时间片用完,则将其记录,同时获取下一个进程,程序继续循环
void process_schedule() //进程调度函数
{
srand(time(NULL)); //随机数种子
cout << endl
<< "------------------------------------FCFS调度算法------------------------------------" << endl
<< endl;
cout << "当前时间 "
<< "当前进程 "
<< "进程到达时间 "
<< "进程等待时间 "
<< "进程服务时间 "
<< "剩余运行时间 "
<< endl;
while (1)
{
if (block_queue.empty() && ready_queue.empty() && run_process.name == "0") //当三个队列均为空时,说明进程全部运行完
break; //结束
if (!block_queue.empty() && block_queue.front().arrive_time <= current_time)
{ //阻塞队列非空且队首元素到达时间已到当前时间
block_queue.front().arrive_time = current_time; //将进入就绪队列的时间作为进程到达时间(针对于阻塞过的进程)
ready_queue.push(block_queue.front()); //则加入就绪队列
block_queue.pop(); //从阻塞队列删除
}
if (!ready_queue.empty() && run_process.name == "0") //就绪队列非空和运行进程为空时
{
run_process = ready_queue.front(); //则从就绪队列取首元素
run_process.start_time=current_time;
ready_queue.pop(); //从就绪队列删除
}
if (run_process.name == "0") //若当前无运行进程,则输出空
{
cout << setw(8) << current_time
<< setw(10) << run_process.name << endl;
current_time++;
}
else
{ //当前有运行进程则输出信息
cout << setw(8) << current_time
<< setw(10) << run_process.name
<< setw(14) << run_process.arrive_time
<< setw(14) << run_process.wait_time
<< setw(14) << run_process.service_time
<< setw(14) << run_process.remain_time
<< endl;
current_time++; //当前时间加1
run_process.remain_time--; //进程剩余运行时间减1
if (run_process.remain_time == 0) //当进程剩余运行时间为0
{
run_process.end_time=current_time;
run_process.turnaround_time=current_time-run_process.start_time;
p[t++] = run_process;
nextprocess(); //取下一个进程
continue;
}
else if (block()) //当进程有运行时间和时间片时,可能发生阻塞
{
cout << "-----------------进程阻塞,切换进程----------------" << endl;
run_process.arrive_time = current_time; //若发生阻塞,修改当前进程到达时间
block_queue.push(run_process); //并加入阻塞队列中
nextprocess();
}
}
}
}
打印进程信息函数
输出进程信息,并输出整个过程的平均等待时间
void print_process_information() //输出进程信息
{
float sumwait = 0;
cout << "进程名字 "
<< "到达时间 "
<< "服务时间 "
<< "开始执行时间 "
<< "结束执行时间 "
<< "周转时间 "
<< "等待时间 "
<< endl;
for (int i = 0; i < n; i++)
{
cout << setw(8) << p[i].name
<< setw(10) << p[i].arrive_time
<< setw(10) << p[i].service_time
<< setw(14) << p[i].start_time
<< setw(14) << p[i].end_time
<< setw(10) << p[i].turnaround_time
<< setw(10) << p[i].wait_time
<< endl;
sumwait += p[i].wait_time;
}
cout << "平均等待时间为:" << setprecision(2) << setiosflags(ios::fixed) << float(sumwait / n) << endl;
}
阻塞函数
随机概率阻塞
设置了30%的阻塞概率
int block() //阻塞函数,随机概率阻塞
{
int a;
a = rand() % 10;
//return 0;
if (a >= 0 && a < 3)
{
return 1;
}
else
return 0;
}
源代码
#include <bits/stdc++.h>
using namespace std;
#define MAXname 100
struct Process //进程结构体,表示进程信息
{
string name; //进程名字
int arrive_time; //到达时间
int service_time; //进程运行时间
int wait_time; //进程等待时间
int remain_time; //进程剩余运行时间
int turnaround_time; //周转时间
int start_time; //进程开始运行时间
int end_time; //进程结束时间
//int time_slice; //进程时间片
} run_process, p[MAXname];
int n; //进程数量
int current_time = 0; //当前时间
int t = 0;
queue<Process> ready_queue, block_queue, run_queue;
bool cmp(Process &a, Process &b)
{
return a.arrive_time < b.arrive_time;
}
void process_init() //进程初始化
{
cout << "请输入进程数量" << endl;
cin >> n;
for (int i = 0; i < n; i++)
{
cout << "请输入第" << i + 1 << "个进程的名字 到达时间 服务时间" << endl;
cin >> p[i].name >> p[i].arrive_time >> p[i].service_time;
p[i].remain_time = p[i].service_time;
}
run_process.name = "0"; //当前无进程运行
sort(p, p + n, cmp);
for (int i = 0; i < n; i++)
{
block_queue.push(p[i]); //将进程加入阻塞队列(未达到则视为阻塞)
}
}
void nextprocess() //取下一个进程
{
if (!ready_queue.empty()) //当就绪队列非空时
{
run_process = ready_queue.front(); //取下个进程
run_process.start_time=current_time;
run_process.wait_time += current_time - run_process.arrive_time; //计算等待时间
ready_queue.pop();
}
else //否则将运行进程置为空
run_process.name = "0";
}
int block() //阻塞函数,随机概率阻塞
{
int a;
a = rand() % 10;
//return 0;
if (a >= 0 && a < 3)
{
return 1;
}
else
return 0;
}
void process_schedule() //进程调度函数
{
srand(time(NULL)); //随机数种子
cout << endl
<< "------------------------------------FCFS调度算法------------------------------------" << endl
<< endl;
cout << "当前时间 "
<< "当前进程 "
<< "进程到达时间 "
<< "进程等待时间 "
<< "进程服务时间 "
<< "剩余运行时间 "
<< endl;
while (1)
{
if (block_queue.empty() && ready_queue.empty() && run_process.name == "0") //当三个队列均为空时,说明进程全部运行完
break; //结束
if (!block_queue.empty() && block_queue.front().arrive_time <= current_time)
{ //阻塞队列非空且队首元素到达时间已到当前时间
block_queue.front().arrive_time = current_time; //将进入就绪队列的时间作为进程到达时间(针对于阻塞过的进程)
ready_queue.push(block_queue.front()); //则加入就绪队列
block_queue.pop(); //从阻塞队列删除
}
if (!ready_queue.empty() && run_process.name == "0") //就绪队列非空和运行进程为空时
{
run_process = ready_queue.front(); //则从就绪对列取首元素
run_process.start_time=current_time;
ready_queue.pop(); //从就绪队列删除
}
if (run_process.name == "0") //若当前无运行进程,则输出空
{
cout << setw(8) << current_time
<< setw(10) << run_process.name << endl;
current_time++;
}
else
{ //当前有运行进程则输出信息
cout << setw(8) << current_time
<< setw(10) << run_process.name
<< setw(14) << run_process.arrive_time
<< setw(14) << run_process.wait_time
<< setw(14) << run_process.service_time
<< setw(14) << run_process.remain_time
<< endl;
current_time++; //当前时间加1
run_process.remain_time--; //进程剩余运行时间减1
if (run_process.remain_time == 0) //当进程剩余运行时间为0
{
run_process.end_time=current_time;
run_process.turnaround_time=current_time-run_process.start_time;
p[t++] = run_process;
nextprocess(); //取下一个进程
continue;
}
else if (block()) //当进程有运行时间和时间片时,可能发生阻塞
{
cout << "-----------------进程阻塞,切换进程----------------" << endl;
run_process.arrive_time = current_time; //若发生阻塞,修改当前进程到达时间
block_queue.push(run_process); //并加入阻塞队列中
nextprocess();
}
}
}
}
void print_process_information() //输出进程信息
{
float sumwait = 0;
cout << "进程名字 "
<< "到达时间 "
<< "服务时间 "
<< "开始执行时间 "
<< "结束执行时间 "
<< "周转时间 "
<< "等待时间 "
<< endl;
for (int i = 0; i < n; i++)
{
cout << setw(8) << p[i].name
<< setw(10) << p[i].arrive_time
<< setw(10) << p[i].service_time
<< setw(14) << p[i].start_time
<< setw(14) << p[i].end_time
<< setw(10) << p[i].turnaround_time
<< setw(10) << p[i].wait_time
<< endl;
sumwait += p[i].wait_time;
}
cout << "平均等待时间为:" << setprecision(2) << setiosflags(ios::fixed) << float(sumwait / n) << endl;
}
int main()
{
process_init();
process_schedule();
print_process_information();
return 0;
}
程序初值和运行结果
不考虑阻塞:
初值:
3
ghh 3 6
ga 4 8
hr 8 4
运行结果
考虑阻塞:
初值:
3
ghh 3 6
ga 4 8
hr 8 4
运行结果:
RR调度算法
数据结构
struct Process //进程结构体,表示进程信息
{
string name; //进程名字
int arrive_time; //到达时间
int service_time; //进程运行时间
int wait_time; //进程等待时间
int remain_time; //进程剩余运行时间
int turnaround_time; //周转时间
int start_time; //进程开始运行时间
int end_time; //进程结束时间
int time_slice; //进程时间片
} run_process, p[MAXname];
queue<Process> ready_queue, block_queue, run_queue;
符号说明
int n; //进程数量
int current_time = 0; //当前时间
int q = 0; //设定的时间片大小
int t = 0;
函数说明
函数设计基本和FCFS相同
少数的不同之处是
- 进程初始化函数加上了时间片的初始化
- 进程调度函数加上了时间片用完时的进程切换
- 时间片用完时回复当前进程的时间片,同时更新到达时间,并将其再次入队
void procese_schedule()
{
`````````//省略部分具体见源代码
`````````
current_time++; //当前时间加1
run_process.remain_time--; //进程剩余运行时间减1
run_process.time_slice--; //进程时间片时间减1
if (run_process.remain_time == 0) //当进程剩余运行时间为0
{
p[t++] = run_process;
nextprocess(); //取下一个进程
continue;
}
else if (run_process.time_slice == 0) //当进程时间片为0时
{
run_process.time_slice = q; //恢复时间片
run_process.arrive_time = current_time; //更新到达时间
ready_queue.push(run_process); //再次入队
nextprocess(); //取下一个进程
continue;
}
else if (block()) //当进程有运行时间和时间片时,可能发生阻塞
{
cout << "-----------------进程阻塞,切换进程----------------" << endl;
run_process.arrive_time = current_time; //若发生阻塞,修改当前进程到达时间
block_queue.push(run_process); //并加入阻塞队列中
nextprocess();
}
`````````//省略部分具体见源代码
`````````
}
源代码
#include <bits/stdc++.h>
using namespace std;
#define MAXname 100
int n; //进程数量
int current_time = 0; //当前时间
int q = 0; //设定的时间片大小
int t = 0;
struct Process //进程结构体,表示进程信息
{
string name; //进程名字
int arrive_time; //到达时间
int service_time; //进程运行时间
int wait_time; //进程等待时间
int remain_time; //进程剩余运行时间
int turnaround_time; //周转时间
int start_time; //进程开始运行时间
int end_time; //进程结束时间
int time_slice; //进程时间片
} run_process, p[MAXname];
queue<Process> ready_queue, block_queue;//依次为就绪队列、阻塞队列
bool cmp(Process &a, Process &b)
{
return a.arrive_time < b.arrive_time;
}
void process_init() //进程初始化
{
cout << "请输入进程数量" << endl;
cin >> n;
cout << "请输入时间片长度" << endl;
cin >> q;
for (int i = 0; i < n; i++)
{
cout << "请输入第" << i + 1 << "个进程的名字 到达时间 服务时间" << endl;
cin >> p[i].name >> p[i].arrive_time >> p[i].service_time;
p[i].remain_time = p[i].service_time;
p[i].time_slice = q; //设置进程时间片
p[i].end_time = 0;
}
run_process.name = "0"; //当前无进程运行
sort(p, p + n, cmp);
for (int i = 0; i < n; i++)
{
block_queue.push(p[i]); //将进程加入阻塞队列(未达到则视为阻塞)
}
}
void nextprocess() //取下一个进程
{
if (!ready_queue.empty()) //当就绪队列非空时
{
run_process = ready_queue.front(); //取下个进程
ready_queue.pop();
}
else //否则将运行进程置为空
run_process.name = "0";
run_process.wait_time += current_time - run_process.arrive_time; //计算等待时间
}
int block() //阻塞函数,随机概率阻塞
{
int a;
a = rand() % 10;
return 0;
if (a >= 0 && a < 3)
{
return 1;
}
else
return 0;
}
void process_schedule() //进程调度函数
{
srand(time(NULL)); //随机数种子
cout << endl
<< "----------------------------------------RR调度算法----------------------------------------"
<< endl
<< endl;
cout << "当前时间 "
<< "当前进程 "
<< "进程到达时间 "
<< "进程等待时间 "
<< "进程服务时间 "
<< "剩余运行时间 "
<< "剩余时间片时间"
<< endl;
while (1)
{
if (block_queue.empty() && ready_queue.empty() && run_process.name == "0") //当三个队列均为空时,说明进程全部运行完
break; //结束
if (!block_queue.empty() && block_queue.front().arrive_time <= current_time)
{ //阻塞队列非空且队首元素到达时间已到当前时间
block_queue.front().arrive_time = current_time; //将进入就绪队列的时间作为进程到达时间(针对于阻塞过的进程)
ready_queue.push(block_queue.front()); //则加入就绪队列
block_queue.pop(); //从阻塞队列删除
}
if (!ready_queue.empty() && run_process.name == "0") //就绪队列非空和运行进程非空
{
run_process = ready_queue.front(); //则从就绪对列取首元素
ready_queue.pop(); //从就绪队列删除
}
if (run_process.name == "0") //若当前无运行进程,则输出空
{
cout << setw(8) << current_time
<< setw(10) << run_process.name << endl;
current_time++;
}
else
{ //当前有运行进程则输出信息
cout << setw(8) << current_time
<< setw(10) << run_process.name
<< setw(14) << run_process.arrive_time
<< setw(14) << run_process.wait_time
<< setw(14) << run_process.service_time
<< setw(14) << run_process.remain_time
<< setw(14) << run_process.time_slice
<< endl;
current_time++; //当前时间加1
run_process.remain_time--; //进程剩余运行时间减1
run_process.time_slice--; //进程时间片时间减1
if (run_process.remain_time == 0) //当进程剩余运行时间为0
{
p[t++] = run_process;
nextprocess(); //取下一个进程
continue;
}
else if (run_process.time_slice == 0) //当进程时间片为0时
{
run_process.time_slice = q; //恢复时间片
run_process.arrive_time = current_time; //更新到达时间
ready_queue.push(run_process); //再次入队
nextprocess(); //取下一个进程
continue;
}
else if (block()) //当进程有运行时间和时间片时,可能发生阻塞
{
cout << "-----------------进程阻塞,切换进程----------------" << endl;
run_process.arrive_time = current_time; //若发生阻塞,修改当前进程到达时间
block_queue.push(run_process); //并加入阻塞队列中
nextprocess();
}
}
}
}
void print_process_information() //输出进程信息
{
float sumwait = 0;
cout << "进程名字 "
<< "服务时间 "
<< "等待时间 "
<< endl;
for (int i = 0; i < n; i++)
{
cout << setw(8) << p[i].name
<< setw(10) << p[i].service_time
<< setw(10) << p[i].wait_time
<< endl;
sumwait += p[i].wait_time;
}
cout << "平均等待时间为:" << setprecision(2) << setiosflags(ios::fixed) << float(sumwait / n) << endl;
}
int main()
{
process_init();
process_schedule();
print_process_information();
return 0;
}
程序初值和运行结果
不考虑阻塞:
初值(小时间片):
3
2
ghh 3 6
ga 4 8
hr 8 4
运行结果:
考虑阻塞:
初值(小时间片):
3
2
ghh 3 6
ga 4 8
hr 8 4
运行结果:
时间片很大(等同于FCFS)
初值:
3
60
ghh 3 6
ga 4 8
hr 8 4
运行结果:
思考题
根据上面的观察结果,比较这两种算法各自的优缺点,根据结果再和其他的算法比较。
FCFS 调度的代码编写简单且容易理解。但是, 采用FCFS 策略的平均等待时间通常较长。
RR算法的性能很大程度上依赖于时间片的大小。在极端情况下,如果时间片非常大,那么RR算法与FCFS算法一样,如果时间片很小(如1ms),那么R算法称为处理器共享。
而SJF 调度算法可证明为最佳的, 这是因为对于给定的一组进程, SJF 算法的平均等待时间最小。
优先级调度算法可以自行选择进程优先级来调度,但是其一个主要问题是无穷阻塞(indefinite blocking)或饥饿( starvation)。可以运行但缺乏CPU 的进程可认为是阻塞的,它在等待CPU。优先级调度算法会使某个低优先级进程无穷等待CPU。
多级反馈队列调度算法允许进程在队列之间移动。主要思想是根据不同CPU区间的特点以区分进程。如果进程使用过多CPU时间, 那么它会被转移到更低优先级队列。这种方案将I/O约束和交互进程留在更高优先级队列。此外, 在较低优先级队列中等待时间过长的进程会被转移到更高优先级队列。这种形式的老化阻止饥饿的发生。
五、实验总结
这次实验主要实现了FCFS进程调度算法和RR进程调度算法,本实验设计了两版代码,最开始设计FCFS时,想的较为简单,将全部进程都排序加入了队列,然后依次出队列即完成了调度,这思路导致了我之后设计RR算法时也采用了这种策略,但这样使得一旦有一个进程被中断后要加入包含全部进程的队列,需要和队列里的所有进程比较到达时间,而我最开始采取的解决方法是全部出队,排序后再次入队,虽然这样是可以的,但是却不符合模拟的初衷。
后来在仔细看流程图后,发现自己第一版的代码里没有设计阻塞进程的函数,而且原来的设计很难加进去这个功能,所以干脆重新写了一版RR调度算法的代码。按照实际调度时的情况,我建立了就绪队列、阻塞队列、当前运行进程三个队列,首先将全部进程加入阻塞队列(未到到达时间的进程默认为阻塞),然后根据时间加入就绪队列,当前进程为空时,从就绪队列取元素。同时设计了一个随机阻塞函数 ,来完成进程的阻塞,被阻塞进程再次加入阻塞队列。
而之后我又根据这一版的RR调度算法更改了FCFS的代码,只需要去掉RR轮转部分的代码就可以变成FCFS了,这与RR算法在时间片无限长时等价于FCFS算法的规律也是相同的(关于这个结果上方的运行结果中也有比较),虽然FCFS代码比第一版复杂了很多,但结构和逻辑上更为完善了。
从这两个实验,让我更加理解了FCFS和轮转法调度的规则,对于进程在调度时就绪队列阻塞队列等的变化也有了更深刻的理解。在实验编写过程中,也遇到许多错误,但后来总结起来看,很多都是没有弄清楚进程入队出队的关系导致的,理清楚就绪和运行队列的具体关系后,按次序将进程加入队列,再执行,逐个判断进程结束的条件,弄清楚这些,代码也就顺利完成了。
总的来说,这次实验收获也很大。