3.3.1 多核心系統下遭遇的問題
當我們在多核心系統上同時執行多套的 simulation engine,由於是分別 獨立執行,屬於 user level 的 simulation engine 的記憶體空間彼此獨立,並不 會造成衝突。但因為 NCTUns 還需要與 kernel 互動,我們雖然執行了多套 NCTUns,但 kernel 部分卻是共享的,所以對於 kernel 內部需要再做修改。
用下圖 3.5 來說明這個問題,原本 NCTUns 的模擬時間是由 simulation engine 來推進,並且以記憶體映射(memory map)的方式儲存在 kernel 內,當
22
kernel 只要有與時間有關係的操作,都需要取用這份模擬時間作為依據。假 設現在執行兩套 NCTUns 進行模擬工作,可能第一套 NCTUns 模擬至 100 秒,而第二套 NCTUns 才開始模擬,則由於系統核心內的模擬時間只存有 一份,無論是保持原本的 100 秒,或被覆蓋為 0 秒,都會造成模擬的錯誤。
圖 3.5 多核心系統下的問題二
這類型的錯誤都是因為在原本版本的 NCTUns kernel 裡,所有為模擬所 做的修改,都只有考慮到一份模擬引擎,如果為每一份參與並行模擬的 simulation engine 都準備一份專屬於它的 kernel 模擬機制,這個問題就得以 解決。我們以圖 3.6 來說明如何解決圖 3.5 的問題,當我們在 kernel 裡也準 備兩份 memory map 的空間分開儲存模擬時間,這樣就能避免因多個 simulation engine 想與 kernel 互動時,kernel 的應對機制卻只有一套帶來的錯 誤。
圖 3.6 多核心系統下的解決方法二
23
3.3.2 kernel 分辨 simulation engine
當我們已經作到複製多份 kernel 裡與 simulation engine 互動的機制,例 如將原來單一個變數宣告為並行模擬數量大小的陣列,還存在一個問題;就 是對於 kernel 來說,它如何去分辨今天來要求模擬機制的是哪一套 simulation engine,如果沒有辦法分辨,即使複製多份 kernel 裡的模擬機制,也無法順 利模擬。
這問題的解決方法便是上節解釋的 coordinator index,但是如果 kernel 每次要作 simulation engine 判斷時都需要比對 coordinator index table 與現在 執行中的 parent process ID,非常沒有效率,所以我們在 linux 描述 process 的資料結構 task_struct 裡新增了 CoorIndex 變數如下圖 3.7。
我們可以看到圖 3.7 左半部是原始版本 NCTUns 已做的修改,加入 nodeID 與 endtime。但 nodeID 僅能分辨該行程是否屬於 NCTUns,當執行多 套 NCTUns 時 node ID 無法分辨該行程是屬於哪一套 NCTUns。因此我們在 此新增一變數 CoorIndex,目的是讓 kernel 能夠在處理 NCTUns 相關服務時,
能夠利用以下方式得到該去以哪一套模擬機制應對的資訊。Current 就是指向 現正執行的 process。
圖 3.7 linux process 資料結構 current->nctuns_task.CoorIndex
24
現在系統核心內的操作都有行程資料結構內的 coordinator index 作為依 據,而最初行程內的 coordinator index 又是如何填入。在此是採用以我們自 定義的 system call 由使用者層級的行程來呼叫填入 coordinator index。精確 的說便是當 coordinator fork 出 simulation engine,當 simulation engine fork 出 Applications,我們都會呼叫這個 system call 來填入 CoorIndex。
當
/* at src/patched_kernel/kernel/nctuns/nctuns_syscall.c */
asmlinkage int sys_NCTUNS_misc(enum syscall_NSC_misc_enum action, unsigned long value1, unsigned long value2, unsigned long value3) {
switch (action) {
case syscall_NSC_misc_SET_COORINDEX:
syscall_NCTUNS_misc(syscall_NSC_misc_SET_COORINDEX, getpid(), CoorIndex, 0);
25
確保了我們能夠在 kernel 複製多份模擬所需要的機制以及 kernel 能夠 準確判斷在屬於哪一套 simulation engine 的 process 處理時該使用哪一套 kernel 裡的模擬機制,我們也就能夠確定模擬過程中 simulation engine 與 kernel 的互動能夠成功。
經過我們整理歸納之後,在 kernel 需要這樣複製多份的模擬機制有 1. 負責模擬時間的維護與 kernel 裡相對應模擬時間的 timer list 的
NCTUNS_nodeVC 與 callwheel。
2. 負責與 simulation engine 溝通的 event tunnel 與記錄 event queue length 的 t0eqlen。
3. 負責記錄模擬拓樸資訊的 Mtable。
由於概念都是一樣的,都是兩個部分,一是在宣告時將原有資料結構宣
告為可並行模擬數量大小的陣列,二是在使用時注意該對應 coordinator index 去取得對應的資訊。在以下小節我們便以 NCTUNS_nodeVC 與 callwheel 作 為範例來說明修改的細節。
3.3.3 範例:NCTUNS_nodeVC
NCTUNS_nodeVC 是 kernel 儲存 global virtual time 的變數,kernel 藉 memory map 技術與 simulation engine 同步取得模擬時間,原本只能記錄一組 的模擬時間,我們將之宣告為一組模擬時間陣列,大小是我們定義的最大可 並行的 coordinator 數量,裡面儲存每組 simulation engine 對應的模擬時間。
在 user level simulation engine 也會根據自己的 coordinator index 更新相應的模 擬時間。
26
而原本在 kernel 裡關於 NCTUNS_nodeVC 的使用都要經過修改,例如 下面所附程式碼,當要取用 NCTUNS_nodeVC 時,都是利用 current->
nctuns_task.CoorIndex 去取得 coordinator index 作為索引值得到相應的模擬時 間。這麼做儘管 kernel 還是共享,但是就能根據行程內附的 coordinator index 來決定該取用哪一個模擬時間。
/* at src/patched_kernel/include/nctuns/nctuns_ticks.h */
#define NCTUNS_ticks_to_ns(i) \
(NCTUNS_nodeVC[current->nctuns_task.CoorIndex]
* NSEC_PER_NCTUNS_TICKS)
#define NCTUNS_ticks_to_us(i) \
div64_u64(NCTUNS_nodeVC[current->nctuns_task.CoorIndex], NCTUNS_TICKS_PER_USEC)
#define NCTUNS_ticks_to_ms(i) \
div64_u64(NCTUNS_nodeVC[current->nctuns_task.CoorIndex], NCTUNS_TICKS_PER_MSEC)
#define NCTUNS_ticks_to_sec(i) \
div64_u64(NCTUNS_nodeVC[current->nctuns_task.CoorIndex], NCTUNS_TICKS_PER_SEC)
/* at src/patched_kernel/drivers/char/nctuns_dev.c */
u64 NCTUNS_nodeVC[ConcunCoorNum];
/* at src/patched_kernel/drivers/char/nctuns_dev.c */
#define ConcunCoorNum 8
27
3.3.4 範例:callwheel
callwheel 是由 NCTUns 自行建立在 kernel 裡的 virtual timer list,同樣如 上面小節敘述的 NCTUNS_nodeVC,原先是只負責單一 simulation engine 的 運行,為了支援並行模擬,在這也將 callwheel 宣告為可支援並行數量大小的 陣列。
在 kernel 內的程式碼與上一小節提述的 NCTUNS_nodeVC 一樣,只要 是有使用到 callwheel 的地方,都要修改為
如同以下所附程式碼展示新增 timer 進入 callwheel 的部分,只要有關 callwheel,全都以 coordinator index 去做存取的索引值。
/* at src/patched_kernel/kernel/nctuns/nctuns_callout.c */
/* Insert timer into callwheel */
int CoorIndex = current->nctuns_task.CoorIndex;
vec = callwheel[CoorIndex].vec + (expires & callwheelmask);
list_add(&timer->entry, vec);
(callwheel[CoorIndex].count)++;
nctuns_proc = find_task_by_pid_ns(nctuns, &init_pid_ns);
if(nctuns_proc != NULL)
wake_up_process(nctuns_proc);
/* at src/patched_kernel/include/nctuns/nctuns_callout.h */
/* at src/patched_kernel/kernel/nctuns/nctuns_callout.c */
#define callwheelsize 8192 struct callwheel {
struct list_head vec[callwheelsize];
int count;
};
struct callwheel callwheel[ConcunCoorNum];
callwheel[current->nctuns_task.CoorIndex]
28