BUAA OS Lab3 Exam

日期:
標籤: BUAA-OS 1

題目描述

課下我們在 MOS 系統中實現了時間片輪轉演算法(Round-Robin,RR)用於程序排程。在本題中,我們將實現最早截止時間優先演算法(Earliest Deadline First,EDF),用於排程週期性程序。

題目要求

在本題中,你需要實現函式 env_create_edf 用於建立週期性程序,並返回指向被建立程序的程序控制塊的指標。該函式宣告如下:

struct Env *env_create_edf(const void *binary, size_t size, int runtime, int period);

其中,引數 binarysizeenv_create 函式中的定義相同,runtimeperiod 為 EDF 排程引數,以時間片為單位:

  • runtime 表示程序在每個週期中需要執行的時間
  • period 表示程序的執行週期

在本題中,我們將 MOS 系統兩次時鐘中斷之間的間隔定義為一個時間片,將 MOS 首次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 0 個時間片,再次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 1 個時間片, 以此類推。

對於一個執行週期為 period 的程序來說,其第 ( t ) 個週期(從 0 開始計數)的範圍為 MOS 的第 [period * t, period * (t + 1) ) 個時間片,相應地,程序的截止時間即為該週期結束時間。

對於每個使用 env_create_edf 建立的程序,你需要使用 EDF 演算法來可能保證程序在每個週期內執行 runtime 個時間片,但不一定連續,也不一定執行完(詳見排程規則和示例)。

排程規則

  1. 新增的 EDF 演算法與 MOS 原有的 RR 演算法擁有各自獨立的排程佇列。EDF 排程佇列中包含所有使用 env_create_edf 建立的程序,而 RR 排程佇列(MOS 原有的排程佇列)中包含所有使用 env_create 建立的程序。
  2. 當時鍾中斷產生時,若 EDF 排程佇列中存在尚未執行完當前週期時間片的程序,則選擇截止時間最早的程序排程。如果多個程序的截止時間相同,則選擇 env_id 最小的程序排程。
  3. 從 EDF 排程佇列選取程序後,僅需使其執行一個時間片,並在下個時鐘中斷產生時使用第二條規則,重新選擇程序排程。本題中要求實現的 EDF 排程演算法不受 yield 引數和優先順序的影響,只與程序的截止時間有關。
  4. 如果 EDF 排程佇列為空,或其中的所有程序均已執行完當前週期的時間片,則使用預設實現的 RR 演算法排程 RR 排程佇列中的程序。需要注意的是,EDF 排程的程序可以在任何時刻搶佔 RR 排程的程序,且 RR 排程的程序執行的時間不會被複用不會發生擠佔
    例如,如果 RR 演算法選擇排程一個優先順序為 5 的程序 A,A 已經使其執行了 3 個時間片,此時 EDF 佇列中產生了可以被排程的程序 B,則程序 B 會搶佔程序 A 的執行,直到程序 B 執行完當前週期的時間片後,程序 A 繼續執行剩餘的 2 個時間片。

    我被這條給坑了,以為是說 RR 演算法的程序在被 EDF 排程的程序切出去之後,resume 時還接著上次選擇的 RR 演算法的程序執行。

  5. 此外,如果在某個週期內,使用 EDF 演算法未能保證程序執行完 runtime 個時間片,則剩餘的時間片不會被保留至下週期,而是直接作廢

示例

程序按照下表的順序依次加入 RR 排程佇列和 EDF 排程佇列:

程序名優先順序每個週期內需要執行的時間片執行週期
A1--
B3--
C-15
D-27

