本次仿造 MIT-OSE JOS 中的系統呼叫,
筆者為自己小OS加新功能,算是對最近學習的一個驗證。
實作程式碼在 https://github.com/benwei/bos (develop branch)。
讓我們先來看JOS的系統呼叫的概念圖:
可參考Lab3 JOS 實作程式碼在branch lab3 https://github.com/benwei/MIT-JOS
執行步驟:
- lib/libmain.c:17 呼叫 sys_getenvid()
- lib/syscall.c:61 呼叫 syscall(SYS_getenvid)
- lib/syscall.c:23 呼叫48號中斷 帶入系統呼叫SYS_getenvid(2) 號
- 發生中斷 kern/trapentry.S:98, 將返回值0及 系統呼叫SYS_getenvid(2) 號放入堆疊,
- 跳至 kern/trapentry.S:312 _alltraps中,將相關的暫存器放入堆疊,接著執行332行trap(%esp), %esp == struct Trapframe *tf
- 為避免重覆執行中斷, kern/trap.c:200, eflags & FL_IF == 0 必須為真,204行檢查只否由使用者空間進入(tf->tf_cs & 3) == 3, 將tf 複製到核心空間保存;執行220行trap_dispatch()
- 執行trap.c:172 核心的syscall() 在 kern/syscall.c
- 跳至kern/syscall.c:87 執行 sys_getenvid(), 取得 curenv->env_id
- 返回trapdispatch, 執行kern/env.c:520 env_run 中 env_pop_tf()
- 呼叫kern/env.c:502 重新設置返回值及相關暫存器
- 於kern/env.c:507 執行iret 結束系統呼叫中斷
- 回到使用者空間,lib/syscall.c:34 檢查回傳值等等,完成一個呼叫流程
BenOS 實作
本次只先實作部分 kernel thread 間的系統呼叫,尚無ring3 至ring 0之間的權限切換。
詳見 user/libsyscall.c:10 系統呼叫:int TRAP_SYSCALL(68) , 其中 BSYS_PUTS(8)
1 #include "user/syscall.h" 2 #include "stdio.h" 3 4 /* 5 * num: BSYS_PUTS, a1 is string for display, a2 is undefined. 6 */ 7 unsigned int syscall(int num, unsigned int a1, unsigned int a2) 8 { 9 unsigned int ret = 0; 10 __asm __volatile( 11 "int %1\n" 12 :"=a" (ret) 13 :"i" (TRAP_SYSCALL), 14 "a" (num), 15 "d" (a1), 16 "c" (a2) 17 :"cc", "memory"); 18 printf("syscall with ret=%d\n", ret); 19 return ret; 20 } 21 22 void uputs(const char *msg) 23 { 24 syscall(BSYS_PUTS, (unsigned int)msg, 202); 25 } |
在kernel/osfunc.s:126 使用nasm 語法寫成,IDT 中斷呼叫256個函式庫。
其中osfunc.s:400 為減少堆疊使用,僅使用三個參數。
(註:將JOS 中的env_pop_tf 改寫成409~413,較易於理解)
400 _all_trap_handler: 401 push eax 402 push edx 403 push ecx 404 405 push esp 406 call trap_handler 407 add esp, 4 408 409 pop ecx 410 pop edx 411 add esp, 4 ; no pop eax for return code 412 add esp, 8 413 iretd |
kernel/trap.c
70 int trap_handler(struct trapframe *tf) 71 { 72 switch(tf->trapno) { 73 case TRAP_SYSCALL: 74 print_tf(tf); 75 76 return kern_syscall(tf->regs.eax, 77 tf->regs.edx, 78 tf->regs.ecx); 79 break; 80 default: 81 print_tf(tf); 82 panic("panic: system halt\n"); 83 } 84 return 0; 85 } |
kernel/ksyscall.c
6 int kern_syscall(uint32_t num, uint32_t a1, uint32_t a2) 7 { 8 int ret = 0; 9 if (num == BSYS_PUTS) { 10 do { 11 struct task *t = get_now_task(); 12 printf("task_id(%d) syscall(BSYS_PUTS,a2=%d),a1: %s\n", t->pid, a2, a1); 13 ret = strlen(a1); 14 } while(0); 15 } 16 return ret; 17 } |
執行結果:
開機完成至bos$ 下,執行run 來執行task 2 (events: hello_main)
小結
本次的系統呼叫的基本框架,以最精簡的部分,實作出來。
筆者正在嘗試加入ring3 至 ring0 時,
在windows的qemu 0.13版,執行run 命令,qemu系統自動重新啟動bos;
但在MAC OS X 中使用qemu 0.12.5 執行同樣測試時,
qemu 會顯示正確存取權限失敗,
因為bos在ring3 中直接呼叫ring0函式。之後,會對使用者權限切換部分繼續實作。
有任何問題建議,歡迎至 http://www.juluos.org or http://julu.staros.mobi 中的Juluosdev google groups 參與討論。