操作系统ucore lab6实验报告

操作系统lab6实验报告

实验五完成了用户进程的管理,可在用户态运行多个进程。可是目前的进程调度策略是FIFO策略,而本实验则会实现Stride Scheduling调度算法。

练习0:填写已有实验

同样使用一款名为meld的软件进行对比即可,大致截图如下:

现在将需要修改的文件罗列如下:

proc.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
trap.c

然后是一些需要简单修改的部分,根据注释的提示,主要是一下两个函数需要额外加以修改。

alloc_proc函数

这里alloc_proc还需要修改一下,完整的代码如下:

static struct proc_struct *
alloc_proc(void) {
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
        proc->state = PROC_UNINIT;
        proc->pid = -1;
        proc->runs = 0;
        proc->kstack = 0;
        proc->need_resched = 0;
        proc->parent = NULL;
        proc->mm = NULL;
        memset(&(proc->context), 0, sizeof(struct context));
        proc->tf = NULL;
        proc->cr3 = boot_cr3;
        proc->flags = 0;
        memset(proc->name, 0, PROC_NAME_LEN);
        proc->wait_state = 0;
        proc->cptr = proc->optr = proc->yptr = NULL;
        proc->rq = NULL;
        list_init(&(proc->run_link));
        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 = 0;
    }
    return proc;
}

相比于lab5,lab6对proc_struct结构体再次做了扩展,这里主要是多出了以下部分

    proc->rq = NULL;//初始化运行队列为空
    list_init(&(proc->run_link));//初始化运行队列的指针
    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 = 0;//初始化优先级

具体的解释见注释。

trap_dispatch函数

这里在时钟产生的地方需要对定时器做初始化,修改的部分如下:

static void
trap_dispatch(struct trapframe *tf) {
    ......
    ......
    ticks ++;
    assert(current != NULL);
    run_timer_list(); //更新定时器,并根据参数调用调度算法
    break;
    ......
    ......
}

练习1 使用Round Robin调度算法

Round Robin调度算法的调度思想是让所有 runnable 态的进程分时轮流使用 CPU 时间。Round Robin 调度器维护当前 runnable进程的有序运行队列。当前进程的时间片用完之后,调度器将当前进程放置到运行队列的尾部,再从其头部取出进程进行调度。

在这个理解的基础上,我们来分析算法的具体实现。

这里Round Robin调度算法的主要实现在default_sched.c之中,源码如下:

static void
RR_init(struct run_queue *rq) {
    list_init(&(rq->run_list));
    rq->proc_num = 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 --;
}

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

struct sched_class default_sched_class = {
    .name = "RR_scheduler",
    .init = RR_init,
    .enqueue = RR_enqueue,
    .dequeue = RR_dequeue,
    .pick_next = RR_pick_next,
    .proc_tick = RR_proc_tick,
};

现在我们来逐个函数的分析,从而了解Round Robin调度算法的原理。

首先是RR_init函数,函数比较简单,不再罗列,完成了对进程队列的初始化。

然后是RR_enqueue函数,

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

看代码,首先,它把进程的进程控制块指针放入到rq队列末尾,且如果进程控制块的时间片为0,则需要把它重置为max_time_slice。这表示如果进程在当前的执行时间片已经用完,需要等到下一次有机会运行时,才能再执行一段时间。然后在依次调整rq和rq的进程数目加一。

然后是RR_dequeue函数

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

即简单的把就绪进程队列rq的进程控制块指针的队列元素删除,然后使就绪进程个数的proc_num减一。

接下来是RR_pick_next函数。

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

选取函数,即选取就绪进程队列rq中的队头队列元素,并把队列元素转换成进程控制块指针,即置为当前占用CPU的程序。

最后是

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

观察代码,即每一次时间片到时的时候,当前执行进程的时间片time_slice便减一。如果time_slice降到零,则设置此进程成员变量need_resched标识为1,这样在下一次中断来后执行trap函数时,会由于当前进程程成员变量need_resched标识为1而执行schedule函数,从而把当前执行进程放回就绪队列末尾,而从就绪队列头取出在就绪队列上等待时间最久的那个就绪进程执行。

之后是一个对象化,提供一个类的实现,不是重点,这里不做赘述。

练习2 实现Stride Scheduling调度算法

首先,根据实验指导书的要求,先用default_sched_stride_c覆盖default_sched.c,即覆盖掉Round Robin调度算法的实现。

覆盖掉之后需要在该框架上实现Stride Scheduling调度算法。

关于Stride Scheduling调度算法,经过查阅资料和实验指导书,我们可以简单的把思想归结如下:

  • 1、为每个runnable的进程设置一个当前状态stride,表示该进程当前的调度权。另外定义其对应的pass值,表示对应进程在调度后,stride 需要进行的累加值。
  • 2、每次需要调度时,从当前 runnable 态的进程中选择 stride最小的进程调度。对于获得调度的进程P,将对应的stride加上其对应的步长pass(只与进程的优先权有关系)。
  • 3、在一段固定的时间之后,回到步骤2,重新调度当前stride最小的进程

