作業系統實驗六實驗報告

jasonyuchen發表於2017-08-11

實驗六:排程器


練習0:填寫已有實驗

使用meld可以簡單地將前幾個lab的程式碼填入lab6中,但是要注意在這次實驗中,部分程式碼需要做出修改,如下,主要是trap_dispatchalloc_proc這兩個函式

  • kern/trap/trap.c中lab5的部分程式碼
/* LAB6 YOUR CODE */
/* you should upate you lab5 code
 * IMPORTANT FUNCTIONS:
 * sched_class_proc_tick
 */
ticks++;
assert(current != NULL);
sched_class_proc_tick(current);     //注意,需要到kern/schedule/sched.c中將sched_class_proc_tick前的static去掉,否則會出現未定義錯誤
break;
  • kern/process/proc.c中lab5的部分程式碼
//LAB6 YOUR CODE : (update LAB5 steps)
/*
 * below fields(add in LAB6) in proc_struct need to be initialized
 *     struct run_queue *rq;                       // running queue contains Process
 *     list_entry_t run_link;                      // the entry linked in run queue
 *     int time_slice;                             // time slice for occupying the CPU
 *     skew_heap_entry_t lab6_run_pool;            // FOR LAB6 ONLY: the entry in the run pool
 *     uint32_t lab6_stride;                       // FOR LAB6 ONLY: the current stride of the process
 *     uint32_t lab6_priority;                     // FOR LAB6 ONLY: the priority of process, set by lab6_set_priority(uint32_t)
 */
proc->rq = NULL;                //初始化執行佇列為空
list_init(&(proc->run_link));   //初始化執行佇列指標
proc->time_slice = 0;           //初始化時間片為0
proc->lab6_run_pool.parent = proc->lab6_run_pool.left = proc->lab6_run_pool.right = NULL;   //初始化優先佇列的相關指標為空
proc->lab6_stride = 0;          //初始化步數為0
proc->lab6_priority = 0;        //初始化優先順序為0(優先順序越大越優先)

練習1:使用Round Robin排程演算法

Round Robin演算法的基本原理是讓所有執行狀態的程式分時輪流使用處理機。當前程式的時間片用完之後,排程器將當前程式放置到執行佇列的尾部,再從其頭部取出程式進行排程。程式控制塊中增加了一個成員變數proc->time_slice,用來記錄程式當前的可執行時間片段。在每個時鐘中斷到時的時候,作業系統會遞減當前執行程式的時間片,當時間片為0時,就意味著這個程式需要把CPU讓給其他程式執行,於是作業系統就需要讓此程式重新回到執行佇列的尾部,且重置此程式的時間片為就緒佇列的成員變數最大時間片值,然後再從佇列頭部取出一個新的程式執行。
Round Robin排程演算法的實現包含在default_sched.c中,原始碼及解釋如下

static void
RR_init(struct run_queue *rq) {
    list_init(&(rq->run_list));                            //初始化執行佇列
    rq->proc_num = 0;                                      //執行佇列中程式個數初始化為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 ++;                                       //執行佇列程式數加1
}

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 --;                                       //執行佇列程式數減1
}

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 --;                               //每一個時鐘中斷程式的時間片減1
    }
    if (proc->time_slice == 0) {
        proc->need_resched = 1;                            //當程式時間片用完時進行程式切換
    }
}

struct sched_class default_sched_class = {                 //將RR排程演算法包裝成統一的介面供ucore使用
    .name = "RR_scheduler",
    .init = RR_init,
    .enqueue = RR_enqueue,
    .dequeue = RR_dequeue,
    .pick_next = RR_pick_next,
    .proc_tick = RR_proc_tick,
};

ucore系統通過定義在kern/schedule/sched.c中的schedule函式真正對程式進行排程,原始碼及解釋如下

void
schedule(void) {
    bool intr_flag;                                        //對全域性變數操作,加鎖
    struct proc_struct *next;
    local_intr_save(intr_flag);
    {
        current->need_resched = 0;                         //將當前程式設定為不需要排程
        if (current->state == PROC_RUNNABLE) {             //若當前程式依然是執行狀態,則加入到執行佇列中
            sched_class_enqueue(current);
        }
        if ((next = sched_class_pick_next()) != NULL) {    //若執行佇列中還有其他程式,則根據演算法挑選出某一個出隊
            sched_class_dequeue(next);
        }
        if (next == NULL) {                                //若沒有其他執行程式,則設定為idleproc程式
            next = idleproc;
        }
        next->runs ++;                                     //下一個執行的程式執行次數加1
        if (next != current) {
            proc_run(next);                                //執行下一個程式
        }
    }
    local_intr_restore(intr_flag);
}

