ucore lab6 调度管理机制

练习0:填写已有实验

具体更改的地方如下:

proc.c 中alloc_proc新增加内容

        proc->rq = NULL;
        list_init(&proc->run_link);
        //proc->run_link.next = proc->run_link.prev = NULL ;
        proc->time_slice = 0;
        proc->lab6_run_pool.left = proc->lab6_run_pool.right = proc->lab6_run_pool.parent = NULL;
        proc->lab6_stride = 0;
        proc->lab6_priority = 1;

链表初始化应该用list_init而不是空

priority要设置成1,不能设置为0,因为后续有的代码会除以这个数字,设置为0可能有bug

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

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

    struct sched_class {
        // 算法名称
        const char *name;
        //初始化算法用数据
        void (*init)(struct run_queue *rq);
        // 当有进程设置为可调度时调用该函数放入就绪队列
        void (*enqueue)(struct run_queue *rq, struct proc_struct *proc);
        //在就绪队列中选出一个去运行然后在就绪队列中一出该信息
        void (*dequeue)(struct run_queue *rq, struct proc_struct *proc);
        //查看就绪队列中下一个是谁可以运行 
        struct proc_struct *(*pick_next)(struct run_queue *rq);
        //每次时钟中断时调用该函数,该函数需要处理,占用cpu时间片的剩余以及是否可调度
        void (*proc_tick)(struct run_queue *rq, struct proc_struct *proc);
    };
    

    ucore调度过程:

    void
    schedule(void) {
        bool intr_flag;
        struct proc_struct *next;
        local_intr_save(intr_flag);
        {
            current->need_resched = 0;
            if (current->state == PROC_RUNNABLE) {
             //   cprintf("insert into %p\n" , current);
                sched_class_enqueue(current);
            }
            if ((next = sched_class_pick_next()) != NULL) {
              //  cprintf("dequeue out %p\n" , next);
                sched_class_dequeue(next);
            }
            if (next == NULL) {
                next = idleproc;
            }
            next->runs ++;
            if (next != current) {
             //   cprintf("run %p \n" , next);
                proc_run(next);
            }
        }
        local_intr_restore(intr_flag);
    }
    

    ​ 首先就是创建俩个内核线程,一个执行初始化后是死循环调度,另一个则是创建子进程然后调度

    ​ 子线程通过do_fork 一出生就被挂到proc_list和hash_list这个链表上了,顺带调用wakeup_proc这个函数然 后就通过进程 调度算法的入队方式挂上去了

    void
    wakeup_proc(struct proc_struct *proc) {
        assert(proc->state != PROC_ZOMBIE);
        bool intr_flag;
        local_intr_save(intr_flag);
        {
            if (proc->state != PROC_RUNNABLE) {
                proc->state = PROC_RUNNABLE;
                proc->wait_state = 0;
                if (proc != current) {
                    sched_class_enqueue(proc);//这个函数就是挂到调度算法列表上
                }
            }
            else {
                warn("wakeup runnable process.\n");
            }
        }
        local_intr_restore(intr_flag);
    }
    
    

    接下来就是,要么时钟中断,要么进程自己放弃cpu然后同过调度算法切换另一个进程,而该算法逻辑是给进程分配一个时间片,然后每一个时钟中断检测一下进程的时间片,要是用完了,就通过调度算法,将进程放到就绪列表尾部。,然后等待下次调度

    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 ++;
    }
    
    

    其中trap.c中是通过run_timer_list函数来实现每次进程时间片减少的

    void
    run_timer_list(void) {
        bool intr_flag;
        local_intr_save(intr_flag);
        {
            list_entry_t *le = list_next(&timer_list);
            if (le != &timer_list) {
                timer_t *timer = le2timer(le, timer_link);
                assert(timer->expires != 0);
                timer->expires --;
                while (timer->expires == 0) {
                    le = list_next(le);
                    struct proc_struct *proc = timer->proc;
                    if (proc->wait_state != 0) {
                        assert(proc->wait_state & WT_INTERRUPTED);
                    }
                    else {
                        warn("process %d's wait_state == 0.\n", proc->pid);
                    }
                    wakeup_proc(proc);
                    del_timer(timer);
                    if (le == &timer_list) {
                        break;
                    }
                    timer = le2timer(le, timer_link);
                }
            }   
            sched_class_proc_tick(current);就走了个这个
        }
        local_intr_restore(intr_flag);
    }
    
    

    嗯~~ 一大堆代码,但是在lab6中好像没有用到if里的就走了 sched_class_proc_tick(current);来调用调度算法的RR_proc_tick这个

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

    嗯~ 不会

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