参照实验指导书所给的伪代码能够更好的理解该调度算法:

接下来针对代码我们逐步分析,首先完整代码如下:

#include <defs.h>
#include <list.h>
#include <proc.h>
#include <assert.h>
#include <default_sched.h>

#define USE_SKEW_HEAP 1
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;
}

static void
stride_init(struct run_queue *rq) {
     /* LAB6: YOUR CODE */
     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) {
     /* LAB6: YOUR CODE */
#if USE_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;
     }
     proc->rq = rq;
     rq->proc_num ++;
}

static void
stride_dequeue(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE */
#if USE_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));
#endif
     rq->proc_num --;
}

static struct proc_struct *
stride_pick_next(struct run_queue *rq) {
     /* LAB6: YOUR CODE */
#if USE_SKEW_HEAP
     if (rq->lab6_run_pool == NULL) return NULL;
     struct proc_struct *p = le2proc(rq->lab6_run_pool, lab6_run_pool);
#else
     list_entry_t *le = list_next(&(rq->run_list));

     if (le == &rq->run_list)
          return NULL;

     struct proc_struct *p = le2proc(le, run_link);
     le = list_next(le);
     while (le != &rq->run_list)
     {
          struct proc_struct *q = le2proc(le, run_link);
          if ((int32_t)(p->lab6_stride - q->lab6_stride) > 0)
               p = q;
          le = list_next(le);
     }
#endif
     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) {
     /* LAB6: YOUR CODE */
     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,
};

首先是初始化函数stride_init

开始初始化运行队列,并初始化当前的运行队,然后设置当前运行队列内进程数目为0。

static void
stride_init(struct run_queue *rq) {
     /* LAB6: YOUR CODE */
     list_init(&(rq->run_list));
     rq->lab6_run_pool = NULL;
     rq->proc_num = 0;
}  

然后是入队函数stride_enqueue,根据之前对该调度算法的分析,这里函数主要是初始化刚进入运行队列的进程 proc 的stride属性,然后比较队头元素与当前进程的步数大小,选择步数最小的运行,即将其插入放入运行队列中去,这里并未放置在队列头部。最后初始化时间片,然后将运行队列进程数目加一。

static void
stride_enqueue(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE */
#if USE_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;
     }
     proc->rq = rq;
     rq->proc_num ++;
}

然后是出队函数stride_dequeue,即完成将一个进程从队列中移除的功能,这里使用了优先队列。最后运行队列数目减一。

static void
stride_dequeue(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE */
#if USE_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));
#endif
     rq->proc_num --;
}

接下来就是进程的调度函数stride_pick_next,观察代码,它的核心是先扫描整个运行队列,返回其中stride值最小的对应进程,然后更新对应进程的stride值,将步长设置为优先级的倒数,如果为0则设置为最大的步长。

static struct proc_struct *
stride_pick_next(struct run_queue *rq) {
     /* LAB6: YOUR CODE */
#if USE_SKEW_HEAP
     if (rq->lab6_run_pool == NULL) return NULL;
     struct proc_struct *p = le2proc(rq->lab6_run_pool, lab6_run_pool);
#else
     list_entry_t *le = list_next(&(rq->run_list));

     if (le == &rq->run_list)
          return NULL;

     struct proc_struct *p = le2proc(le, run_link);
     le = list_next(le);
     while (le != &rq->run_list)
     {
          struct proc_struct *q = le2proc(le, run_link);
          if ((int32_t)(p->lab6_stride - q->lab6_stride) > 0)
               p = q;
          le = list_next(le);
     }
#endif
    //更新对应进程的stride值
     if (p->lab6_priority == 0)
          p->lab6_stride += BIG_STRIDE;
     else p->lab6_stride += BIG_STRIDE / p->lab6_priority;
     return p;
}

最后是时间片函数stride_proc_tick,主要工作是检测当前进程是否已用完分配的时间片。如果时间片用完,应该正确设置进程结构的相关标记来引起进程切换。这里和之前实现的Round Robin调度算法一样,所以不赘述。

还有一个优先队列的比较函数proc_stride_comp_f的实现,主要思路就是通过步数相减,然后根据其正负比较大小关系。

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

另外还有就是一个封装类的实现,也不详细解释了。

实验结果

在lab6的根目录下运行make qemu,得到如下结果:

观察可知,实验成功。

时间: 2024-10-10 18:21:55

操作系统ucore lab6实验报告的相关文章

操作系统ucore lab7实验报告