根據理論課內容,多級反饋佇列演算法MLFQ的基本思想如下:

  • 多個執行佇列,程式首先進入最高優先順序的佇列,並可在佇列間移動
  • 不同佇列的時間片大小不同,時間片大小隨優先順序別增加而減小
  • 若程式在當前時間片下沒有處理完成,則下降到低一級的佇列
  • 高一級的佇列沒有程式時才會排程低一級佇列的程式,並且高一級佇列有程式時可以搶佔低一級的程式

這裡寫圖片描述

圖中只是一個最簡單的示意圖,實際有更復雜的實現,多級反饋佇列的每一個佇列都可以有自己的演算法(FCFS、RR、etc),時間片也可以設定為各種不同的值,參考《作業系統概念》,多級反饋佇列的排程程式可由下述引數來定義:

  • 佇列數量
  • 每個佇列的排程演算法
  • 用以確定何時升級到更高優先順序佇列的方法
  • 用以確定何時降級到更低優先順序佇列的方法
  • 用以確定程式在需要服務時應進入哪個佇列的方法

這裡不給出多級反饋佇列的詳細實現,僅提供一個概念和需要考慮的一些因素


練習2:實現Stride Scheduling排程演算法

Stride Scheduling排程演算法參考下文

Stride Scheduling: Deterministic Proportional-Share Resource Management

這裡簡單介紹一下ucore中將要實現的Stride Scheduling排程演算法,基於這個演算法將給每個程式控制塊新增兩個關鍵成員變數stridepriority,前者表示這個程式的排程權,後者表示這個程式的優先順序
演算法的基本思想如下:

  • 定義一個足夠大的值BIG_STRIDE = 0x7FFFFFFF(為什麼是0x7FFFFFFF將在文末給出解釋),當一個程式排程後將在其排程權上加上一個步長即stride += BIG_STRIDE / priority(若優先順序為0則直接加上BIG_STRIDE)
  • 每次需要排程時,從佇列中選取排程權stride最小的程式進行排程

可以證明由於每次選擇stride最小的排程,且排程後加上BIG_STRIDE / priority,則優先順序越高單次增加越小,被排程的次數也越多,即該演算法為每個程式分配的時間與其優先順序成正比。

參考Round Robin的介面實現,Stride Scheduling的實現及解釋如下,設計優先佇列的操作可以參考libs/skew_heap.h

#define USE_SKEW_HEAP 1             //通過這個巨集來切換實現Stride Scheduling演算法的資料結構(斜堆(優先佇列)1、連結串列0)

/* You should define the BigStride constant here*/
/* LAB6: YOUR CODE */
#define BIG_STRIDE 0x7FFFFFFF   /*you should give a value, and is ??? */
                                    //0x7FFFFFFF = 0111 1111 ... 1111 最大的正數
/* The compare function for two skew_heap_node_t's and the
 * corresponding procs*/
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;       //排程權(型別是無符號數uint32_t)相減並按有符號數進行比較,原理將在文末給出解釋
     if (c > 0) return 1;
     else if (c == 0) return 0;
     else return -1;
}

/*
 * stride_init initializes the run-queue rq with correct assignment for
 * member variables, including:
 *
 *   - run_list: should be a empty list after initialization.
 *   - lab6_run_pool: NULL
 *   - proc_num: 0
 *   - max_time_slice: no need here, the variable would be assigned by the caller.
 *
 * hint: see libs/list.h for routines of the list structures.
 */
static void
stride_init(struct run_queue *rq) {
     /* LAB6: YOUR CODE 
      * (1) init the ready process list: rq->run_list
      * (2) init the run pool: rq->lab6_run_pool
      * (3) set number of process: rq->proc_num to 0       
      */
    list_init(&(rq->run_list));     //初始化執行佇列
    rq->lab6_run_pool = NULL;       //優先佇列指標初始化為NULL
    rq->proc_num = 0;               //執行佇列中程式數初始化為0

}

