首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ucore-lab6

ucore-lab6

作者头像
Heeler-Deer
发布于 2023-02-22 06:18:18
发布于 2023-02-22 06:18:18
5140
举报
文章被收录于专栏:HD-学习笔记HD-学习笔记

练习解答

练习0

填写实验

  • kern/process/proc.c
  • kern/mm/pmm.c

有需要更改的文件,建议看Kiprey

练习1

练习1: 使用 Round Robin 调度算法(不需要编码) 完成练习0后,建议大家比较一下(可用kdiff3等文件比较软件)个人完成的lab5和练习0完成后的刚修改的lab6之间的区别,分析了解lab6采用RR调度算法后的执行过程。执行make grade,大部分测试用例应该通过。但执行priority.c应该过不去。 请在实验报告中完成:

  • 请理解并分析sched_class中各个函数指针的用法,并结合Round Robin 调度算法描ucore的调度执行过程
  • 请在实验报告中简要说明如何设计实现”多级反馈队列调度算法“,给出概要设计,鼓励给出详细设计

请理解并分析sched_class中各个函数指针的用法,并结合Round Robin 调度算法描ucore的调度执行过程

在kern/schedule/sched.h不难找到:

123456789101112131415161718192021

struct sched_class { // the name of sched_class const char *name; // Init the run queue void (*init)(struct run_queue *rq); // put the proc into runqueue, and this function must be called with rq_lock void (*enqueue)(struct run_queue *rq, struct proc_struct *proc); // get the proc out runqueue, and this function must be called with rq_lock void (*dequeue)(struct run_queue *rq, struct proc_struct *proc); // choose the next runnable task struct proc_struct *(*pick_next)(struct run_queue *rq); // dealer of the time-tick void (*proc_tick)(struct run_queue *rq, struct proc_struct *proc); /* for SMP support in the future * load_balance * void (*load_balance)(struct rq* rq); * get some proc from this rq, used in load_balance, * return value is the num of gotten proc * int (*get_proc)(struct rq* rq, struct proc* procs_moved[]); */};

由于时间较紧,且根据注释易于理解,此处不再赘述

在kern/schedule/default_sched.c不难找到rr算法的实现,

123456789101112131415161718192021222324252627282930313233343536373839404142434445

static voidRR_init(struct run_queue *rq) { list_init(&(rq->run_list)); rq->proc_num = 0;}//初始化对应的list//run queue的进程号设置为0static voidRR_enqueue(struct run_queue *rq, struct proc_struct *proc) { assert(list_empty(&(proc->run_link))); list_add_before(&(rq->run_list), &(proc->run_link)); if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { //进程时间片等于0或者大于最大时间片时,重新赋值 proc->time_slice = rq->max_time_slice; } proc->rq = rq; rq->proc_num ++;}//将进程添加至队列,static voidRR_dequeue(struct run_queue *rq, struct proc_struct *proc) { assert(!list_empty(&(proc->run_link)) && proc->rq == rq); list_del_init(&(proc->run_link)); rq->proc_num --;}//去除队列static struct proc_struct *RR_pick_next(struct run_queue *rq) { list_entry_t *le = list_next(&(rq->run_list)); if (le != &(rq->run_list)) { return le2proc(le, run_link); } return NULL;}//选择队列最前面的进程static voidRR_proc_tick(struct run_queue *rq, struct proc_struct *proc) { if (proc->time_slice > 0) { proc->time_slice --; } if (proc->time_slice == 0) { proc->need_resched = 1; }}//tick,用于时间片的处理,等于0是重新安排(resched)

如注释所示。

那么也就是说,ucore的RR算法,首先调用kern/schedule/sched.c中的sched_init函数进行初始化(加入list),之后初始化进程(proc目录下的函数),ucore执行cpu_idle(proc目录下)函数,调用sched_class_enqueue加入队列,启动进程计时器。之后pick_next,有可以执行的进程就dequeue,继续proc_run.

相关调用图:

请在实验报告中简要说明如何设计实现”多级反馈队列调度算法“,给出概要设计,鼓励给出详细设计

多级反馈队列实际上是通过设置多个优先级不同的队列实现,其中优先级越高,时间片越高。在enqueue中,如果该进程时间片为0则不改变其优先级,大于0则降低其优先级;调度时,参照ostep的规则,如果 A 的优先级 > B 的优先级,运行 A(不运行 B);如果 A 的优先级 = B 的优先级,轮转运行A 和 B;新进程进入系统时,放在最高优先级(最上层队列);一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次CPU),就降低其优先级(移入低一级队列);经过一段时间 S,就将系统中所有工作重新加入最高优先级队列。