操作系统lab7实验报告 lab6完成了用户进程的调度框架和调度算法的具体实现,即到lab6位置,ucore系统已经可以同事调度运行多个程序.但是这又引来了一个新的问题,那就是当多个同时运行的进程要协同操作或是访问共享内存的时候,如何解决同步和有序竞争的问题. 本次实验的主要就是解决进程的同步问题 练习0:填写已有实验 同样使用一款名为meld的软件进行对比即可,大致截图如下: 这里把需要填充的文件罗列如下: proc.c default_pmm.c pmm.c swap_fifo.c vmm.

操作系统ucore lab1实验报告

操作系统lab1实验报告 [练习1] 理解通过 make 生成执行文件的过程.(要求在报告中写出对下述问题的回答) 在此练习中,大家需要通过阅读代码来了解: 1. 操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中 每一条相关命令和命令参数的含义,以及说明命令导致的结果) 2. 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么? [练习1.1] 1.生成ucore.img需要kernel和bootblock 生成ucore.img的代码如下

操作系统ucore lab2实验报告

操作系统lab2实验报告 实验二主要是完成Ucore操作系统的物理内存管理. 主要包括了解如何建立对物理内存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解. 练习0:填写已有实验 lab2会依赖lab1,需要把做的lab1的代码填到lab2中缺失的位置上面.这道题就是一个工具的利用.这里我使用的是linux下的一个叫做meld的工具.如下图: 直接将两个文件夹拖入,然后点击compare就行了

操作系统ucore lab4实验报告

操作系统lab4实验报告 本次实验将接触的是内核线程的管理.内核线程是一种特殊的进程,内核线程与用户进程的区别有两个:内核线程只运行在内核态而用户进程会在在用户态和内核态交替运行:所有内核线程直接使用共同的ucore内核内存空间,不需为每个内核线程维护单独的内存空间而用户进程需要维护各自的用户内存空间. 在本次实验完成之后,为了加深理解,我这里简单将之前的所有代码又重新阅读并梳理了一遍,简单作了下总结. 这里主要是从kern_init函数的物理内存管理初始化开始的,截图如下: 按照函数的次序我进

操作系统ucore lab8实验报告

操作系统lab8实验报告 本次实验涉及的是文件系统,通过分析了解 ucore 文件系统的总体架构设计,完善读写文件操作,从新实现基于文件系统的执行程序机制(即改写do_execve),从而可以完成执行存储在磁盘上的文件和实现文件读写等功能.可以看到,在kern_init函数中,多了一个fs_init 函数的调用.fs_init 函数就是文件系统初始化的总控函数,它进一步调用了虚拟文件系统初始化函数 vfs_init,与文件相关的设备初始化函数 dev_init 和 Simple FS 文件系统的

操作系统ucore lab5实验报告

操作系统lab5实验报告 到实验四为止,ucore还一直在核心态"打转",没有到用户态执行.创建用户进程,让用户进程在用户态执行,且在需要ucore支持时,可通过系统调用来让ucore提供服务.而本实验将进程的执行空间扩展到了用户态空间,出现了创建子进程执行应用程序等.即实验五主要是分析用户进程的整个生命周期来阐述用户进程管理的设计与实现. 练习0 填写已有实验 这里和前几个实验一样,照样运用meld软件进行对比,大致的截图如下: 这里简单将我们需要修改的地方罗列如下: proc.c

《ucore lab5》实验报告

资源 ucore在线实验指导书 我的ucore实验代码 练习1: 加载应用程序并执行(需要编码) 题目 do_execv函数调用load_icode(位于kern/process/proc.c中) 来加载并解析一个处于内存中的ELF执行文件格式的应用程序,建立相应的用户内存空间来放置应用程序的代码段.数据段等,且要设置好proc_struct结构中的成员变量trapframe中的内容,确保在执行此进程后,能够从应用程序设定的起始执行地址开始执行.需设置正确的trapframe内容. 请在实验报告

操作系统实验报告二

  操作系统实验报告二 姓名:许恺 学号:2014011329 日期:10月14日 题目1:编写线程池 关键代码如下: 1.Thread.h #pragma once #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #include <pthread.h> #pragma comment(lib,"x86/pthreadVC2.lib") using

[操作系统实验lab4]实验报告

昨天跟老师建议了OS实验改革的事情,感觉助教老师给的指导书挺坑哈,代码注释也不全.我也算沦落到看别人家的源码了... 我参考的源码注释是:https://github.com/benwei/MIT-JOS/ 这个源码质量暂且不评价,但这个注释质量真心不错!!!良心注释啊!!! 本不想去找源码注释啥来看的,毕竟可能一不小心就抄袭了源码的思想?不知道HT和WLM是怎么写的,他们做的都好快啊=.=难道只有我一个人做OS实验的周期是1~2周吗... 哎,不吐槽了,这篇文章留着慢慢更,不着急.感觉在鸣神的