操作系统实验报告.docx
《操作系统实验报告.docx》由会员分享,可在线阅读,更多相关《操作系统实验报告.docx(17页珍藏版)》请在冰豆网上搜索。
操作系统实验报告
HUNANUNIVERSITY
操作系统
实验报告
题目:
调度器
学生姓名:
完成
学生学号:
201308010314
专业班级:
计科1303
同组成员:
上课老师:
肖德贵
目录
一、内容2
二、目的2
三、实验设计思想和流程2
四、主要数据结构及符号说明2
五、程序初值及运行结果2
六、实验体会和思考题2
附录(源代码及注释)2
一、内容
实验五完成了用户进程的管理,可在用户态运行多个进程。
但到目前为止,采用的调度策略是很简单的FIFO调度策略。
本次
实验,主要是熟悉ucore的系统调度器框架,以及基于此框架的Round-Robin(RR)调度算法。
然后参考RR调度算法的实
现,完成StrideScheduling调度算法。
二、目的
理解操作系统的调度管理机制
熟悉ucore的系统调度器框架,以及缺省的Round-Robin调度算法
基于调度器框架实现一个(StrideScheduling)调度算法来替换缺省的调度算法
三、实验设计思想和流程
实行一个进程调度策略,到底需要实现哪些基本功能对应的数据结构?
首先考虑到一个无论哪种调度算法都需要选择一个就
绪进程来占用CPU运行。
为此我们可把就绪进程组织起来,可用队列(双向链表)、二叉树、红黑树、数组…等不同的组织方式。
在操作方面,如果需要选择一个就绪进程,就可以从基于某种组织方式的就绪进程集合中选择出一个进程执行。
需要注意,这里“选择”和“出”是两个操作,选择是在集合中挑选一个“合适”的进程,“出”意味着离开就绪进程集合。
另外考虑到一个处于运行态的进程还会由于某种原因(比如时间片用完了)回到就绪态而不能继续占用CPU执行,这就会重新进入到就绪进程集合中。
这两种情况就形成了调度器相关的三个基本操作:
在就绪进程集合中选择、进入就绪进程集合和离开就绪进程集合。
这三个操作属于调度器的基本操作。
在进程的执行过程中,就绪进程的等待时间和执行进程的执行时间影响调度选择的重要因素,这两个因素随着时间的流逝和各种事件的发生在不停地变化,比如处于就绪态的进程等待调度的时间在增长,处于运行态的进程所消耗的时间片在减少
等。
这些进程状态变化的情况需要及时让进程调度器知道,便于选择更合适的进程执行。
所以这种进程变化的情况就形成了调度器相关的一个变化感知操作:
timer时间事件感知操作。
这样在进程运行或等待的过程中,调度器可以调整进程控制块中与进程调度相关的属性值(比如消耗的时间片、进程优先级等)并可能导致对进程组织形式的调整(比如以时间片大小的顺序来重排双向链表等),并最终可能导致调选择新的进程占用CPU运行。
这个操作属于调度器的进程调度属性调整操作。
考察round-robin调度器,在假设所有进程都充分使用了其拥有的CPU时间资源的情况下,所有进程得到的CPU时间应该
是相等的。
但是有时候我们希望调度器能够更智能地为每个进程分配合理的CPU资源。
假设我们为不同的进程分配不同的优先级,则我们有可能希望每个进程得到的时间资源与他们的优先级成正比关系。
Stride调度是基于这种想法的一个较为典型和简单的算法。
除了简单易于实现以外,它还有如下的特点:
可控性:
如我们之前所希望的,可以证明StrideScheduling对进程的调度次数正比于其优先级。
确定性:
在不考虑计时器事件的情况下,整个调度机制都是可预知和重现的。
该算法的基本思想可以考虑如下:
1.为每个runnable的进程设置一个当前状态stride,表示该进程当前的调度权。
另外定义其对应的pass值,表示对应进
程在调度后,stride需要进行的累加值。
2.每次需要调度时,从当前runnable态的进程中选择stride最小的进程调度。
3.对于获得调度的进程P,将对应的stride加上其对应的步长pass(只与进程的优先权有关系)。
4.在一段固定的时间之后,回到2.步骤,重新调度当前stride最小的进程。
可以证明,如果令P.pass=BigStride/P.priority其中P.priority表示进程的优先权(大于1),而BigStride表示一
个预先定义的大常数,则该调度方案为每个进程分配的时间将与其优先级成正比。
证明过程我们在这里略去,有兴
趣的同学可以在网上查找相关资料。
将该调度器应用到ucore的调度器框架中来,则需要将调度器接口实现如下:
init:
–初始化调度器类的信息(如果有的话)。
–初始化当前的运行队列为一个空的容器结构。
(比如和RR调度算法一样,初始化为一个有序列表)
enqueue
–初始化刚进入运行队列的进程proc的stride属性。
–将proc插入放入运行队列中去(注意:
这里并不要求放置在队列头部)。
dequeue
–从运行队列中删除相应的元素。
picknext
–扫描整个运行队列,返回其中stride值最小的对应进程。
–更新对应进程的stride值,即pass=BIG_STRIDE/P->priority;P->stride+=pass。
proctick:
–检测当前进程是否已用完分配的时间片。
如果时间片用完,应该正确设置进程结构的相关标记来引起进程切换。
–一个process最多可以连续运行rq.max_time_slice个时间片。
在具体实现时,有一个需要注意的地方:
stride属性的溢出问题,在之前的实现里面我们并没有考虑stride的数值范围,而这个值在理论上是不断增加的,在stride溢出以后,基于stride的比较可能会出现错误。
比如假设当前存在两个进程A和B,stride属性采用16位无符号整数进行存储。
当前队列中元素如下(假设当前运行的进程已经被重新放置进运行队列中):
使用优先队列实现StrideScheduling
可以看到由于溢出的出现,进程间stride的理论比较和实际比较结果出现了偏差。
我们首先在理论上分析这个问题:
令
PASS_MAX为当前所有进程里最大的步进值。
则我们可以证明如下结论:
对每次Stride调度器的调度步骤中,有其最大的步
进值STRIDE_MAX和最小的步进值STRIDE_MIN之差:
STRIDE_MAX–STRIDE_MIN<=PASS_MAX
提问1:
如何证明该结论?
有了该结论,在加上之前对优先级有Priority>1限制,我们有STRIDE_MAX–STRIDE_MIN<=BIG_STRIDE,于是我们只要
将BigStride取在某个范围之内,即可保证对于任意两个Stride之差都会在机器整数表示的范围之内。
而我们可以通过其与0
的比较结构,来得到两个Stride的大小关系。
在上例中,虽然在直接的数值表示上98<65535,但是98-65535的结果用带
符号的16位整数表示的结果为99,与理论值之差相等。
所以在这个意义下98>65535。
基于这种特殊考虑的比较方法,即便Stride有可能溢出,我们仍能够得到理论上的当前最小Stride,并做出正确的调度决定。
提问2:
在ucore中,目前Stride是采用无符号的32位整数表示。
则BigStride应该取多少,才能保证比较的正确性?
在上述的实现描述中,对于每一次pick_next函数,我们都需要完整地扫描来获得当前最小的stride及其进程。
这在进程非常
多的时候是非常耗时和低效的,有兴趣的同学可以在实现了基于列表扫描的Stride调度器之后比较一下priority程序在Round-Robin及Stride调度器下各自的运行时间。
考虑到其调度选择于优先队列的抽象逻辑一致,我们考虑使用优化的优先队列数据
结构实现该调度。
优先队列是这样一种数据结构:
使用者可以快速的插入和删除队列中的元素,并且在预先指定的顺序下快速取得当前在队列中的最小(或者最大)值及其对应元素。
可以看到,这样的数据结构非常符合Stride调度器的实现。
本次实验提供了libs/skew_heap.h作为优先队列的一个实现,该实现定义相关的结构和接口,其中主要包括:
1//优先队列节点的结构
2typedefstructskew_heap_entryskew_heap_entry_t;
3//初始化一个队列节点
4voidskew_heap_init(skew_heap_entry_t*a);
5//将节点b插入至以节点a为队列头的队列中去,返回插入后的队列
6skew_heap_entry_t*skew_heap_insert(skew_heap_entry_t*a,
7skew_heap_entry_t*b,
8compare_fcomp);
9//将节点b插入从以节点a为队列头的队列中去,返回删除后的队列
10skew_heap_entry_t*skew_heap_remove(skew_heap_entry_t*a,
11skew_heap_entry_t*b,
12compare_fcomp);
其中优先队列的顺序是由比较函数comp决定的,sched_stride.c中提供了proc_stride_comp_f比较器用来比较两个stride的大小,你可以直接使用它。
当使用优先队列作为Stride调度器的实现方式之后,运行队列结构也需要作相关改变,其中包括:
structrun_queue中的lab6_run_pool指针,在使用优先队列的实现中表示当前优先队列的头元素,如果优先队列为空,则其指向空指针(NULL)。
structproc_struct中的lab6_run_pool结构,表示当前进程对应的优先队列节点。
本次实验已经修改了系统相关部分的代码,使得其能够很好地适应LAB6新加入的数据结构和接口。
而在实验中我们需要做的是用优先队列实现一个正确和高效的Stride调度器,如果用较简略的伪代码描述,则有:
init(rq):
–Initializerq->run_list
–Setrq->lab6_run_pooltoNULL
–Setrq->proc_numto0
enqueue(rq,proc)
–Initializeproc->time_slice
–Insertproc->lab6_run_poolintorq->lab6_run_pool
–rq->proc_num++
dequeue(rq,proc)
–Removeproc->lab6_run_poolfromrq->lab6_run_pool
–rq->proc_num--
pick_next(rq)
–Ifrq->lab6_run_pool==NULL,returnNULL
–Findtheproccorrespondingtothepointerrq->lab6_run_pool
–proc->lab6_stride+=BIG_STRIDE/proc->lab6_priority
–Returnproc
proc_tick(rq,proc):
–Ifproc->time_slice>0,proc->time_slice--
–Ifproc->time_slice==0,settheflagproc->need_resched
四、主要数据结构及符号说明
在理解框架之前,需要先了解一下调度器框架所需要的数据结构。
通常的操作系统中,进程池是很大的(虽然在ucore中,MAX_PROCESS很小)。
在ucore中,调度器引入runqueue(
简称rq,即运行队列)的概念,通过链表结构管理进程。
由于目前ucore设计运行在单CPU上,其内部只有一个全局的运行队列,用来管理系统内全部的进程。
运行队列通过链表的形式进行组织。
链表的每一个节点是一个list_entry_t,每个list_entry_t又对应到了structproc_struct*,这其间的转换是通过宏le2proc来完成的。
具体来说,我们知道在structproc_struct中有一个叫run_link的list_entry_t,因此可以通过偏移量逆向找到对因某个run_list的structproc_struct。
即进程结构指针proc=le2proc(链表节点指针,run_link)。
为了保证调度器接口的通用性,ucore调度框架定义了如下接口,该接口中,几乎全部成员变量均为函数指针。
具体的功
能会在后面的框架说明中介绍。
1structsched_class{
2//调度器的名字
3constchar*name;
4//初始化运行队列
5void(*init)(structrun_queue*rq);
6//将进程p插入队列rq
7void(*enqueue)(structrun_queue*rq,structproc_struct*p);
8//将进程p从队列rq中删除
9void(*dequeue)(structrun_queue*rq,structproc_struct*p);
10//返回运行队列中下一个可执行的进程
11structproc_struct*(*pick_next)(structrun_queue*rq);
12//timetick处理函数
13void(*proc_tick)(structrun_queue*rq,structproc_struct*p);
14};
此外,proc.h中的structproc_struct中也记录了一些调度相关的信息:
1structproc_struct{
2//...
3//该进程是否需要调度,只对当前进程有效
4volatileboolneed_resched;
5//该进程的调度链表结构,该结构内部的连接组成了运行队列列表
6list_entry_trun_link;
7//该进程剩余的时间片,只对当前进程有效
8inttime_slice;
9//round-robin调度器并不会用到以下成员
10//该进程在优先队列中的节点,仅在LAB6使用
11skew_heap_entry_tlab6_run_pool;
12//该进程的调度优先级,仅在LAB6使用
13uint32_tlab6_priority;
14//该进程的调度步进值,仅在LAB6使用
15uint32_tlab6_stride;
16};
在此次实验中,你需要了解default_sched.c中的实现RR调度算法的函数。
在该文件中,你可以看到ucore已经为RR调度算
法创建好了一个名为RR_sched_class的调度策略类。
通过数据结构structrun_queue来描述完整的run_queue(运行队列)。
它的主要结构如下:
1structrun_queue{
2//其运行队列的哨兵结构,可以看作是队列头和尾
3list_entry_trun_list;
4//优先队列形式的进程容器,只在LAB6中使用
5skew_heap_entry_t*lab6_run_pool;
6//表示其内部的进程总数
7unsignedintproc_num;
8//每个进程一轮占用的最多时间片
uCoreLabDocuments
9intmax_time_slice;
10};
在ucore框架中,运行队列存储的是当前可以调度的进程,所以,只有状态为runnable的进程才能够进入运行队列。
当前正
在运行的进程并不会在运行队列中,这一点需要注意。
五、程序初值及运行结果
六、实验体会和思考题
附录(源代码及注释)
附:
default_sched.cpp文件
#include
#include
#include
#include
#include
#defineUSE_SKEW_HEAP1
/*YoushoulddefinetheBigStrideconstanthere*/
/*LAB6:
YOURCODE*/
#defineBIG_STRIDE0x7FFFFFFF/*?
?
?
*/
/*Thecomparefunctionfortwoskew_heap_node_t'sandthe
*correspondingprocs*/
staticint
proc_stride_comp_f(void*a,void*b)
{
structproc_struct*p=le2proc(a,lab6_run_pool);
structproc_struct*q=le2proc(b,lab6_run_pool);
int32_tc=p->lab6_stride-q->lab6_stride;
if(c>0)return1;
elseif(c==0)return0;
elsereturn-1;
}
实现对进程的比较。
其实是比较stride,如果进程p的tride大就返回1,一样大返回0,如果p的小则返回-1.
/*
*stride_initinitializestherun-queuerqwithcorrectassignmentfor
*membervariables,including:
*
*-run_list:
shouldbeaemptylistafterinitialization.
*-lab6_run_pool:
NULL
*-proc_num:
0
*-max_time_slice:
noneedhere,thevariablewouldbeassignedbythecaller.
*
*hint:
seeproj13.1/libs/list.hforroutinesoftheliststructures.
*/
staticvoid
stride_init(structrun_queue*rq){
/*LAB6:
YOURCODE*/
list_init(&(rq->run_list));
rq->lab6_run_pool=NULL;
rq->proc_num=0;
}
/*
*stride_enqueueinsertstheprocess``proc''intotherun-queue
*``rq''.Theprocedureshouldverify/initializetherelevantmembers
*of``proc'',andthenputthe``lab6_run_pool''nodeintothe
*queue(sinceweusepriorityqueuehere).Theprocedureshouldalso
*updatethemetadatein``rq''structure.
*
*proc->time_slicedenotesthetimeslicesallocationforthe
*process,whichshouldsettorq->max_time_slice.
*
*hint:
seeproj13.1/libs/skew_heap.hforroutinesofthepriority
*queuestructures.
*/
staticvoid
stride_enqueue(structrun_queue*rq,structproc_struct*proc){
/*LAB6:
YOURCODE*/
#ifUSE_SKEW_HEAP
rq->lab6_run_pool=
skew_heap_insert(rq->lab6_run_pool,&(proc->lab6_run_pool),proc_stride_comp_f);
#else
assert(list_empty(&(proc->run_link)));
list_add_before(&(rq->run_list),&(proc->run_link));
#endif
if(proc->time_slice==0||proc->time_slice>rq->max_time_slice){
proc->time_slice=rq->max_time_slice;//如果时间片为0或者时间片比
}
proc->rq=rq;
rq->proc_num++;
}
/*把某进程的进程控制块指针放入到rq队列末尾,且如果进程控制块的时间片为0,则
需要把它重置为rq成员变量max_time_slice。
这表示如果进程在当前的执行时间片已经用完,需要等到下一次有机会运行时,才能再执行一段时间。
*/
/*
*stride_dequeueremovestheprocess``proc''fromtherun-queue
*``rq'',theoperationwouldbefinishedbytheskew_heap_remove
*operations.Remembertoupdatethe``rq''structure.
*
*hint:
seeproj13.1/libs/skew_heap.hforroutinesofthepriority
*queuestructures.
*/
staticvoid
stride_dequeue(structrun_queue*rq,structproc_struct*proc){
/*LAB6:
YOURCODE*/
#ifUSE_SKEW_HEAP
rq->lab6_run_pool=
skew_heap_remove(rq->lab6_run_pool,&(proc->lab6_run_pool),proc_stride_comp_f);
#else
assert(!
list_empty(&(proc->run_link))&&proc->rq==rq);
list_del_init(&(proc->run_link));
#