Lab4: System Call - exec, fork and exit
1. 更改架構
為了方便 required 4 的要求先更改整體架構。
1-1. system call
仿效 glibc 使用巨集產生 privilege function 的 wrapper 提供給 user。以 printf 為例,在 user 可透過 uart_write system call 間接呼叫 _uart_write 。
1 |
|
但在 kernel 呼叫 printk 則可直接使用 _uart_write ,避開 system call 帶來的 overhead。
1 |
|
巨集參考
- https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysdep.h
- https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/aarch64/sysdep.h
1-2. object file 分離
仿造 sungyuanyao 的作法,將 source file 分成三部份放
- kernel: 需要存取硬體相關操作例如 uart
- user: 定義 user task
- lib: 提供給 user 的函式庫例如
printf
2. Syscall - exec()
2-1. system call definition
Glibc 是用 marco 去定義 system call wrapper,如果要定義 int64_t reset(uint64_t) ,會使用 SYSCALL_ARG1:
1 |
|
Expands to:
1 | int64_t reset(uint64_t x0) { |
但定義 int64_t exec(((void *)())) 會出錯,原因在於exec 是帶有 function pointer 型態參數的函式:
1 | SYSCALL_ARG1(reset, int64_t, ((void *)())) |
遇到 exec 這種特例只得直接寫死:
1 | int64_t exec(void (*x0)()){ return INTERNAL_SYSCALL(exec, 1, INPUT_ARGS_1); } |
又或者使用 pointer to void 作為參數型態,傳遞 function pointer 進去?
1 | int64_t exec(void *x0){ |
先查看規格書的用法。截自 Cppreference - Pointer declaration,並沒有提到 function pointer 隱性轉換至 pointer to void。
Pointer declaration
Pointer is a type of an object that refers to a function or an object of another type, possibly adding qualifiers.
Pointers to void
Pointer to object of any type can be implicitly converted to pointer to void (optionally const or volatile-qualified), and vice versa:
- If a pointer to object is converted to a pointer to void and back, its value compares equal to the original pointer.
- No other guarantees are offered
但是透過以下程式碼實驗,是可以正常編譯執行
1 | static int i = 2; |
但是開啟 -Wpedantic 跑出一堆警告
1 | -Wpedantic |
1 | ~$ gcc test.c -o test -Wall -std=c99 -Wpedantic |
再次查閱規格書 p521, ISO/IEC 9899:1999 (E),發現 function pointer be cast to pointer to void 是屬於 extension
J.5 Common extensions
The following extensions are widely used in many systems, but are not portable to all
implementations.
J.5.7 Function pointer casts
A pointer to an object or to void may be cast to a pointer to a function, allowing data to
be invoked as a function (6.5.4).A pointer to a function may be cast to a pointer to an object or to void, allowing a
function to be inspected or modified (for example, by a debugger) (6.5.4).
在有隱慮的情況下,還是放棄 pointer to void 的想法。
2-2. implementation
exec 目的在替換 TrapFrame 內的 elr_el1, sp,使其回到 user space 時執行給定的 function 與使用新的 stack pointer:
1 | int64_t sys_exec(struct TrapFrame *tf) |
3. Syscall - fork()
Kernel should allocate new task struct and kernel stack for child. Also, parent’s user context should be copied to child’s. But you should modify the trapframe to make the return value different.
3-1. stack pointer of exception handler
Exception handler 所使用的 stack pointer 是基於是最近使用的 EL1 stack pointer,那在 kernel/user task 發生 exception 時是基於誰的 stack pointer?列舉所有可能:
- kernel task 發生 exception 時,exception handler 所使用的 stack pointer 是屬於該 task 的 kernel stack。
- user task 是從 kernel task 執行
exec切換到 EL0,因此在 user 發生 exception 切換至 EL1 時是使用屬於該 task 的 kernel stack。 switch_to在 EL1 載入 kernel stack,再跳轉回 kernel/user task
綜合上述,不論 exception 發生在 user 或 kernel,所使用的 stack pointer 就是當前 task 的 kernel stack。
好像講了一段廢話
3-2. implementation
實作借用了 sungyuanyao 的想法,不然想半天沒想法
在 exception handler,也就是 fork syscall 內
- 先用 privilege_task_create 取得 child process 的
task_struct - 初始化
task_struct,讓 child process 如同從 EL1 跳回 EL0 完成 syscall,因此我們需要- 將
task_context.lr指向ret_to_user,完成kernel_exit的最後一步 - kernel_exit 最後是還原
TrapFrame到各個 register,task_context.sp應該指向TrapFrame TrapFrame也需要初始化,就從 parent process 複製過來,唯一不同的是TrapFrame內的x0 = 0(return value)TrapFrame內的sp(sp_el1) 要指向 child process 的 user stack
- 將
- child process 的 user stack 從 parent process 複製過來
- 將 child process 加入
runqueue,並設為TASK_RUNNABLE
比起文字,還是看圖快些。
1 | user user |
4. Syscall - exit()
將目前 task state 標記為 TASK_ZOMBIE ,並放入 zombie_list 等待 zombie reaper 來回收。
1 | void do_exit() |
5. Zombie Reaper
zombie_list 為一 doubly linked list,專門放 TASK_ZOMBIE 的 task。zombie reaper 就是將 task state 重新標記為 TASK_UNUSED,這樣 privilege_task_create 就能再次取得該 task。
1 | void zombie_reaper() |
6. test case
6-1. For required 1 and required 2

6-2. For required 3 and required 4
因為 timer 頻率較低的關係,執行到 task id=3 剛好觸發 rescheduling 所以切換到 task id=4
