OS ucore lab 6

练习零: 填写已有实验:

复制以下文件 其中 proc.c 和 trap.c 需要进行修正
vmm.c trap.c default_pmm.c pmm.c proc.c swap_fifo.c
proc.c:
static struct proc_struct *alloc_proc(void) {
   
    //初始化进程所属就绪队列
    proc->rq = NULL;
    //初始化就绪队列指针
    list_init(&(proc->run_link));
    //初始化时间片
    proc->time_slice = 0;
}
trap.c:
static void trap_dispatch(struct trapframe *tf) {
   
    // 在时钟中断下添加以下几行并去掉之前设置 进程需要调度标记
    // 这个标记现在已经被调度程序所使用了,不再需要自己控制
    ++ticks;
    sched_class_proc_tick(current);
    // current->need_resched = 1;
}

练习一:使用 Round Robin 调度算法(不需要编码)

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

请在实验报告中完成

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

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

  • Q1:
// 初始化算法所需要的数据结构
static void RR_init(struct run_queue *rq) {
   
    list_init(&(rq->run_list));
    rq->proc_num = 0;
}
/*** 进程入队: 将进程加入就绪队列(不同的就绪队列的时间片不同 也就是说有不同优先级的就绪队列) 在RR调度中,当进程时间片为0或因某种情况被阻塞,则将其加入到就绪队列并将其时间片进行重置 ***/

static void RR_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) {
   
        proc->time_slice = rq->max_time_slice;
    }
    proc->rq = rq;
    rq->proc_num ++;
}

// 进程出队:将进程从就绪队列中删去
static void RR_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 --;
}
/***挑选出下一个进程 占用处理机去运行 在 RR调度中 直接按照就绪队列的顺序轮询 ***/
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;
}
// 时钟中断时调用此函数 
// 在RR调度中,每次调用都会减少当前进程时间片
static void RR_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;
    }
}

Round Robin 调度执行过程:

* 在ucore中调用调度器的主体函数(不包括init,proc_tick)的代码仅存在在wakeup_proc和schedule,前者的作用在于将某一个指定进程放入可执行进程队列中,后者在于将当前执行的进程放入可执行队列中,然后将队列中选择的下一个执行的进程取出执行;

* 当需要将某一个进程加入就绪进程队列中,则需要将这个进程的能够使用的时间片进行初始化,然后将其插入到使用链表组织的队列的对尾;这就是具体的Round-Robin enqueue函数的实现;

* 当需要将某一个进程从就绪队列中取出的时候,只需要将其直接删除即可;

* 当需要取出执行的下一个进程的时候,只需要将就绪队列的队头取出即可;

* 每当出现一个时钟中断,则会将当前执行的进程的剩余可执行时间减1,一旦减到了0,则将其标记为可以被调度的,这样在ISR中的后续部分就会调用schedule函数将这个进程切换出去
  • Q2:

    在proc_struct中添加总共N个多级反馈队列的入口,每个队列都有着各自的优先级,编号越大的队列优先级约低,并且优先级越低的队列上时间片的长度越大,为其上一个优先级队列的两倍;并且在PCB中记录当前进程所处的队列的优先级;
    处理调度算法初始化的时候需要同时对N个队列进行初始化;
    在处理将进程加入到就绪进程集合的时候,观察这个进程的时间片有没有使用完,如果使用完了,就将所在队列的优先级调低,加入到优先级低1级的队列中去,如果没有使用完时间片,则加入到当前优先级的队列中去;
    在同一个优先级的队列内使用时间片轮转算法;
    在选择下一个执行的进程的时候,有限考虑高优先级的队列中是否存在任务,如果不存在才转而寻找较低优先级的队列;(有可能导致饥饿)
    从就绪进程集合中删除某一个进程就只需要在对应队列中删除即可;
    处理时间中断的函数不需要改变;
    至此完成了多级反馈队列调度算法的具体设计;
    
    

练习二: 实现 Stride Scheduling 调度算法(需要编码)

​ 首先需要换掉RR调度器的实现,即用default_sched_stride_c覆盖default_sched.c。然后根据此文件和后续文档对Stride度器的相关描述,完成Stride调度算法的实现。

proc.c:
static struct proc_struct *alloc_proc(void) {
    proc->rq = NULL;
    list_init(&(proc->run_link));
    proc->time_slice = 0;
    // 斜堆实现的 Priority Queue
    proc->lab6_run_pool.parent = proc->lab6_run_pool.left = proc->lab6_run_pool.right = NULL;
    // 优先级 (和步进成反比)
    proc->lab6_priority = 0;
    // 步进值
    proc->lab6_stride = 0;
}
// 就是32位系统下int的最大值 2147483647
#define BIG_STRIDE (((uint32_t)-1) / 2)

static void stride_init(struct run_queue *rq) {
   
    list_init(&rq->run_list);
    rq->lab6_run_pool = NULL;
    rq->proc_num = 0;
}
//在将指定进程加入就绪队列的时候,需要调用斜堆的插入函数将其插入到斜堆中,然后对时间片等信息进行更新
static void stride_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->lab6_priority == 0) {
   
        proc->lab6_priority = 1;
    }
    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;// 维护就绪队列中进程的数量
}   

//stride_dequeue:将指定进程从就绪队列中删除,只需要将该进程从斜堆中删除掉
static void stride_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;// 维护就绪队列中的进程总数
}
//stride_pick_next: 选择下一个要执行的进程,根据stride算法,只需要选择stride值最小的进程,即斜堆的根节点对应的进程
static struct proc_struct * stride_pick_next(struct run_queue *rq) {
   
    if (rq->lab6_run_pool == NULL) {
   
        return NULL;
    }
    // 斜堆的顶就是 stride 值最小的进程
    struct proc_struct *p = le2proc(rq->lab6_run_pool, lab6_run_pool);
    // 提前增加 stride 的值 因为在之后调度别的进程之前一定会执行这么多
    p->lab6_stride += BIG_STRIDE / p->lab6_priority;
    return p;
}
//stride_proc_tick:每次时钟中断需要调用的函数,与RR算法中的实现没有区别
static void stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) {
   
    if (proc->time_slice == 0) {
   
        proc->need_resched = 1;
    } else {
   
        --proc->time_slice;// 维护就绪队列中的进程总数
    }
}

实验结果:

图

参考链接