注:由於課下程式碼中程序總是插入排程連結串列的頭部,因此 RR 排程佇列中的實際順序為 B、A。

  • 第 0 個時間片,程序C、D 均位於第 0 個執行週期,未執行完當前週期的時間片,程序 C 截止時間為 5,程序 D 截止時間為 7,選擇截止時間最早的程序 C 執行;
  • 第 1 個時間片,程序 C 已執行完當前週期的時間片,而程序 D 未執行完,選擇程序 D 執行;
  • 第 2 個時間片,程序 C 已執行完當前週期的時間片,而程序 D 未執行完,選擇程序 D 執行;
  • 第 3 個時間片,程序 C、D 已執行完當前週期的時間片,因此選擇 RR 排程佇列中的程序 B 執行三個時間片
  • 第 4 個時間片,程序 C、D 已執行完當前週期的時間片,因此繼續選擇程序 B 執行剩餘的兩個時間片;
  • 第 5 個時間片,程序 D 已執行完當前週期的時間片,而程序 C 進入第 1 個執行週期,未執行完當前週期的時間片,截止時間為 10,選擇程序 C 執行;
  • 第 6 個時間片,程序 C、D 已執行完當前週期的時間片,因此繼續選擇程序 B 執行剩餘的一個時間片;
  • 第 7 個時間片,程序 C 已執行完當前週期的時間片,而程序 D 進入第 1 個執行週期,未執行完當前週期的時間片,截止時間為 14,選擇程序 D 執行;
  • 第 8 個時間片,程序 C 已執行完當前週期的時間片,而程序 D 未執行完,選擇程序 D 執行;
  • 第 9 個時間片,程序 C、D 已執行完當前週期的時間片,RR 排程佇列中的程序 B 已執行完分配的時間片,被移動至排程佇列尾部,因此選擇程序 A 執行一個時間片;

參考實現思路

  1. include/env.h 中新增以下宣告:

    LIST_HEAD(Env_edf_sched_list, Env);
     
    extern struct Env_edf_sched_list env_edf_sched_list; // EDF 排程佇列
     
    struct Env *env_create_edf(const void *binary, size_t size, int runtime, int period);

    注:由於 EDF 演算法不需要在隊尾插入或取出的操作,因此我們推薦使用 LIST 結構實現 EDF 排程佇列。

  2. kern/env.c 中新增 env_edf_sched_list 的定義,並在 env_init 函式中初始化 env_edf_sched_list

    struct Env_edf_sched_list env_edf_sched_list; // EDF 排程佇列

注意我們只需要這一個,不用像作業一樣區分 env_sched_listenv_free_list

  1. include/env.hEnv 結構體中新增以下欄位:

    LIST_ENTRY(Env) env_edf_sched_link; // 構造 env_edf_sched_list 的連結串列項
    u_int env_edf_runtime;              // EDF 排程引數:程序在每個週期內需要執行的時間片
    u_int env_edf_period;               // EDF 排程引數:程序的執行週期
    u_int env_period_deadline;          // 程序當前週期的截止時間
    u_int env_runtime_left;             // 程序當前週期剩餘的時間片
  2. kern/env.c 中按照 env_create 實現 env_create_edf 函式,其中需要初始化程序控制塊的相關欄位,參考如下:

struct Env *env_create_edf(const void *binary, size_t size, int runtime, int period);
...
// diff --git a/kern/env.c b/kern/env.c
@@ -15,6 +15,9 @@ static struct Env_list env_free_list; // Free list
  // Invariant: 'env' in 'env_sched_list' iff. 'env->env_status' is 'RUNNABLE'.
  struct Env_sched_list env_sched_list; // Runnable list
 
+ // 注:由於 EDF 演算法不需要在隊尾插入或取出的操作,因此我們推薦使用 LIST 結構實現 EDF 排程佇列。
+ struct Env_edf_sched_list env_edf_sched_list; // EDF 排程佇列
+ 
  static Pde *base_pgdir;
  
  static uint32_t asid_bitmap[NASID / 32] = {0};
@@ -402,6 +405,31 @@ struct Env *env_create(const void *binary, size_t size, int priority) {
  	return e;
  }
 
