06时间片轮转调度.docx
《06时间片轮转调度.docx》由会员分享,可在线阅读,更多相关《06时间片轮转调度.docx(16页珍藏版)》请在冰豆网上搜索。
![06时间片轮转调度.docx](https://file1.bdocx.com/fileroot1/2023-2/11/e6a6851a-c959-4443-ad46-63aef9f4770a/e6a6851a-c959-4443-ad46-63aef9f4770a1.gif)
06时间片轮转调度
《操作系统》实验报告
实验6时间片轮转调度
1.实验目的与要求
●调试EOS的线程调度程序,熟悉基于优先级的抢先式调度。
●为EOS添加时间片轮转调度,了解其它常用的调度算法。
2.实验原理
阅读本书第5章中的第节。
重点理解EOS当前使用的基于优先级的抢先式调度,调度程序执行的时机和流程,以及实现时间片轮转调度的细节。
3.实验内容
准备实验
按照下面的步骤准备实验:
1.启动OSLab。
2.新建一个EOSKernel项目。
阅读控制台命令“rr”相关的源代码
阅读ke/文件中第690行的ConsoleCmdRoundRobin函数,及该函数用到的第649行的ThreadFunction函数和第642行的THREAD_PARAMETER结构体,学习“rr”命令是如何测试时间片轮转调度的。
在阅读的过程中需要特别注意下面几点:
●在ConsoleCmdRoundRobin函数中使用ThreadFunction函数做为线程函数,新建了20个优先级为8的线程,做为测试时间片轮转调度用的线程。
●在新建的线程中,只有正在执行的线程才会在控制台的对应行(第0个线程对应第0行,第1个线程对应第1行)增加其计数,这样就可以很方便的观察到各个线程执行的情况。
●控制台对于新建的线程来说是一种临界资源,所以,新建的线程在向控制台输出时,必须使用“关中断”和“开中断”进行互斥(参见ThreadFunction函数的源代码)。
●由于控制台线程的优先级是24,高于新建线程的优先级8,所以只有在控制台线程进入“阻塞”状态后,新建的线程才能执行。
●新建的线程会一直运行,原因是在ThreadFunction函数中使用了死循环,所以只能在ConsoleCmdRoundRobin函数的最后调用TerminateThread函数来强制结束这些新建的线程。
按照下面的步骤执行控制台命令“rr”,查看其在没有时间片轮转调度时的执行效果:
1.按F7生成在本实验中创建的EOSKernel项目。
2.按F5启动调试。
3.待EOS启动完毕,在EOS控制台中输入命令“rr”后按回车。
命令开始执行后,观察其执行效果(如图1),会发现并没有体现“rr”命令相关源代码的设计意图。
通过之前对这些源代码的学习,20个新建的线程应该在控制台对应的行中轮流地显示它们的计数在增加,而现在只有第0个新建的线程在第0行显示其计数在增加,说明只有第0个新建的线程在运行,其它线程都没有运行。
造成上述现象的原因是:
所有20个新建线程的优先级都是8,而此时EOS只实现了基于优先级的抢先式调度,还没有实现时间片轮转调度,所以至始至终都只有第0个线程在运行,而其它具有相同优先级的线程都没有机会运行,只能处于“就绪”状态。
图1:
没有时间片轮转调度时“rr”命令的执行效果
调试线程调度程序
在为EOS添加时间片轮转调度之前,先调试一下EOS的线程调度程序PspSelectNextThread函数,学习就绪队列、就绪位图以及线程的优先级是如何在线程调度程序中协同工作的,从而加深对EOS已经实现的基于优先级的抢先式调度的理解。
3.3.1调试当前线程不被抢先的情况
正像图1中显示的,新建的第0个线程会一直运行,而不会被其它同优先级的新建线程或者低优先级的线程抢先。
按照下面的步骤调试这种情况在PspSelectNextThread函数中处理的过程。
1.结束之前的调试。
2.在ke/文件的ThreadFunction函数中,调用fprintf函数的代码行(第680行)添加一个断点。
3.按F5启动调试。
4.待EOS启动完毕,在EOS控制台中输入命令“rr”后按回车。
“rr”命令开始执行后,会在断点处中断。
5.查看ThreadFunction函数中变量pThreadParameter->Y的值应该为0,说明正在调试的是第0个新建的线程。
图2执行效果
6.激活虚拟机窗口,可以看到第0个新建的线程还没有在控制台中输出任何内容,原因是fprintf函数还没有执行。
7.激活OSLab窗口后按F5使第0个新建的线程继续执行,又会在断点处中断。
再次激活虚拟机窗口,可以看到第0个新建的线程已经在控制台中输出了第一轮循环的内容。
可以多按几次F5查看每轮循环输出的内容。
图3第一轮循环
通过之前的调试,可以观察到第0个新建的线程执行的情况。
按照下面的步骤调试,查看当有中断发生从而触发线程调度时,第0个新建的线程不会被抢先的情况。
1.在ps/文件的PspSelectNextThread函数中,调用BitScanReverse函数扫描就绪位图的代码行(第384行)添加一个断点。
2.按F5继续执行,当有定时计数器中断发生时(每10ms一次),就会在新添加的断点处中断。
3.在“调试”菜单的“窗口”中选择“监视”,激活“监视”窗口(此时按F1可以获得关于“监视”窗口的帮助)。
4.在“监视”窗口中添加表达式“/tPspReadyBitmap”,以二进制格式查看就绪位图的值。
此时就绪位图的值应该为1,表示优先级为8和0的两个就绪队列中存在就绪线程。
(注意,如果就绪位图的值不是1,就继续按F5,直到就绪位图变为此值)。
5.在“调试”菜单中选择“快速监视”,在“快速监视”对话框的“表达式”中输入表达式“*PspCurrentThread”后,点击“重新计算”按钮,可以查看当前正在执行的线程(即被中断的线程)的线程控制块中各个域的值。
其中优先级(Priority域)的值为8;状态(State域)的值为2(运行态);时间片(RemainderTicks域)的值为6;线程函数(StartAddr域)为ThreadFunction。
综合这些信息即可确定当前正在执行的线程就是新建的第0个线程。
关闭“快速监视”对话框。
图4快速“监视”窗口
6.在“监视”窗口中添加表达式“ListGetCount(&PspReadyListHeads[8])”,查看优先级为8的就
绪队列中就绪线程的数量,值为19。
说明除了正在执行的第0个新建的线程外,其余19个新建的线程都在优先级为8的就绪队列中。
ListGetCount函数在文件rtl/中定义。
7.按F10单步调试,BitScanReverse函数会从就绪位图中扫描最高优先级,并保存在变量HighestPriority中。
查看变量HighestPriority的值为8。
图5“监视”窗口
8.继续按F10单步调试,直到在PspSelectNextThread函数返前停止(第465行),注意观察线程调度执行的每一个步骤。
第0个新建的线程在执行线程调度时没有被抢先的原因可以归纳为两点:
(1)第0个新建的线程仍然处于“运行”状态。
(2)没有比其优先级更高的就绪线程。
3.3.2调试当前线程被抢先的情况
如果有比第0个新建的线程优先级更高的线程进入就绪状态,则第0个新建的线程就会被抢先。
按照下面的步骤调试这种情况在PspSelectNextThread函数中处理的过程(注意,接下来的调试要从本实验3.3.1调试的状态继续调试,所以不要结束之前的调试)。
1.选择“调试”菜单中的“删除所有断点”,删除之前添加的所有断点。
2.在ps/文件的PspSelectNextThread函数的第395行添加一个断点。
3.按F5继续执行,激活虚拟机窗口,可以看到第0个新建的线程正在执行。
4.在虚拟机窗口中按下一次空格键,EOS会在之前添加的断点处中断。
5.在“监视”窗口中查看就绪位图的值为0001,说明此时在优先级为24的就绪队列中存在就绪线程。
在“监视”窗口中添加表达式“ListGetCount(&PspReadyListHeads[24])”,其值为1,说明优先级为24的就绪队列中只有一个就绪线程。
扫描就绪位图后获得的最高优先级的值HighestPriority也就应该是24。
图6“监视”窗口就绪位图为0001
6.按F10单步调试一次,执行的语句会将当前正在执行的第0个新建的线程,放入优先级为8的就绪队列的队首。
“监视”窗口中显示的优先级为8的就绪队列中的线程数量就会增加1,变为20。
图7“监视”窗口线程数量
7.继续按F10单步调试,直到在第444行中断执行,注意观察线程调度执行的每一个步骤。
此时,正在执行的第0个新建的线程已经进入了“就绪”状态,让出了CPU。
线程调度程序接下来的工作就是选择优先级最高的非空就绪队列的队首线程作为当前运行线程,也就是让优先级为24的线程在CPU上执行。
8.按F10单步调试一次,当前线程PspCurrentThread指向了优先级为24的线程。
可以在“快速监视”窗口中查看表达式“*PspCurrentThread”的值,注意线程控制块中StartAddr域的值为IopConsoleDispatchThread函数(在文件io/中定义),说明这个优先级为24的线程是控制台派遣线程。
图8快速“监视”窗口
9.继续按F10单步调试,直到在PspSelectNextThread函数返回前(第465行)中断执行,注意观察线程调度执行的每一个步骤。
此时,优先级为24的线程已经进入了“运行”状态,在中断返回后,就可以开始执行了。
在“监视”窗口中,就绪位图的值变为1,优先级为24的就绪队列中线程的数量变为0,就绪位图和就绪队列都是在刚刚被调用过的PspUnreadyThread函数(在文件ps/中定义)内更新的。
图9“监视”窗口
10.删除所有断点后结束调试。
为EOS添加时间片轮转调度
3.4.1要求
修改ps/文件中的PspRoundRobin函数(第337行),在其中实现时间片轮转调度算法。
代码如下:
VOID
PspRoundRobin(
VOID
)
/*++
功能描述:
时间片轮转调度函数,被定时计数器中断服务程序KiIsrTimer调用。
参数:
无。
返回值:
无。
--*/
{
3.4.2代码修改完毕后,按F7生成EOS内核项目。
2.按F5启动调试。
3.在EOS控制台中输入命令“rr”后按回车。
应能看到20个线程轮流执行的效果,如图10。
图10:
进行时间片轮转调度时“rr”命令的执行效果
3.4.3提示
●PspRoundRobin函数具体的流程可以参考图5-11。
●在PspRoundRobin函数中,全局变量PspCurrentThread指向的线程控制块就是被定时计数器中断的线程的线程控制块,通过对PspCurrentThread指向的线程控制块的各个域进行修改,就可以改变被中断线程的各种属性。
全局变量PspCurrentThread的定义参见ps/的第45行。
线程控制块结构体的定义参见ps/的第58行。
●被中断线程的状态有可能不是“运行”状态,而是“阻塞”状态。
所以,在进行时间片轮转调度前,要先判断一下被中断线程是否仍处于“运行”状态。
只有当被中断线程仍处于“运行”状态时,才需要进行时间片轮转调度。
在PspRoundRobin函数中的第一行代码可以如下(线程状态的定义可以参见ps/的第93行):
if(NULL!
=PspCurrentThread&&Running==PspCurrentThread->State){在OSLab的“项目管理器”窗口中找到ps/文件,双击打开此文件。
2.将ps/第104行定义的TICKS_OF_TIME_SLICE的值修改为1。
3.在EOS控制台中输入命令“rr”后按回车。
观察执行的效果。
4.按F7生成EOS内核项目。
5.按F5启动调试。
图11线程示意
还可以按照上面的步骤为TICKS_OF_TIME_SLICE取一些其它的极端值,例如20或100等,分别观察“rr”命令执行的效果。
通过分析造成执行效果不同的原因,理解时间片的大小对时间片轮转调度造成的影响。
(规律:
将TICKS_OF_TIME_SLICE取100时依次出现再循环依次出现)
图12时间片的大小对时间片轮转调度造成的影响。
4.思考与练习
1.结合线程调度执行的时机,说明在ThreadFunction函数中,为什么可以使用“关中断”和“开中断”的方法来保护控制台这种临界资源。
一般情况下,应该使用互斥信号量(MUTEX)来保护临界资源,但是在ThreadFunction函数中却不能使用互斥信号量,而只能使用“关中断”和“开中断”的方法,结合线程调度的对象说明这样做的原因。
答:
关中断后CPU就不会响应任何由外部设备发出的硬中断(包括定时计数器中断和键盘中断等)了,也就不会发生线程调度了,从而保证各个线程可以互斥的访问控制台。
这里绝对不能使用互斥信号量(mutex)保护临界资源的原因:
如果使用互斥信号量,则那些由于访问临界区而被阻塞的线程,就会被放入互斥信号量的等待队列,就不会在相应优先级的就绪列中了,而时间轮转调度算法是对就绪队列的线程进行轮转调度,而不是对这些被阻塞的线程进行调度,也就无法进行实验了。
使用“关中断”和“开中断”进行同步就不会改变线程的状态,可以保证那些没有获得处理器的线程都在处于就绪队列中。
2.时间片轮转调度发现被中断线程的时间片用完后,而且在就绪队列中没有与被中断线程优先级相同的就绪线程时,为什么不需要将被中断线程转入“就绪”状态?
如果此时将被中断线程转入了“就绪”状态又会怎么样?
可以结合PspRoundRobin函数和PspSelectNextThread函数的流程进行思考,并使用抢先和不抢先两种情况进行说明。
答:
因为由PspRoundRobin函数和PspSelectNextThread函数的流程可知,当时间片轮转调度发现被中断线程的时间片用完后,而且在就绪队列中没有与被中断线程优先级相同的就绪线程时,PspRoundRobin函数会直接结束,所以不需要将被中断线程转入“就绪”状态。
如果此时将被中断线程转入了“就绪”状态,那么比该中断线程更高的就绪进程就无法运行。
3.在EOS只实现了基于优先级的抢先式调度时,同优先级的线程只能有一个被执行。
当实现了时间片轮转调度算法后,同优先级的线程就能够轮流执行从而获得均等的执行机会。
但是,如果有高优先级的线程一直占用CPU,低优先级的线程就永远不会被执行。
尝试修改ke/文件中的ConsoleCmdRoundRobin函数来演示这种情况(例如在20个优先级为8的线程执行时,创建一个优先级为9的线程)。
设计一种调度算法来解决此问题,让低优先级的线程也能获得被执行的机会。
答:
解决该问题的最简单的方法是实现动态优先级算法。
动态优先级是指在创建进程时所赋予的优先级,可以随线程的推进而改变,以便获得良好的调度性能。
例如,可用规定,在就绪队列中的线程,随着其等待时间的增长,其优先级以速率X增加,并且正在执行的线程,其优先级以速率y下降。
这样,在各个线程具有不同优先级的情况下,对于优先级低的线程,在等待足够的时间后,其优先级便可能升为最高,从而获得被执行的机会。
此时,在基于优先级的抢占式调度算法、时间片轮转调度算法和动态优先级算法的共同作用下,可防止一个高优先级的长作业长期的垄断处理器。
4.EOS内核时间片大小取60ms(和Windows操作系统完全相同),在线程比较多时,就可以观察出线程轮流执行的情况(因为此时一次轮转需要60ms,20个线程轮流执行一次需要60×20=1200ms,也就是需要1秒多的时间,所以EOS的控制台上可以清楚地观察到线程轮流执行的情况)。
但是在Windows、Linux等操作系统启动后,正常情况下都有上百个线程在并发执行,为什么觉察不到它们被轮流执行,并且每个程序都运行的很顺利呢?
答:
在Windows、linux等操作系统中,虽然都提供了时间片轮转调度算法却很少真正被派上用场,下面解释原因,在Windows任务管理器中,即使系统中已经运行了数百个线程,
但CPU的利用率仍然很低,甚至为0.因为这些线程在大部分时间都处于阻塞状态,阻塞的原因是各种各样的,最主要的原因是等待I/O完成或者等待命令消息的到达。
例如,在编辑Word文档时,每敲击一次键盘,Word就会立即作出反应,并且文档中插入字符。
此时会感觉Word运行的非常流畅。
事实上,并非如此,Word主线程大部分时间都处于阻塞等待状态,等待用户敲击键盘。
在用户没有敲击键盘或没有使用鼠标点击时,Word主线程处于阻塞状态,它将让出处理器给其它需要的线程。
当用户敲击一个按键后,Word主线程将会立刻被操作系统唤醒,此时Word开始处理请求。
Word在处理输入请求时所用的CPU时间是非常短的(因为CPU非常快),是微秒级的,远远低于时间片轮转调度的时间片大小(Windows下是60毫秒),处理完毕后Word又立刻进入阻塞状态,等待用户下一次敲击键盘。
或者拿音乐播放器来分析,表面上感觉播放器在不停地播放音乐,但是CPU的利用率仍然会很低。
这是由于播放器将一段声音编码交给声卡,由声卡来播放,在声卡播放完这段声音之前,播放器都是处于阻塞等待状态的。
当声卡播放完片段后,播放器将被唤醒,然后它将下一个声音片段交给声卡继续播放。
掌握了上面的知识后,就可以很容易解释为什么这么多线程同时在运行而一点都感觉不到轮替现象。