练习二

练习2: 实现 Stride Scheduling 调度算法(需要编码) 首先需要换掉RR调度器的实现,即用default_sched_stride_c覆盖default_sched.c。然后根据此文件和后续文档对Stride度器的相关描述,完成Stride调度算法的实现。 后面的实验文档部分给出了Stride调度算法的大体描述。这里给出Stride调度算法的一些相关的资料(目前网上中文的资料比较欠缺)。

执行:make grade。如果所显示的应用程序检测都输出ok,则基本正确。如果只是priority.c过不去,可执行 make run-priority 命令来单独调试它。大致执行结果可看附录。( 使用的是 qemu-1.0.1 )。 请在实验报告中简要说明你的设计实现过程。

所谓stride算法,实际上就是通过对每一个进程添加一个stride以及一个pass数据,每次选择最小stride(或者说离cpu最近)的进程执行,执行后该进程的stride添加步长pass.其中pass与优先级成反比,pass=BIG_STRIDE / priority

关于BIG_STRIDE的详细推导建议参考yuerer

具体代码实现建议参考kiprey

这里贴一下代码:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879

#include <defs.h>#include <list.h>#include <proc.h>#include <assert.h>#include <default_sched.h>#define USE_SKEW_HEAP 1/* You should define the BigStride constant here*//* LAB6: YOUR CODE */#define BIG_STRIDE ((uint32_t) -1) / 2//选取合适的进程static intproc_stride_comp_f(void *a, void *b){ struct proc_struct *p = le2proc(a, lab6_run_pool); struct proc_struct *q = le2proc(b, lab6_run_pool); int32_t c = p->lab6_stride - q->lab6_stride; if (c > 0) return 1; else if (c == 0) return 0; else return -1;}//直接选取斜堆的顶端,static struct proc_struct *stride_pick_next(struct run_queue *rq) { skew_heap_entry_t* she = rq->lab6_run_pool; if (she != NULL) { struct proc_struct* p = le2proc(she, lab6_run_pool); //更新stride p->lab6_stride += BIG_STRIDE / p->lab6_priority; return p; } return NULL;}//初始化,多了一个指针static voidstride_init(struct run_queue *rq) { list_init(&(rq->run_list)); rq->lab6_run_pool = NULL; rq->proc_num = 0;}//enqueue以及dequeue更新指针即可static voidstride_enqueue(struct run_queue *rq, struct proc_struct *proc) { rq->lab6_run_pool = skew_heap_insert(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f); if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { proc->time_slice = rq->max_time_slice; } proc->rq = rq; rq->proc_num ++;}static voidstride_dequeue(struct run_queue *rq, struct proc_struct *proc) { rq->lab6_run_pool = skew_heap_remove(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f); rq->proc_num --;}//tick正常static voidstride_proc_tick(struct run_queue *rq, struct proc_struct *proc) { if (proc->time_slice > 0) { proc->time_slice --; } if (proc->time_slice == 0) { proc->need_resched = 1; }}//改一下名字就好了struct sched_class default_sched_class = { .name = "stride_scheduler", .init = stride_init, .enqueue = stride_enqueue, .dequeue = stride_dequeue, .pick_next = stride_pick_next, .proc_tick = stride_proc_tick,};

