題目描述
課下我們在 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);其中,引數 binary、size 與 env_create 函式中的定義相同,runtime、period 為 EDF 排程引數,以時間片為單位:
runtime表示程序在每個週期中需要執行的時間period表示程序的執行週期
在本題中,我們將 MOS 系統兩次時鐘中斷之間的間隔定義為一個時間片,將 MOS 首次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 0 個時間片,再次呼叫 schedule 函式直至下次時鐘中斷前的間隔作為第 1 個時間片, 以此類推。
對於一個執行週期為 period 的程序來說,其第 ( t ) 個週期(從 0 開始計數)的範圍為 MOS 的第
[period * t, period * (t + 1) )
個時間片,相應地,程序的截止時間即為該週期結束時間。
對於每個使用 env_create_edf 建立的程序,你需要使用 EDF 演算法來可能保證程序在每個週期內執行 runtime 個時間片,但不一定連續,也不一定執行完(詳見排程規則和示例)。
排程規則
- 新增的 EDF 演算法與 MOS 原有的 RR 演算法擁有各自獨立的排程佇列。EDF 排程佇列中包含所有使用
env_create_edf建立的程序,而 RR 排程佇列(MOS 原有的排程佇列)中包含所有使用env_create建立的程序。 - 當時鍾中斷產生時,若 EDF 排程佇列中存在尚未執行完當前週期時間片的程序,則選擇截止時間最早的程序排程。如果多個程序的截止時間相同,則選擇
env_id最小的程序排程。 - 從 EDF 排程佇列選取程序後,僅需使其執行一個時間片,並在下個時鐘中斷產生時使用第二條規則,重新選擇程序排程。本題中要求實現的 EDF 排程演算法不受
yield引數和優先順序的影響,只與程序的截止時間有關。 - 如果 EDF 排程佇列為空,或其中的所有程序均已執行完當前週期的時間片,則使用預設實現的 RR 演算法排程 RR 排程佇列中的程序。需要注意的是,EDF 排程的程序可以在任何時刻搶佔 RR 排程的程序,且 RR 排程的程序執行的時間不會被複用,不會發生擠佔。
例如,如果 RR 演算法選擇排程一個優先順序為 5 的程序 A,A 已經使其執行了 3 個時間片,此時 EDF 佇列中產生了可以被排程的程序 B,則程序 B 會搶佔程序 A 的執行,直到程序 B 執行完當前週期的時間片後,程序 A 繼續執行剩餘的 2 個時間片。我被這條給坑了,以為是說 RR 演算法的程序在被 EDF 排程的程序切出去之後,resume 時還接著上次選擇的 RR 演算法的程序執行。
- 此外,如果在某個週期內,使用 EDF 演算法未能保證程序執行完
runtime個時間片,則剩餘的時間片不會被保留至下週期,而是直接作廢。
示例
程序按照下表的順序依次加入 RR 排程佇列和 EDF 排程佇列:
| 程序名 | 優先順序 | 每個週期內需要執行的時間片 | 執行週期 |
|---|---|---|---|
| A | 1 | - | - |
| B | 3 | - | - |
| C | - | 1 | 5 |
| D | - | 2 | 7 |
注:由於課下程式碼中程序總是插入排程連結串列的頭部,因此 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 執行一個時間片;
參考實現思路
-
在
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 排程佇列。
-
在
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_list和env_free_list。
-
在
include/env.h的Env結構體中新增以下欄位: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; // 程序當前週期剩餘的時間片 -
在
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);
}
評論