/*
 * stride_enqueue inserts the process ``proc'' into the run-queue
 * ``rq''. The procedure should verify/initialize the relevant members
 * of ``proc'', and then put the ``lab6_run_pool'' node into the
 * queue(since we use priority queue here). The procedure should also
 * update the meta date in ``rq'' structure.
 *
 * proc->time_slice denotes the time slices allocation for the
 * process, which should set to rq->max_time_slice.
 * 
 * hint: see libs/skew_heap.h for routines of the priority
 * queue structures.
 */
static void
stride_enqueue(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE 
      * (1) insert the proc into rq correctly
      * NOTICE: you can use skew_heap or list. Important functions
      *         skew_heap_insert: insert a entry into skew_heap
      *         list_add_before: insert  a entry into the last of list   
      * (2) recalculate proc->time_slice
      * (3) set proc->rq pointer to rq
      * (4) increase rq->proc_num
      */
#if USE_SKEW_HEAP
    rq->lab6_run_pool = skew_heap_insert(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f);     //插入優先佇列
#else                                            
//下面與RR的實現相同
    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 removes the process ``proc'' from the run-queue
 * ``rq'', the operation would be finished by the skew_heap_remove
 * operations. Remember to update the ``rq'' structure.
 *
 * hint: see libs/skew_heap.h for routines of the priority
 * queue structures.
 */
static void
stride_dequeue(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE 
      * (1) remove the proc from rq correctly
      * NOTICE: you can use skew_heap or list. Important functions
      *         skew_heap_remove: remove a entry from skew_heap
      *         list_del_init: remove a entry from the  list
      */
#if USE_SKEW_HEAP
    rq->lab6_run_pool = skew_heap_remove(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f);    //從優先佇列中移除
#else                                            
//下面與RR的實現相同
    assert(!list_empty(&(proc->run_link)) && proc->rq == rq);
    list_del_init(&(proc->run_link));
#endif
    rq->proc_num --;
}
/*
 * stride_pick_next pick the element from the ``run-queue'', with the
 * minimum value of stride, and returns the corresponding process
 * pointer. The process pointer would be calculated by macro le2proc,
 * see kern/process/proc.h for definition. Return NULL if
 * there is no process in the queue.
 *
 * When one proc structure is selected, remember to update the stride
 * property of the proc. (stride += BIG_STRIDE / priority)
 *
 * hint: see libs/skew_heap.h for routines of the priority
 * queue structures.
 */
static struct proc_struct *
stride_pick_next(struct run_queue *rq) {
     /* LAB6: YOUR CODE 
      * (1) get a  proc_struct pointer p  with the minimum value of stride
             (1.1) If using skew_heap, we can use le2proc get the p from rq->lab6_run_poll
             (1.2) If using list, we have to search list to find the p with minimum stride value
      * (2) update p;s stride value: p->lab6_stride
      * (3) return p
      */
#if USE_SKEW_HEAP      
//優先佇列的頭結點就是排程權最小的程式,直接取出並呼叫le2proc獲得相應的程式
    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);
    while((le = list_next(le)) != &(rq->run_list)){
        struct proc_struct *tmp = le2proc(le, run_link);
        if((int32_t)(tmp->lab6_stride - p->lab6_stride) < 0){
            p = tmp;
        }
    }
#endif
    if(p->lab6_priority == 0){
        p->lab6_stride += BIG_STRIDE;   //注意由於優先順序初始化為0,因此要考慮程式優先順序為0的情況,否則會出現divide error
    }
    else{
        p->lab6_stride += BIG_STRIDE / p->lab6_priority;
    }
    return p;
}

/*
 * stride_proc_tick works with the tick event of current process. You
 * should check whether the time slices for current process is
 * exhausted and update the proc struct ``proc''. proc->time_slice
 * denotes the time slices left for current
 * process. proc->need_resched is the flag variable for process
 * switching.
 */
static void
stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) {
     /* LAB6: YOUR CODE */             
    //對時間片的處理與RR的實現一樣
    if (proc->time_slice > 0) {
        proc->time_slice --;
    }
    if (proc->time_slice == 0) {
        proc->need_resched = 1;
    }
}
//封裝的實現,參考RR
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,
};