首先是这个BIG_STRIDE应该取多少值:

static int
proc_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;
}

下面来仔细分析一下吧:

​ 在具体实现时,有一个需要注意的地方:stride属性的溢出问题,在之前的实现里面我们并没有考虑 stride 的数值范围,而这个值在理论上是不断增加的,在 stride溢出以后,基于stride的比较可能会出现错误。比如假设当前存在两个进程A和B,stride属性采用16位无符号整数进行存储。当前队列中元素如下(假设当前运行的进程已经被重新放置进运行队列中):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMnZa3UF-1690546856134)(https://chyyuu.gitbooks.io/ucore_os_docs/content/lab6_figs/image001.png)]

此时应该选择 A 作为调度的进程,而在一轮调度后,队列将如下:

image

可以看到由于溢出的出现,进程间stride的理论比较和实际比较结果出现了偏差。我们首先在理论上分析这个问题:令PASS_MAX为当前所有进程里最大的步进值。则我们可以证明如下结论:对每次Stride调度器的调度步骤中,有其最大的步进值STRIDE_MAX和最小的步进值STRIDE_MIN之差:

STRIDE_MAX – STRIDE_MIN <= PASS_MAX

有了该结论,在加上之前对优先级有Priority > 1限制,我们有STRIDE_MAX – STRIDE_MIN <= BIG_STRIDE,于是我们只要将BigStride取在某个范围之内,即可保证对于任意两个 Stride 之差都会在机器整数表示的范围之内。而我们可以通过其与0的比较结构,来得到两个Stride的大小关系。在上例中,虽然在直接的数值表示上 98 < 65535,但是 98 - 65535 的结果用带符号的 16位整数表示的结果为99,与理论值之差相等。所以在这个意义下 98 > 65535。基于这种特殊考虑的比较方法,即便Stride有可能溢出,我们仍能够得到理论上的当前最小Stride,并做出正确的调度决定。

所以最后应该取 0x7FFFFFFF 即 (((uint32_t)-1) / 2)

#define BIG_STRIDE 0x7FFFFFFF /* you should give a value, and is ??? */

然后就是

stride调度算法是抢占式的。在stride_enqueue中,每当就绪队列入队时都会为其分配一定的时间片,当线程运行的过程中发生时钟中断时则会通过stride_proc_tick函数扣减对应的时间片。当为线程分配的时间片扣减为0时,则会将线程的need_resched设置为1。

在trap中断处理函数中,当对应中断号的处理例程返回时会单独的检查need_resched的值,当发现为1时,则会触发schedule函数进行一次强制的线程调度,从而令当前时间片扣减为0的线程得以让出CPU,使其它的就绪线程能得到执行的机会。这也是stride调度算法被称为抢占式调度算法的原因:无论当前执行的线程是否主动的让出cpu,在分配的时间片用完之后,操作系统将会强制的撤下当前线程,进行一次调度,通过如下就可以看出

void trap(struct trapframe *tf)
{
    // dispatch based on what type of trap occurred
    // used for previous projects
    if (current == NULL)
    {
        trap_dispatch(tf);
    }
    else
    {
        // keep a trapframe chain in stack
        struct trapframe *otf = current->tf;
        current->tf = tf;

        bool in_kernel = trap_in_kernel(tf);

        trap_dispatch(tf);

        current->tf = otf;
        if (!in_kernel)
        {
            if (current->flags & PF_EXITING)
            {
                do_exit(-E_KILLED);
            }
            if (current->need_resched)
            {
                schedule();
            }
        }
    }
}

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->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
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--;
}

static struct proc_struct *
stride_pick_next(struct run_queue *rq)
{
     if (rq->lab6_run_pool == NULL)
          return NULL;
     struct proc_struct *p = le2proc(rq->lab6_run_pool, lab6_run_pool);
     if(p->lab6_priority == 0){
          p->lab6_stride += BIG_STRIDE;
     }else{
          p->lab6_stride += BIG_STRIDE / p->lab6_priority;
     }
     return p;
}

static void
stride_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;
     }
}

整体理解算法不难,因为人家的斜堆已经给出了,不需要自己实现

斜堆结构实现的就绪队列其入队、出队操作能达到O(logn)的对数复杂度,比其双向链表实现的就绪队列入队、出队效率O(n)要高出一个数量级

你可能感兴趣的:(操作系统,c语言)