+ struct Env *env_create_edf(
+     // 其中,引數 binary、size 與 env_create 函式中的定義相同
+     const void *binary, size_t size,
+     // runtime、period 為 EDF 排程引數,以時間片為單位:
+     // runtime 表示程序在每個週期內需要執行的時間
+     int runtime,
+     // period 表示程序的執行週期。
+     int period) {
+   struct Env *e;
+   /* ... */
+   env_alloc(&e, 0);
+ 
+   e->env_edf_runtime = runtime;
+   e->env_edf_period = period;
+   e->env_period_deadline = 0; // 初始化為 0,使程序在首次呼叫 schedule
+                               // 函式時觸發條件判斷,進入首個執行週期
+   e->env_status = ENV_RUNNABLE;
+ 
+   /* ... */
+ 	load_icode(e, binary, size);
+ 	LIST_INSERT_HEAD(&env_edf_sched_list, e, env_edf_sched_link);
+ 
+ 	return e;
+ }
+ 
  #include <pmap.h>
  #include <printk.h>
 
+ struct Env *last_rr_env;
 
  /* Overview:
   *   Implement a round-robin scheduling to select a runnable env and schedule it
   * using 'env_run'.
   * 'noreturn'.
   */
  void schedule(int yield) {
    static int count = 0; // remaining time slices of current env
-   struct Env *e = curenv;
 
    static int clock = -1; // 當前時間片,從 0 開始計數
+   clock++;
+   /* 在本題中,我們將 MOS 系統兩次時鐘中斷之間的間隔定義為一個時間片,
+    * 將 MOS 首次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 0 個時間片,
+    * 再次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 1 個時間片,以此類推。
+    */
+ 
+   /* (1) 遍歷 env_edf_sched_list,
+    * 如果程序進入了新的執行週期(可透過 clock == env_period_deadline 判斷),
+    * 則更新 env_period_deadline,
+    * 並將 env_runtime_left 設定為 env_edf_runtime。 */
+   /* 在此實現你的程式碼 */
+   struct Env *env; // 迴圈變數
+   LIST_FOREACH(env, &env_edf_sched_list, env_edf_sched_link) {
+     // 在這裡對 env 進行操作
+     // 此外,如果在某個週期內,
+     // 使用 EDF 演算法未能保證某程序執行完 runtime 個時間片,
+     // 則剩餘的時間片不會被保留至下一週期,而是直接作廢。
+     if (env->env_period_deadline == clock) {
+       env->env_period_deadline = clock + env->env_edf_period;
+       env->env_runtime_left = env->env_edf_runtime;
+     }
+   }
+ 
+   /* (2) 遍歷 env_edf_sched_list,
+    * 選取 env_runtime_left 大於 0 且 env_period_deadline 最小的程序排程
+    * (若相同,則選擇 env_id 最小的程序)。
+    * 如果不存在這樣的程序,則不進行排程。 */
+   /* 在此實現你的程式碼 */
+   u_int min_deadline = 11451419;
+   u_int curr_deadline;
+   struct Env *selected_env = NULL;
+   LIST_FOREACH(env, &env_edf_sched_list, env_edf_sched_link) {
+     // 在這裡對 env 進行操作
+     if (env->env_runtime_left > 0) {
+       curr_deadline = env->env_period_deadline;
+       if (curr_deadline < min_deadline) {
+         selected_env = env;
+       } else if (curr_deadline == min_deadline) {
+         // then selected_env is not NULL
+         if (selected_env->env_id > env->env_id)
+           selected_env = env;
+       }
+     }
+   }
+   /* printk("min_deadline %u\n", min_deadline); */
 
+   struct Env *e = last_rr_env;
+   struct Env *e_to_run;
+   // 請根據提示修改這行程式碼
+   /* We always decrease the 'count' by 1.
+    *
+    * If 'yield' is set, or 'count' has been decreased to 0, or 'e' (previous
+    * You may want to use macros below:
+    *   'TAILQ_FIRST', 'TAILQ_REMOVE', 'TAILQ_INSERT_TAIL'
+    */
+   /* 請將 Exercise 3.12 中的程式碼貼上至此 */
+   // 如果 EDF 排程佇列為空,或其中的所有程序均已執行完當前週期的時間片,
+   // 則使用課下實現的 RR 演算法排程 RR 排程佇列中的程序。
+   // 需要注意的是,EDF 排程的程序可以在任何時刻搶佔 RR 排程的程序,
+   // 且 RR 排程的程序執行的時間片在被 EDF 搶佔後不發生變化。
+   // 例如,如果 RR 演算法選擇排程一個優先順序為 5 的程序 A,
+   // 並已經使其執行了 3 個時間片,此時 EDF 佇列中產生了
+   // 可以被排程的程序 B,則程序 B 會搶佔程序 A 的執行,
+   // 直至程序 B 執行完當前週期的時間片後,程序 A 繼續執行剩餘的 2 個時間片。
+   if (selected_env != NULL) {
+     /* printk("b1: selected! \n"); */
+     /* printk("selected_env %u\n", selected_env->env_id); */
+     // 從 EDF 排程佇列選取程序後,你僅需使其執行一個時間片,
+     // 並在下個時鐘中斷產生時使用第二條規則,重新選擇程序排程。
+     // 本題中要實現的 EDF 排程演算法不受 yield 引數和程序優先順序的影響,
+     // 只與程序的截止時間有關。
+     e_to_run = selected_env;
+     selected_env->env_runtime_left -= 1;
+   }
    /* Exercise 3.12: Your code here. */
    // 判斷是否需要切換程序:
+   else {
     /* if (e != NULL) { */
     /* printk("b2: last rr is not NULL! \n"); */
     /* e_to_run = e; */
     /* count--; */
     /* ran_last = 1; */
     if (yield != 0 ||
         //     顯式請求讓出
         count == 0 ||
         //     時間片耗盡、
         e == NULL ||
         //     歷史沒有程序執行過、
         e->env_status != ENV_RUNNABLE
         //     當前程序已經不處於就緒狀態
     ) {
       /* printk("b3/n"); */
       // (4)如果當前程序 e 仍然“就緒”,則把它從佇列頭移到佇列尾,實現“輪轉”
       if (e != NULL && e->env_status == ENV_RUNNABLE) {
         TAILQ_REMOVE(&env_sched_list, e, env_sched_link);
         TAILQ_INSERT_TAIL(&env_sched_list, e, env_sched_link);
       }
       // (5)從排程佇列頭取下一個就緒程序
       e = TAILQ_FIRST(&env_sched_list);
       // (6)如果佇列為空,則沒有可執行的程序,直接 panic
       if (e == NULL) {
         panic("schedule: no runnable envs\n");
       }
       // (7)將新選中程序的時間片數初始化為其優先順序值
       count = e->env_pri;
       last_rr_env = e;
     }
     // (5)從排程佇列頭取下一個就緒程序
     e = TAILQ_FIRST(&env_sched_list);
     // (6)如果佇列為空,則沒有可執行的程序,直接 panic
     if (e == NULL) {
       panic("schedule: no runnable envs\n");
     }
     // (7)將新選中程序的時間片數初始化為其優先順序值
+    count = e->env_pri;
+    count--;
+    e_to_run = e;
   }
   // (8)消費一個時間片
   count--;
 
   // (9)真正切換執行到程序 e(這個呼叫不會返回)
-   env_run(e);
+   env_run(e_to_run);
  }

評論

評論將在稽覈後顯示,閣下可以在本部落格的 Github 倉庫的 拉取請求列表 中檢視。提交成功後會自動跳轉。

本站不支持 Dark Reader 的暗色模式,请对本站关闭后再访问,亮色模式的对比度、亮度等选项不受影响。部分页面右上角提供暗色模式切换按钮,如果你没看到,说明你的浏览器尚不支持此特性。本提示不依赖于 JavaScript,你可自行查找其他用户在本站发表的关于如何关闭此提示的评论。