下面解釋一下如何對兩個程式的排程權進行比較proc_stride_comp_fBIG_STRIDE = 0x7FFFFFFF的原因:
從定義上可以知道,排程權被定義為32位無符號數,而隨著每次排程後增加BIG_STRIDE / priority最終會導致溢位,正確獲得兩個程式排程權的真實大小很關鍵,利用函式proc_stride_comp_f實現,本質上是直接對兩個32位無符號的排程權求差,並化為32位有符號數與0進行比較,來確定兩個程式排程權的大小

int32_t c = p->lab6_stride - q->lab6_stride;
if (c > 0) return 1;       //p->lab6_stride > q->lab6_stride
else if (c == 0) return 0; //p->lab6_stride == q->lab6_stride
else return -1;            //p->lab6_stride < q->lab6_stride

假設存在兩個32位無符號數a,b分別表示排程權,初始時a=b,定義步長s = BIG_STRIDE / priority <=S(S為最大步進)
(1)排程b後b+s=B,若B未溢位,且a-B=a-(b+s)=-s<0而不會下溢有符號數,則s<2^31,此時a-B<0成立
(2)排程b後b+s=B,若B溢位,此時B溢位意味著b+s>2^32,B=b+s-2^32,由(1)可知s<2^31那麼必有a=b>2^31,為了a-B<0即a-B的值上溢有符號數,需要使a-(b+s-2^32)>=2^31則s<=2^31

綜上可見s<2^31即最大步進S的精確值為S=2^31-1=0x7FFFFFFF


總結

完成後可以修改#define USE_SKEW_HEAP的值後再呼叫make grade可以獲得如下輸出,說明利用優先佇列和連結串列的實驗均成功

//USE_SKEW_HEAP = 0
badsegment:              (3.4s)
  -check result:                             OK
  -check output:                             OK
divzero:                 (1.6s)
  -check result:                             OK
  -check output:                             OK
softint:                 (1.6s)
  -check result:                             OK
  -check output:                             OK
faultread:               (1.6s)
  -check result:                             OK
  -check output:                             OK
faultreadkernel:         (1.6s)
  -check result:                             OK
  -check output:                             OK
hello:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
testbss:                 (1.7s)
  -check result:                             OK
  -check output:                             OK
pgdir:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
yield:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
badarg:                  (1.6s)
  -check result:                             OK
  -check output:                             OK
exit:                    (1.6s)
  -check result:                             OK
  -check output:                             OK
spin:                    (1.8s)
  -check result:                             OK
  -check output:                             OK
waitkill:                (2.3s)
  -check result:                             OK
  -check output:                             OK
forktest:                (1.7s)
  -check result:                             OK
  -check output:                             OK
forktree:                (1.7s)
  -check result:                             OK
  -check output:                             OK
matrix:                  (10.8s)
  -check result:                             OK
  -check output:                             OK
priority:                (11.6s)
  -check result:                             OK
  -check output:                             OK
Total Score: 170/170

//USE_SKEW_HEAP = 1
badsegment:              (3.3s)
  -check result:                             OK
  -check output:                             OK
divzero:                 (1.6s)
  -check result:                             OK
  -check output:                             OK
softint:                 (1.6s)
  -check result:                             OK
  -check output:                             OK
faultread:               (1.6s)
  -check result:                             OK
  -check output:                             OK
faultreadkernel:         (1.6s)
  -check result:                             OK
  -check output:                             OK
hello:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
testbss:                 (1.7s)
  -check result:                             OK
  -check output:                             OK
pgdir:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
yield:                   (1.6s)
  -check result:                             OK
  -check output:                             OK
badarg:                  (1.6s)
  -check result:                             OK
  -check output:                             OK
exit:                    (1.6s)
  -check result:                             OK
  -check output:                             OK
spin:                    (1.8s)
  -check result:                             OK
  -check output:                             OK
waitkill:                (2.1s)
  -check result:                             OK
  -check output:                             OK
forktest:                (1.6s)
  -check result:                             OK
  -check output:                             OK
forktree:                (1.6s)
  -check result:                             OK
  -check output:                             OK
matrix:                  (10.5s)
  -check result:                             OK
  -check output:                             OK
priority:                (11.7s)
  -check result:                             OK
  -check output:                             OK
Total Score: 170/170

相關文章