实验目的
实验内容
查看lab6相关的代码
练习0:填写已有实验
本实验依赖实验1/2/3/4/5。请把你做的实验2/3/4/5的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”“LAB5”的注释相应部分。并确保编译通过。注意:为了能够正确执行lab6的测试应用程序,可能需对已完成的实验1/2/3/4/5的代码进行进一步改进。
要修改的文件有proc.c default_pmm.c pmm.c swap_fifo.c vmm.c trap.c kdebug.c
另外,有部分需要在原代码基础上修改
1 | proc.c |
练习1: 使用 Round Robin 调度算法
完成练习0后,建议大家比较一下(可用kdiff3等文件比较软件)个人完成的lab5和练习0完成后的刚修改的lab6之间的区别,分析了解lab6采用RR调度算法后的执行过程。执行make grade,大部分测试用例应该通过。但执行priority.c应该过不去。
请在实验报告中完成:
- 请理解并分析sched_class中各个函数指针的用法,并结合Round Robin 调度算法描ucore的调度执行过程
- 请在实验报告中简要说明如何设计实现“多级反馈队列调度算法”,给出概要设计,鼓励给出详细设计
实验过程
执行make grade
执行priority.c失败
回答问题
请理解并分析sched_class中各个函数指针的用法,并结合Round Robin 调度算法描ucore的调度执行过程
1 | sched.h |
init
函数指针用于初始化调度器enqueue
函数指针用于将一个进程放入调度队列中dequeue
函数指针用于将一个进程从调度队列中出队pick_next
函数指针用于从调度队列中根据算法选出下一个要被运行的进程proc_tick
函数指针用于系统时钟中断时通知调度器
查看default_sched.c
使用RR调度算法
时间片轮转法(Round-Robin,RR)主要用于分时系统中的进程调度。为了实现轮转调度,系统把所有就绪进程按先入先出的原则排成一个队列。新来的进程加到就绪队列末尾。每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间。时间片是一个小的时间单位,通常为10~100ms数量级。当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该进程的运行,把它放入就绪队列的末尾;然后,把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此往复。
1 | default_sched.c |
- 设置当前进程剩余时间片
- 每次时钟中断 都将当前进程剩余时间片 -1
- 直到 剩余时间片 为 0 当前进程的 need_resched 被置1 意为 需要被调度
- 中断服务进程 一发现当前进程需要被调度 就 调用 schedule() 将它调度
- 会将当前进程 放入就绪队列中 并将其时间片设为当前队列的最大时间片
- 接着调用 pick_next() 选择下一个需要换上处理机的进程 若选择成功 就让其 出就绪队列
- 若查找不到 则 内核线程 idle() 运行 该进程会死循环 不断查找可被调度的进程
- 最后调用 proc_run() 进行进程切换实现新进程的运行
RR调度算法的就绪队列在组织结构上也是一个双向链表,只是增加了一个成员变量,表明在此就绪进程队列中的最大执行时间片。而且在进程控制块proc_struct中增加了一个成员变量time_slice,用来记录进程当前的可运行时间片段。这是由于RR调度算法需要考虑执行进程的运行时间不能太长。在每个timer到时的时候,操作系统会递减当前执行进程的time_slice,当time_slice为0时,就意味着这个进程运行了一段时间(这个时间片段称为进程的时间片),需要把CPU让给其他进程执行,于是操作系统就需要让此进程重新回到rq的队列尾,且重置此进程的时间片为就绪队列的成员变量最大时间片max_time_slice值,这表示如果进程在当前的执行时间片已经用完,需要等到下一次有机会运行时,才能再执行一段时间,然后再从rq的队列头取出一个新的进程执行。
请在实验报告中简要说明如何设计实现“多级反馈队列调度算法”,给出概要设计,鼓励给出详细设计
其与RR调度算法的区别的是对各进程的时间片大小进行了有规定过的量化
算法思想:
- 时间片大小随优先级级别增加而增加
- 进程在当前时间片没有完成 则降到下一优先级
- I/O密集型在高优先级 CPU密集型在低优先级
算法说明
例如,一个多级反馈队列的调度程序有三个队列,从 0 到 2。调度程序首先执行队列 0 内的所有进程。只有当队列 0 为空时,它才能执行队列 1 内的进程。类似地,只有队列 0 和 1 都为空时,队列 2 的进程才能执行。到达队列 1 的进程会抢占队列 2 的进程。同样,到达队列 0 的进程会抢占队列 1 的进程。
每个进程在进入就绪队列后,就被添加到队列 0 内。队列 0 内的每个进程都有 8ms 的时间片。如果一个进程不能在这一时间片内完成,那么它就被移到队列 1 的尾部。如果队列 0 为空,队列 1 头部的进程会得到一个 16ms 的时间片。如果它不能完成,那么将被抢占,并添加到队列 2。只有当队列 0 和 1 为空时,队列 2 内的进程才可根据 FCFS 来运行。
这种调度算法将给那些 CPU 执行不超过 8ms 的进程最高优先级。这类进程可以很快得到 CPU,完成 CPU 执行,并且处理下个 I/O 执行。
所需超过 8ms 但不超过 24ms 的进程也会很快得以服务,但是它们的优先级要低一点。长进程会自动沉入队列 2,队列 0 和 1 不用的 CPU 周期按 FCFS 顺序来服务。
算法实现:
-
init
初始化算法维护的数据结构 -
enqueue
(所有进程一开始都进入高优先级队列)
如果当前进程的时间片计时器值为0(意味着该进程要么不能在相应时间片里运行结束,要么是新来的进程) 如果是新进程,将其入队到最高优先级队列,设置时间片为该优先级的最大值
否则将其从当前优先级队列复制入队到更低优先级队列,再将其从原队列dequeue,并且将当前时间片计数器值设置为原队列最大时间片大小的2倍,(等待下一次的调度)
-
dequeue
从相应优先级的队列中删去该进程 -
pick_next
从最高优先级队列中开始查找进程,若无则往更低的优先级队列继续查找,直到找到可运行的进程,并将其换上处理机去运行 -
proc_tick
时钟中断所使用 每次时钟中断 减少当前进程的时间片 若为0 则 将进程标记为需要调度 调用enqueue进行从新调度
练习2: 实现 Stride Scheduling 调度算法
首先需要换掉RR调度器的实现,即用default_sched_stride_c覆盖default_sched.c。然后根据此文件和后续文档对Stride度器的相关描述,完成Stride调度算法的实现。
后面的实验文档部分给出了Stride调度算法的大体描述。这里给出Stride调度算法的一些相关的资料(目前网上中文的资料比较欠缺)。
- strid-shed paper location1
- strid-shed paper location2
- 也可GOOGLE “Stride Scheduling” 来查找相关资料
执行:make grade。如果所显示的应用程序检测都输出ok,则基本正确。如果只是priority.c过不去,可执行 make run-priority 命令来单独调试它。大致执行结果可看附录。( 使用的是 qemu-1.0.1 )。
请在实验报告中简要说明你的设计实现过程。
使用斜堆数据结构
实验过程
首先,根据的要求覆盖掉Round Robin调度算法。 覆盖掉之后需要在该框架上实现Stride Scheduling调度算法。
- 1、为每个RUNNABLE的进程设置一个当前状态stride,表示该进程当前的调度权。另外定义其对应的pass值,表示对应进程在调度后,stride 需要进行的累加值。
- 2、 每次需要调度时,从当前 runnable 态的进程中选择 stride最小的进程调度。对于获得调度的进程P,将对应的stride加上其对应的步长pass(只与进程的优先权有关系)。
- 3、在一段固定的时间之后,回到步骤2,重新调度当前stride最小的进程
由于算法涉及到每次取出当前stride最小的进程,用优先队列(斜堆)存放各进程的stride,降低算法时间复杂度。
**斜堆数据结构 **:
libs/skew_heap.h
1 |
|
对default_sched_stride_c文件进行修改,依照default_sched.c对各函数进行仿写补充
proc_stride_comp_f函数
1 |
|
stride_init函数
首先初始化调度器类的信息,初始化运行队列为一个空的容器结构,然后设置当前运行队列内进程数目为0
1 | /* |
stride_enqueue函数
初始化刚进入运行队列的进程proc的stride属性。 比较队头元素(队头即为最小堆的最小值元素)与当前进程的步数大小,选择步数最小的运行,将proc插入放入运行队列中去(注意:这里并不要求放置在队列头部)。 最后初始化时间片,然后将运行队列进程数目加一。
1 | /* |
stride_dequeue函数
从运行队列中删除相应的元素,完成将一个进程从队列中移除的功能,使用优先队列。最后运行队列数目减一。
1 | /* |
stride_pick_next函数
扫描整个运行队列,返回其中stride值最小的对应进程。
更新对应进程的stride值,即pass = BIG_STRIDE / P->priority; P->stride += pass。将步长设置为优先级的倒数,如果为0则设置为最大的步长。
1 | /* |
stride_proc_tick函数
检测当前进程是否已用完分配的时间片。如果时间片用完,应该正确设置进程结构的相关标记来引起进程切换。 一个进程最多可以连续运行 rq.max_time_slice个时间片。 具体思想同RR算法思想,该部分代码和RR算法完全相同
1 | /* |
stride_sched_class
定义一个c语言类的实现,提供调度算法的切换接口,这里将原来的default_sched_class改为stride_sched_class
1 | struct sched_class stride_sched_class = { |
替换调用接口
1 | cp default_sched_stride_c default_sched_stride.c |
1 | default_sched.h |
测试代码
make run-priority
make grade
测试通过
如果测验未能通过,问题可能出现在MAX_TIME_SLICE上,这个MAX_TIME_SLICE的值有时候能通过priority测验,有时候却不行,如果不行的话,可以将sched.h
的MAX_TIME_SLICE的值进行调节(例如把20改成5)看是否能通过测验。
实验总结
本次实验的主要知识点在于对处理机调度算法的掌握和理解,这次试验我了解了时间中断的具体实现方法,即是用计数器循环减一来实现的。通过此次实验我了解了Round Robin调度算法 和 Stride Scheduling 调度算法的具体实现方法,对课上的知识有了更深的理解和掌握。