目录
经过上一节的介绍,相信你一定已经了解进程调度最为简单常见的三种算法,这一节的实验需要你阅读并理解所给源代码,然后补全代码,编译运行它们,体会先来先服务算法、短作业优先算法和轮转调度算法的实现过程。源程序均放在/headless/Desktop/os/project2/目录下,三个文件分别是fcfshard.c、sjfhard.c和rrhard.c。现在先进入该目录。
我们已经知道进程控制块描述了进程十分重要的状态信息,在这个结构体中,各成员的具体含义见代码中的注释,需要说明的是,最后一个成员struct pcb *link指向的是在某个进程队列中该进程的下一个进程。
一.补全代码及分析
一、先来先服务算法
浏览代码,你可以看到我们定义了geti()、fcfs()、input()、disp(PCB *pr)、check()、destroy()、running()和main()函数,各个函数的功能后面进行介绍。
现在需要你补全fcfs()函数,实则为将新进入进程放入就绪队列队尾。
补全的fcfs()为
void fcfs()
{
if(!ready ){
ready = p;
pfend = p;
}
else{
pfend->link = p;
pfend = p;
}
}
一旦正在运行进程执行完毕,也就是该进程的PCB所描述的rtime信息和ntime信息想当时,意味着该进程已经执行完毕,这是我们需要销毁进程。从我们的模拟实验代码中可以看出只需要释放掉该进程的空间即可。想必你已经体会到一个进程从创建到消亡的生命周期了,现在实现fcfs()函数并把代码编译运行起来吧。
1 gcc fcfshard.c -o fcfshard 2 ./fcfshard
函数功能分析
- 功能目的
- 函数
fcfs
(先来先服务)似乎是用于处理进程的调度。其目的是将一个进程(这里用p
表示)添加到一个队列(可能是就绪队列,这里用ready
和pfend
相关操作来表示队列的操作)中。
- 函数
- 操作流程
- 首先检查就绪队列是否为空(
if(!ready)
)。- 如果为空,将传入的进程
p
作为队列的第一个元素,即ready = p
且pfend = p
。 - 如果队列不为空,将进程
p
添加到队列的末尾,先通过pfend->link = p
将p
连接到队列末尾,然后更新队列末尾指针pfend = p
。
- 如果为空,将传入的进程
- 首先检查就绪队列是否为空(
二、代码结构剖析
- 队列空判断逻辑
- 通过
if(!ready)
简单地判断队列是否为空,这是一种基本的队列操作中的边界条件判断方式。
- 通过
- 队列操作逻辑
- 根据队列是否为空进行不同的操作。如果为空,直接初始化队列;如果不为空,按照先来先服务的原则将进程添加到队列末尾,这种操作符合先来先服务算法的基本思想,即新到达的进程排在队列的末尾等待执行。
二、短作业优先算法
在这个文件中我们同样定义了进程控制块pcb数据结构,不再做累赘叙述。你可以看到文件中SJF()函数需要由你来实现,由于running()函数同样每次都执行ready指针指向的进程,你需要做的是将不断创建的进程按照其运行时间正确插入已按运行时间由小到大排列的队列中。请补全下面的代码并编译运行。
补全的SJF()为
void SJF()
{
if (!ready)
{
ready = p;
pfend = p;
}
else
{
PCB *tp = ready, *tempp;
if (ready->oncerun == 1)
tp = tp->link;
while (tp!= NOTHING && p->ntime >= tp->ntime)
{
tempp = tp;
tp = tp->link;
}
if (tp == ready)
{
if (ready->oncerun == 0)
{
p->link = ready;
ready = p;
}
else
{
p->link = ready->link;
ready->link = p;
}
}
else if (tp == NOTHING)
{
pfend->link = p;
pfend = p;
}
else
{
p->link = tp;
tempp->link = p;
}
}
}
在完成SJF()函数后编译并运行,验证你的函数是否编写正确。
1 gcc sjfhard.c -o sjfhard 2 ./sjfhard
功能概述
- 总体目标
- 此
SJF
(最短作业优先)函数旨在将一个进程(由p
表示)按照最短作业优先的原则插入到一个由ready
和pfend
相关操作表示的队列结构中。
- 此
- 具体流程
- 首先检查队列是否为空(
if (!ready)
)。- 若为空,直接将
p
设为队列的头(ready = p
)且尾(pfend = p
)。 - 若队列不为空:
- 初始化两个指针
tp
(指向ready
)和tempp
用于遍历和临时存储。 - 如果
ready
进程已运行过一次(ready->oncerun == 1
),则tp
指向下一进程(tp = tp->link
)。 - 通过
while
循环找到合适的插入位置,即当tp
不为空(tp!= NOTHING
)且p
的运行时间(p->ntime
)大于等于tp
的运行时间(tp->ntime
)时,移动tempp
和tp
指针。 - 根据
tp
的位置分情况插入p
:- 若
tp
为队列头部(tp == ready
):- 若
ready
未运行过(ready->oncerun == 0
),将p
设为新头部(p->link = ready; ready = p
)。 - 若
ready
已运行过,将p
插入ready
之后(p->link = ready->link; ready->link = p
)。
- 若
- 若
tp
为队列尾部(tp == NOTHING
):- 将
p
添加到队列尾部(pfend->link = p; pfend = p
)。
- 将
- 若
tp
在队列中间:- 将
p
插入tempp
和tp
之间(p->link = tp; tempp->link = p
)。
- 将
- 若
- 初始化两个指针
- 若为空,直接将
- 首先检查队列是否为空(
代码结构分析
- 队列空判断部分
if (!ready)
是对队列是否为空的简单有效判断,这是处理队列操作的常见起始步骤。
- 非空队列处理部分
- 定义指针
tp
和tempp
进行遍历和临时存储,通过if (ready->oncerun == 1)
对特殊情况(ready
已运行过)进行处理,使tp
指针移动到合适位置。 while
循环是核心部分,用于确定p
的插入位置,根据比较p->ntime
和tp->ntime
来移动指针。- 最后的
if - else if - else
结构根据tp
的不同位置(头部、尾部、中间)准确地将p
插入到队列中。
- 定义指针
三、轮转调度算法
继续浏览代码,你可以看到insert()和rr()函数是需要你来实现的。前者函数只需要简单的将新创建的进程插入就绪队列,而后者需要你在每次执行running()前将需要执行的正确的进程放在队首,时间片用完的进程放在队尾。现在请根据轮转调度算法的原理补全rr()函数。
补全的rr()为
void rr()
{
if (!ready->link)
return;
PCB *temp = ready;
ready = ready->link;
pfend->link= temp;
temp->link = NOTHING;
pfend = temp;
}
然后编译运行rrhard.c,验证你的函数是否正确。
1 gcc rrhard.c -o rrhard 2 ./rrhard
函数功能分析
- 功能目的
- 此
rr
函数(可能是轮询调度算法相关函数)旨在对一个由ready
和pfend
表示的进程队列进行操作,实现类似轮询调度的功能,即将队列头部的进程移到队列尾部。
- 此
- 操作流程
- 首先检查队列中除头部外是否还有其他进程(
if (!ready->link)
)。- 如果没有(即队列只有一个元素),则直接返回,不进行任何操作。
- 如果有:
- 先将
temp
指针指向当前的队列头部(PCB *temp = ready;
)。 - 然后更新队列头部为原头部的下一个进程(
ready = ready->link;
)。 - 将原队列头部(
temp
指向的进程)添加到队列尾部,先通过pfend->link = temp
将temp
连接到队列末尾的下一个位置,再将temp
的下一个指针设为NOTHING
(temp->link = NOTHING;
),最后更新队列末尾指针为temp
(pfend = temp;
)。
- 先将
- 首先检查队列中除头部外是否还有其他进程(
代码结构剖析
- 边界条件检查
if (!ready->link)
作为边界条件检查,用于判断队列是否只有一个元素,这是避免在不必要的情况下进行操作的有效方式。
- 队列操作逻辑
- 当队列不止一个元素时,通过一系列指针操作实现将队列头部元素移到队列尾部的功能。先保存头部元素指针,更新头部指针,然后将保存的头部元素添加到队列尾部,这种操作符合轮询调度算法中轮流处理进程的基本思想。
二.总结
功能总结
- 主要功能
- 函数
rr
可能用于轮询调度相关操作,目的是在队列至少有两个元素时,将队列头部进程移到队列尾部。
- 函数
- 操作流程
- 先检查队列除头部外是否有元素,若没有则直接返回。若有,先保存头部进程指针,更新头部指针,再将保存的头部进程移到队列尾部。
代码结构总结
- 边界判断
- 通过
if (!ready->link)
判断队列是否只有一个元素,这是关键的边界条件判断。
- 通过
- 操作逻辑
- 在队列不止一个元素时,通过指针操作实现头部元素移到队列尾部的功能,操作步骤之间相互关联,符合轮询调度思想。