*【教学目标】*
*关键词:**队列,分时操作系统,时间片轮转调度算法*
****知识和能力目标:****熟练掌握队列的存储及其操作方法,理解队列适用的问题场景。
*价值目标:*
(1) ****职业素养(工程与社会):****计算机系统的设计与实施,是一个系统工程,或者说,它就是一个工程问题。工程师在解决问题时,总是需要在资源使用和资源保护方面寻找平衡点。时间片轮转调度算法很有意思,你可能会发现,虽然时间片的大小、进程的执行时间等因素间存在强相关性,但为了获得良好的带权周转时间,却很难有一个标准答案,需要我们权衡多种因素,按需进行设计和选择。作为未来的计算机工程师,你在处理实际问题时,应该对此有充分的认知和心理准备。
(2) ****人文素质:****你认为作为计算机工程师,我们工作中的美学体现在哪些方面?
*【问题描述】*
操作系统(Operating System,OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石。操作系统需要管理内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络、管理文件系统等,操作系统也提供一个让用户与系统交互的界面。
分时操作系统(Time-sharing operating System)是指在一台主机上连接了多个带有显示器和键盘等设备的终端,允许多个用户同时通过自己的终端以交互方式共享使用计算机的系统。在分时操作系统管理下,终端用户通过各自的终端向系统发出命令请求,系统采用时间 片轮转法的方式,来接收每个用户的命令并处理服务请求,同时通过交互方式在终端上向用户显示处理的结果。即系统将CPU的执行时间分割为一个个时间段(称为时间片),然后轮流分配给每个用户使用,每个用户程序每次只能在CPU上运行一个时间片。由于时间间隔很短,每个用户感觉自己独占了计算机。Unix和Linux是典型的分时操作系统。
*本问题的任务是:编写程序,模拟实现分时操作系统的时间片轮转调度算法。*
*【基本思路】*
*时间片轮转(**Round Robin**,**RR**)调度算法*:为分时操作系统设计。在早期的时间片轮转法中,系统将所有就绪进程按先来先服务的原则排成一个队列。每次调度时,把CPU分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断,调度程序据此信号来停止该进程,并将它送往就绪队列的末尾;然后再把处理机分配给就绪队列中新的队首进程,也让它执行一个时间片。如此反复,就可以保证就绪队列中的所有进程在一个给定的时间段内均能获得若干时间片的处理机执行时间。也即,操作系统能在给定时间段内响应所有用户的请求。
*RR**算法思路:*
Step1:将需要运行的进程依次放入就绪队列Q
Step2:如果Q为空,则return ;否则,从Q出队一个进程,让其运行一个时间片的时间。如进程在一个时间片未执行完,则其去Q末尾重新排队,等待下一次调度。
Step3:转Step2
【例】进程****A**、*B**、*C**、*D**需运行的时间分别为20ms、10 ms、15 ms、5 ms,均在0时刻到达。到达次序为***A**、*B**、*C**、*D**。如果时间片分别为1 ms和5ms*,计算各进程的带权周转时间和平均带权周转时间。分析:根据****RR****算法,图4.1表示了进程执行情况。
进程****i****的带权周转时间: *其中*为周转时间,为实际运行时间。
所有进程的平均带权周转时间****:****
**RR**算法的性能指标如下(以下除带权周转时间外,其余时间单位均为ms):
到达时间 | 进程名 | 到达时间 | 运行时间 | 开始时间 | 完成时间 | 周转时间 | 带权周转时间 |
---|---|---|---|---|---|---|---|
时间片=5 | A | 0 | 20 | 0 | 50 | 50 | 2.5 |
B | 0 | 10 | 5 | 30 | 30 | 3.0 | |
C | 0 | 15 | 10 | 45 | 45 | 3.0 | |
D | 0 | 5 | 15 | 20 | 20 | 4.0 | |
平均周转时间=36.25 平均带权周转时间=3.125 | |||||||
时间片=1 | A | 0 | 20 | 0 | 50 | 50 | 2.5 |
B | 0 | 10 | 1 | 34 | 34 | 3.4 | |
C | 0 | 15 | 2 | 45 | 45 | 3.0 | |
D | 0 | 5 | 3 | 20 | 20 | 4.0 | |
平均周转时间=37.25 平均带权周转时间=3.225 |
*【实验结果】*
\1. 算法概述
①将需要执行的进程按照到达时间的顺序放入就绪队列(Ready Queue)中。
②设置一个固定长度的时间片(Time Slice),即每个进程被允许执行的时间量。
③从就绪队列中取出队首的进程,让其执行一个时间片的时间。
④如果进程在一个时间片内执行完毕,则标记为已完成,计算其周转时间、带权周转时间,并将其从就绪队列中移除。
⑤如果进程在一个时间片内未执行完毕,将其放回就绪队列的末尾,等待下一次调度。
重复步骤3到步骤5,直到就绪队列为空。2. 实验重点或难点
\3. 实验结果
\4. 实验方案分析
4.1本实验设计的优点:
①简单易懂:实验设计简单明了,使用轮转调度算法对进程进行调度,计算带权周转时间和平均带权周转时间。
②具有可视化效果:通过展示进程的到达时间、运行时间、开始时间、完成时间、周转时间和带权周转时间等信息,可以清晰地观察到进程的调度过程和结果。
③具备实用性:轮转调度算法是一种常用的调度算法,适用于多任务环境和交互式系统。通过本实验,可以帮助学习者理解轮转调度算法的工作原理和性能指标。
4.2本实验设计的不足和可能存在的问题:
①时间片大小的选择:时间片大小的选择会影响进程的调度顺序和带权周转时间。在实际应用中,需要根据具体情况和需求选择合适的时间片大小。本实验设计中仅给出了两种时间片大小,可能无法全面展示不同时间片大小对调度结果的影响。
②实验数据的单一性:本实验中仅给出了一个示例数据集,进程的到达时间和运行时间都是固定的。在实际场景中,进程的到达时间和运行时间可能是随机的或者根据实际情况变化的,因此可能需要更多的数据集进行实验和分析。
③未考虑其他因素:本实验设计仅关注于轮转调度算法和带权周转时间,未考虑其他因素如优先级调度、I/O等操作对进程调度和性能的影响。在实际系统中,这些因素会对进程的调度和性能产生重要影响。
4.3时空开销问题分析:
①时间开销:轮转调度算法存在上下文切换的开销,当时间片较小或进程数量较多时,上下文切换的次数增多,会占用一定的CPU时间。较大的时间片可以减少上下文切换的次数,从而降低时间开销。
②空间开销:在本实验中,使用了一个就绪队列(Ready Queue)来存储就绪状态的进程。随着进程数量的增加,就绪队列的大小也会增加,占用一定的内存空间。然而,就绪队列的大小通常是可控的,并且相比于进程自身的内存消耗,就绪队列的空间开销通常可以忽略不计。
\5. 附件:源程序代码
#include <stdio.h>
struct Process {
char name;
int arrival_time;
int run_time;
int start_time;
int finish_time;
int turnaround_time;
int waiting_time;
float weighted_turnaround_time;
};
void round_robin(struct Process processes[], int n, int time_slice) {
struct Process queue[100];
int front = 0, rear = 0;
int current_time = 0;
for (int i = 0; i < n; i++) {
queue[rear++] = processes[i];
}
while (front != rear) {
struct Process current_process = queue[front++];
if (current_process.run_time <= time_slice) {
current_process.start_time = current_time;
current_time += current_process.run_time;
current_process.finish_time = current_time;
current_process.turnaround_time = current_process.finish_time - current_process.arrival_time;
current_process.waiting_time = current_process.turnaround_time - current_process.run_time;
current_process.weighted_turnaround_time = (float)current_process.turnaround_time / current_process.run_time;
} else {
current_process.start_time = current_time;
current_time += time_slice;
current_process.run_time -= time_slice;
queue[rear++] = current_process;
}
}
}
int main() {
struct Process processes[] = {
{‘A’, 0, 20, 0, 50, 50, 0, 2.5},
{‘B’, 0, 10, 5, 30, 30, 0, 3.0},
{‘C’, 0, 15, 10, 45, 45, 0, 3.0},
{‘D’, 0, 5, 15, 20, 20, 0, 4.0}
};
struct Process processes2[] = {
{‘A’, 0, 20, 0, 50, 50, 0, 2.5},
{‘B’, 0, 10, 1, 34, 34, 0, 3.4},
{‘C’, 0, 15, 2, 45, 45, 0, 3.0},
{‘D’, 0, 5, 3, 20, 20, 0, 4.0}
};
int n = sizeof(processes) / sizeof(struct Process);
int time_slice1 = 5;
int time_slice2 = 1;
// 执行轮转调度算法**
** round_robin(processes, n, time_slice1);
// 输出结果**
** printf(“时间片=%d\n”, time_slice1);
printf(“进程名\t到达时间\t运行时间\t开始时间\t完成时间\t周转时间\t带权周转时间\n”);
for (int i = 0; i < n; i++) {
printf(“%c\t%d\t%d\t%d\t%d\t%d\t%.1f\n”, processes[i].name, processes[i].arrival_time, processes[i].run_time,
processes[i].start_time, processes[i].finish_time, processes[i].turnaround_time,
processes[i].weighted_turnaround_time);
}
// 计算平均带权周转时间**
** float avg_weighted_turnaround_time1 = 0;
for (int i = 0; i < n; i++) {
avg_weighted_turnaround_time1 += processes[i].weighted_turnaround_time;
}
avg_weighted_turnaround_time1 /= n;
printf(“平均带权周转时间=%.3f\n”, avg_weighted_turnaround_time1);
// 执行轮转调度算法**
** round_robin(processes2, n, time_slice2);
// 输出结果**
** printf(“\n时间片=%d\n”, time_slice2);
printf(“进程名\t到达时间\t运行时间\t开始时间\t完成时间\t周转时间\t带权周转时间\n”);
for (int i = 0; i < n; i++) {
printf(“%c\t%d\t%d\t%d\t%d\t%d\t%.1f\n”, processes2[i].name, processes2[i].arrival_time, processes2[i].run_time,
processes2[i].start_time, processes2[i].finish_time, processes2[i].turnaround_time,
processes2[i].weighted_turnaround_time);
}
// 计算平均带权周转时间**
** float avg_weighted_turnaround_time2 = 0;
for (int i = 0; i < n; i++) {
avg_weighted_turnaround_time2 += processes2[i].weighted_turnaround_time;
}
avg_weighted_turnaround_time2 /= n;
printf(“平均带权周转时间=%.3f\n”, avg_weighted_turnaround_time2);
return 0;
}