到这里,执行make grade时,会出错。

根据网上博客,解决办法是注释掉grade.sh的221到233行,我们来看一下这一段:

1234567891011121314151617

# found和not相等就为真# found,not都是标志变量,# not在代码没有错误的情况下为0# found=$(($? == 0)),不是很清楚在干什么。。。if [ $found -eq $not ]; then if [ $found -eq 0 ]; then msg="!! error: missing '$i'" else msg="!! error: got unexpected line '$i'" fi okay=no if [ -z "$error" ]; then error="$msg" else error="$error\n$msg" fi fi

这一段是在check_regexps里面,okay为no时就会报错,而ucore的最终得分就是通过检查有无报错产生的。如果想蒙混过关,把这个no改成yes或者注释掉这一段代码或者把-eq改成-ne就行了

但如果每一次验收实验都只是为了验收而验收,自己什么都没学到,那还有什么意思呢?

当然对os不感兴趣的话混就得了

实际上,我们的错误是由于一个细节,在之前的kern/schedule/default_sched.c中,优先级初始化为0,而0在stride中是不能作为被除数存在的,因此需要更改kern/process/proc.c中的proc->lab6_priority为1。

结果:

Challenge 1

扩展练习 Challenge 1 :实现 Linux 的 CFS 调度算法

在ucore的调度器框架下实现下Linux的CFS调度算法。可阅读相关Linux内核书籍或查询网上资料,可了解CFS的细节,然后大致实现在ucore中。

CFS (完全公平调度器)实现的主要思想是维护为任务提供处理器时间方面的平衡(公平性)。它给每个进程设置了一个虚拟时钟vruntime。其中vruntime=实际运行时间∗1024/进程权重 进程按照各自不同的速率在物理时钟节拍内前进,优先级高则权重大,其虚拟时钟比真实时钟跑得慢,但获得比较多的运行时间;CFS调度器总是选择虚拟时钟跑得慢的进程来运行,从而让每个调度实体的虚拟运行时间互相追赶,进而实现进程调度上的平衡。 CFS使用红黑树来进行快速高效的插入和删除进程。

仍是参考PKUanonym的仓库:ucore

先上结果:

下面分析佬的代码,

kern/schedule/cfs_sched.h没有什么大的改变,改个名字就好了

kern/schedule/cfs_sched.c中,

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384

#define NICE_0_LOAD 1024static intproc_cfs_comp_f(void *a, void *b){ struct proc_struct *p = le2proc(a, lab6_run_pool); struct proc_struct *q = le2proc(b, lab6_run_pool); int32_t c = p->lab6_stride - q->lab6_stride; if (c > 0) return 1; else if (c == 0) return 0; else return -1;}//绝对公平,不需要list或者其他数据static voidcfs_init(struct run_queue *rq) { rq->lab6_run_pool = NULL; rq->proc_num = 0;}//enqueue以及dequeue没什么说的static voidcfs_enqueue(struct run_queue *rq, struct proc_struct *proc) { rq->lab6_run_pool = skew_heap_insert(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_cfs_comp_f); if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { proc->time_slice = rq->max_time_slice; } proc->rq = rq; rq->proc_num += 1;}static voidcfs_dequeue(struct run_queue *rq, struct proc_struct *proc) { rq->lab6_run_pool = skew_heap_remove(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_cfs_comp_f); rq->proc_num -= 1;}//通过设置进程的时间来改变优先级,类似与stride//可以对比一下,/*static struct proc_struct *stride_pick_next(struct run_queue *rq) { skew_heap_entry_t* she = rq->lab6_run_pool; if (she != NULL) { struct proc_struct* p = le2proc(she, lab6_run_pool); //更新stride p->lab6_stride += BIG_STRIDE / p->lab6_priority; return p; } return NULL;}*/static struct proc_struct *cfs_pick_next(struct run_queue *rq) { if (rq->lab6_run_pool == NULL) return NULL; struct proc_struct* min_proc = le2proc(rq->lab6_run_pool, lab6_run_pool); if (min_proc->lab6_priority == 0) { min_proc->lab6_stride += NICE_0_LOAD; } else if (min_proc->lab6_priority > NICE_0_LOAD) { min_proc->lab6_stride += 1; } else { min_proc->lab6_stride += NICE_0_LOAD / min_proc->lab6_priority; } return min_proc;}//tick没什么static voidcfs_proc_tick(struct run_queue *rq, struct proc_struct *proc) { if (proc->time_slice > 0) { proc->time_slice --; } if (proc->time_slice == 0) { proc->need_resched = 1; }}struct sched_class cfs_sched_class = { .name = "cfs_scheduler", .init = cfs_init, .enqueue = cfs_enqueue, .dequeue = cfs_dequeue, .pick_next = cfs_pick_next, .proc_tick = cfs_proc_tick,};

