注:根據mit-jos的lab指導手冊,以下不明確區分「環境」和「程序」
到目前位置我們以及你實現了核心基本的異常處理,現在要在此基礎上利用異常處理進行系統呼叫。
頁面錯誤(page fault,中斷向量14)是我們在本實驗和往後會大量使用的乙個重要例子。當頁面錯誤發生時,處理器將導致故障的線性位址(虛擬位址)存放在特殊暫存器cr2
中。在trap.c
中,提供了乙個用於頁面錯誤的函式page_fault_handler()
修改trap_dispatch()
,呼叫page_fault_handler()
處理頁面錯誤
斷點異常(breakpoint,中斷向量3)常被偵錯程式用來在程式的斷點處臨時用int 3
代替原來的語句。在jos中我們會把這個異常作為乙個偽系統呼叫以便任何使用者環境都能呼叫到jos的核心monitor。
修改trap_dispatch()
,在斷點異常時呼叫monitor()
這兩節的**實現如下:
static void
trap_dispatch(struct trapframe *tf)
}}
對於斷點異常,為了能讓他在使用者態被正常呼叫,還要對其許可權進行修改:
setgate(idt[t_brkpt], 1, gd_kt, t_brkpt, 3);
否則,使用者將無權呼叫int $3
,在斷點異常產生時將再產生乙個異常protection fault(trap 13),就像上乙個實驗最後做的那樣
使用者程序利用系統呼叫請求核心為它完成一些操作。當使用者程序發起系統呼叫,處理器進入核心態,處理器和核心將儲存當前使用者程序的上下文狀態,核心執行相應**實現系統呼叫,然後返回繼續執行使用者程序的**。使用者如何向核心發起系統呼叫以及某個特定系統呼叫的具體用途在不同的作業系統中有很多不同的實現方式
在jos系統中,我們使用int
指令,它會出發乙個處理器的中斷。特別的,我們使用int $0x30
作為系統呼叫
我們定義其中斷向量為48(0x30),然後需要在中斷向量表中初始化它的中斷號和入口函式。這個中斷不會被硬體觸發
使用者程式會通過暫存器向核心傳遞系統呼叫號和引數,這樣核心就不需要從使用者的堆疊或指令流中獲取引數了
例如在hello.c
中,跟進cprintf
可以發現它是最終利用lib/syscall.c
中的syscall()
,將需要列印的字串位址、字串長度放在暫存器中傳給核心,並傳遞系統呼叫號num
告訴核心想要做什麼,請求核心幫助完成列印。
補充**完成系統呼叫:
trap_dispatch()
的補充**如下:
case t_syscall:
tf->tf_regs.reg_eax = syscall(tf->tf_regs.reg_eax,
tf->tf_regs.reg_edx,
tf->tf_regs.reg_ecx,
tf->tf_regs.reg_ebx,
tf->tf_regs.reg_edi,
tf->tf_regs.reg_esi);
break;
syscall()
實現如下:
int32_t
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
return 0;
}
使用者程序從lib/entry.s
開始執行。在進行一些初始化後,呼叫lib/libmain.c
的libmain()
,它設定使用者環境指向當前環境,設定使用者程序的名稱,並呼叫umain()
進入使用者程序的主函式。修改libmain()
,使
**:thisenv = envs + envx(sys_getenvid());
之後,libmain()
呼叫user/hello.c
中的umain()
,執行程式hello
。在libmain()
完整之前在qemu執行核心,列印「hello world」後發生了頁面錯誤,就是因為程式還沒完善,hello.c
中的thisenv->env_id
指向0
記憶體保護是作業系統中至關重要的乙個特性,它保證乙個程序中的錯誤不會破壞其他程序或系統核心。
作業系統通常依賴硬體支援來實現記憶體保護。作業系統始終能讓硬體知道哪些虛擬位址是有效的、哪些是無效的,當程序需要訪問無效的位址或它無權訪問某位址時,作業系統會在導致錯誤的指令處終止程式,進入核心態,並將錯誤資訊報告給核心。如果這個異常是可修復的,那麼核心修復這個異常,程式繼續執行;如果這個異常無法被修復,程式將被終止。
舉乙個可修復的錯誤的例子:考慮乙個可自動擴充套件的堆疊。很多作業系統中,核心為使用者程式分配單個頁面作為棧區,如果程式想要訪問這個棧區以外的空間而觸發異常,作業系統自動為其分配更大的空間保證使用者程式繼續執行。通過這種做法,作業系統實際上只分配程式需要的棧記憶體量,但程式會覺得自己能在任意大的堆疊下執行。
系統呼叫也為記憶體保護帶來問題:大部分系統呼叫介面讓使用者程式傳遞乙個指標引數給核心。這些指標指向的是使用者讀寫的緩衝區。通過這種方式,系統呼叫在執行時就可以讀寫這些指標指向的資料。但是這裡有兩個問題:
針對這兩點,我們之前寫的中斷處理和系統呼叫還存在一些問題:
接下來就需要完善這些函式保證系統呼叫的安全性
在pmap.c
中實現user_mem_check()
:
int
user_mem_check(struct env *env, const void *va, size_t len, int perm)
else
} return 0;
}
這裡由於將va
向下4k對齊,因此需要檢查begin < (uintptr_t)va
,如果在va
首位址處就發現許可權不對,要正確返回va
的位址而不是begin
在page_fault_handler()
中增加檢查是否核心發生了頁面錯誤:
void
page_fault_handler(struct trapframe *tf)
在sys_cputs()
中檢查使用者是否具有許可權讀寫相應記憶體:
static void
sys_cputs(const char *s, size_t len)
敏捷開發使用者故事系列之三 使用者建模
這是敏捷開發使用者故事系列的第三篇。之一,之二,之三,之四,之五,之六,之七,之八,之九 使用者建模的目的,是為了更好地分析使用者行為和使用者價值,並因此獲得商機。有一次培訓中,分組建模的時候,一位學員就自言自語地說了一句話 真的啊 我好像不知道誰會使用我的產品 這其實是一種常見的現象。比如前文所說...
敏捷開發使用者故事系列之三 使用者建模
這是敏捷開發使用者故事系列的第三篇。之一,之二,之三,之四,之五,之六,之七,之八,之九 使用者建模的目的,是為了更好地分析使用者行為和使用者價值,並因此獲得商機。有一次培訓中,分組建模的時候,一位學員就自言自語地說了一句話 真的啊 我好像不知道誰會使用我的產品 這其實是一種常見的現象。比如前文所說...
敏捷開發使用者故事系列之三 使用者建模
這是敏捷開發使用者故事系列的第三篇。之一,之二,之三,之四,之五,之六,之七,之八,之九 使用者建模的目的,是為了更好地分析使用者行為和使用者價值,並因此獲得商機。有一次培訓中,分組建模的時候,一位學員就自言自語地說了一句話 真的啊 我好像不知道誰會使用我的產品 這其實是一種常見的現象。比如前文所說...