Challenge 2

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ucoreOS_lab6 实验报告
lab6 会依赖 lab1~lab5 ,我们需要把做的 lab1~lab5 的代码填到 lab6 中缺失的位置上面。练习 0 就是一个工具的利用。这里我使用的是 Linux 下的系统已预装好的 Meld Diff Viewer 工具。和 lab5 操作流程一样,我们只需要将已经完成的 lab1~lab5 与待完成的 lab6 (由于 lab6 是基于 lab1~lab5 基础上完成的,所以这里只需要导入 lab5 )分别导入进来,然后点击 compare 就行了。
Angel_Kitty
2019/08/05
1.8K0
ucoreOS_lab6 实验报告
CFS调度主要代码分析二
本节在围绕一个进程的生命周期,继续分析一个进程是如何被抢占? 如果睡眠? 如何被调度出去的?
DragonKingZhu
2020/03/24
1.2K0
Linux O(1)调度器
O(n)调度器采用一个runqueue运行队列来管理所有可运行的进程,在主调度schedule函数中会选择一个优先级最高,也就是时间片最大的进程来运行,同时也会对喜欢睡眠的进程做一些补偿,去增加此类进程的时间片。当runqueue运行队列中无进程可选择时,则会对系统中所有的进程进行一次重新计算时间片的操作,同时也会对剩余时间片的进程做一次补偿。
DragonKingZhu
2020/03/24
3.1K0
Linux O(1)调度器
Linux进程调度之 - O(1)调度算法
Linux是一个支持多任务的操作系统,而多个任务之间的切换是通过 调度器 来完成,调度器 使用不同的调度算法会有不同的效果。
用户7686797
2020/08/24
5.2K0
Linux进程调度之 - O(1)调度算法
Linux内核调度分析(进程调度)
本文是《Linux内核设计与实现》第四章的阅读笔记,代码则是摘自最新的4.6版本linux源码(github),转载请注明出处。
Marky Lumin
2018/01/23
15.3K0
Linux调度原理介绍和应用(前篇)
提示:公众号展示代码会自动折行,建议横屏阅读 摘要 本文(有码慎入)主要介绍Linux任务调度相关的发展历史和基本原理。多年以来,内核界的黑客们一直着力于寻找既能满足高负载后台任务资源充分利用,又能满足桌面系统良好交互性的调度方法,尽管截至到目前为止仍然没有一个完美的解决方案。本文希望通过介绍调度算法的发展历程,因为任务调度本身不是一个局限于操作系统的话题,包括数据库,程序语言实现等,都会与调度相关。本文在介绍过程中,会引用Linux的代码实现作为说明,同时阐述其中的一些趣闻轶事。 调度实体 进程任务通常包
腾讯数据库技术
2018/07/26
1.4K0
【Linux 内核】实时调度类 ⑥ ( 实时调度类核心函数源码分析 | 插入进程到执行队列 | 从执行队列中选择优先级最高的进程 )
本篇博客中 , 开始分析 struct sched_class rt_sched_class 结构体变量 中的各个 函数指针 指向的 函数源码 ;
韩曙亮
2023/03/30
6070
Linux 进程调度之schdule主调度器
考虑到文章篇幅,在这里我只讨论普通进程,其调度算法采用的是CFS(完全公平)调度算法。 至于CFS调度算法的实现后面后专门写一篇文章,这里只要记住调度时选择一个优先级最高的任务执行
233333
2023/05/03
2K0
Linux 进程调度之schdule主调度器
CFS调度主要代码分析一
在前面学习了CFS调度的原理和主要的数据结构,今天我们就来进入代码分析环节。当然了代码分析只看主要主干不看毛细,同时我们也是根据一个进程是如何被调度的思路来分析一些重要的代码。
DragonKingZhu
2020/03/24
2.5K0
CFS调度主要代码分析一
调度器及CFS调度器
调度:就是按照某种调度的算法设计,从进程的就绪队列中选择进程分配CPU,主要是协调进程对CPU等相关资源的使用。
laputa
2022/11/21
1.2K0
Linux进程调度器的设计--Linux进程的管理与调度(十七)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换.
233333
2018/12/04
3.8K0
Linux进程调度器的设计--Linux进程的管理与调度(十七)
【Linux 内核】实时调度类 ⑤ ( 实时调度类 rt_sched_class 源码分析 | 结构体字段及函数指针分析 )
在 【Linux 内核】实时调度类 ③ ( 实时调度类 rt_sched_class 源码 | 调度类 sched_class 源码 ) 博客中 , 简单介绍了 实时调度类 rt_sched_class 结构体 , 下面开始分析该结构体的具体字段含义 ,
韩曙亮
2023/03/30
1.2K0
【Linux 内核】实时调度类 ⑤ ( 实时调度类 rt_sched_class 源码分析 | 结构体字段及函数指针分析 )
Linux CFS调度器之pick_next_task_fair选择下一个被调度的进程--Linux进程的管理与调度(二十八)
每个调度器类sched_class都必须提供一个pick_next_task函数用以在就绪队列中选择一个最优的进程来等待调度, 而我们的CFS调度器类中, 选择下一个将要运行的进程由pick_next_task_fair函数来完成
233333
2018/12/14
2.2K0
Linux CFS调度器之pick_next_task_fair选择下一个被调度的进程--Linux进程的管理与调度(二十八)
Linux Kernel调度器的过去,现在和未来
Linux Kernel Development 一书中,关于 Linux 的进程调度器并没有讲解的很全面,只是提到了 CFS 调度器的基本思想和一些实现细节;并没有 Linux 早期的调度器介绍,以及最近这些年新增的在内核源码树外维护的调度器思想。所以在经过一番搜寻后,看到了这篇论文 A complete guide to Linux process scheduling,对 Linux 的调度器历史进行了回顾,并且相对细致地讲解了 CFS 调度器。整体来说,虽然比较啰嗦,但是对于想要知道更多细节的我来说非常适合,所以就有了翻译它的冲动。当然,在学习过程也参考了其它论文。下面开启学习之旅吧,如有任何问题,欢迎指正~
刘盼
2020/04/20
2.7K0
Linux Kernel调度器的过去,现在和未来
聊聊Linux内核进程调度下篇
进程优先级 Linux内核中进程优先级一般分为动态优先级和静态优先级,动态优先级是内核根据进程的nice值、IO密集行为或者计算密集行为以及等待时间等因素,设置给普通的进程;静态优先级是用户态应用设置给实时进程。在调度中静态优先级的进程优先级更高。 一般应用分为IO密集型和计算密集型;I/O密集型是进程执行I/O操作时候等待资源或者事件时候,数据读取到后恢复进程的运行,这样基本出于等待IO和运行之间进行交替,由于具有这样的特性,进程调度器通常会将短的CPU时间片分配给I/O密集型进程。计算密集型是进
用户4700054
2022/08/17
1.4K0
聊聊Linux内核进程调度下篇
【Linux 内核】调度器 ② ( sched_class 调度类结构体源码 | 源码路径 linux-5.6.18\kernel\sched\sched.h )
上一篇博客 【Linux 内核】调度器 ( 调度器概念 | 调度器目的 | 调度器主要工作 | 调度器位置 | 进程优先级 | 抢占式调度器 | Linux 进程状态 | Linux 内核进程状态 ) 介绍了 " 调度器 " 概念 ,
韩曙亮
2023/03/30
7020
【Linux 内核】调度器 ② ( sched_class 调度类结构体源码 | 源码路径 linux-5.6.18\kernel\sched\sched.h )
你的新进程是如何被内核调度执行到的?
在前面的文章《Linux进程是如何创建出来的?》 和 《聊聊Linux中线程和进程的联系与区别》 中我们都讲过了,进程和线程在创建出来后会加入运行队列里面等待被调度。
开发内功修炼
2022/12/07
8230
你的新进程是如何被内核调度执行到的?
CFS 调度器数据结构篇
在上一节我们了解了CFS的设计原理,包括CFS的引入,CFS是如何实现公平,CFS工作原理的。本小节我们重点在分析CFS调度器中涉及到的一些常见的数据结构,对这些数据结构做一个简单的概括,梳理各个数据结构之间的关系图出来。
DragonKingZhu
2020/03/24
1.4K0
CFS 调度器数据结构篇
深入理解Linux内核之进程唤醒
进程唤醒的主要调用链如上:会唤醒特定状态的进程(wake_up_process唤醒三种睡眠状态的进程,睡眠文章已经讲到),然后选择一个合适的cpu,接着会加入到cpu的运行队列以及进行唤醒抢占操作(这里还会有很多防止并发访问的自旋锁、关抢占、内存屏障等操作,大家自行研究)。
用户7244416
2021/09/03
3.5K0
linux内核上下文切换解析
    linux的上下文切换就是进程线程的切换,也就是切换struct task_struct结构体,一个任务的上下文包括cpu的寄存器,内核栈等,由于1个cpu上的所有任务共享一套寄存器,所以在任务挂起的时候需要保存寄存器,当任务重新被调度执行的时候需要恢复寄存器。每种处理器都提供了硬件级别的上下文切换,比如x86架构下的TSS段,TSS段包括了一个任务执行的所需要的所有上下文,主要有:1.通用寄存器和段寄存器。2.标志寄存器EFLAGS,程序指针EIP,页表基地址寄存器CR3,任务寄存器和LDTR寄存器。3.I/O映射位图基地址和I/O位图信息。4.特权级0,1,2堆栈指针。5.链接到前一任务的链指针。所以上下文切换也很简单,直接用call或者jmp指令调度任务。同样ARM架构也有快速上下文切换技术。但是Linux为了适用更多的cpu架构没使用处理器相关的上下文切换技术,而是大部分通过软件实现。linux上下文切换就在schedule()函数里,很多地方都会调用这个函数。scchedule函数前面大部分代码是和调度算法相关的,比如实时任务调度算法,O(1)调度算法(2.6.22版本被CFS调度算法取代),CFS调度算法等。经过前面的代码计算后找出下一个要执行的任务,然后开始执行上下文切换。先看一段linux2.6.18版本还使用O(1)调度算法的schedule函数代码:
用户4415180
2022/06/23
1.4K0
linux内核上下文切换解析
相关推荐
ucoreOS_lab6 实验报告
更多 >
LV.0
这个人很懒,什么都没有留下~
作者相关精选
换一批
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
加入讨论
的问答专区 >
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档