為什麼要使用作業系統
- 使用作業系統的主要原因是為了實現 CPU 多程序分時複用以及記憶體隔離
- 如果沒有作業系統,應用程式會直接與硬體進行互動,這時應用程式會直接使用 CPU,比如假設只有一個 CPU 核,一個應用程式在這個 CPU 核上執行,但是同時其他程式也需要執行,因為沒有作業系統來幫助切換,就需要應用程式時不時釋放 CPU 資源,但是如果這個程式的某個函式有一個死迴圈,那它就永遠也不會釋放 CPU,甚至沒辦法做到執行第三方程式來停止或者殺死這個死迴圈程式,這種情況下就沒辦法實現 CPU 多程序的分時複用
- 還有從記憶體的角度來看,如果應用程式直接執行在硬體上,則程式的資料程式碼都直接儲存到實體記憶體中,這樣不同程式的記憶體之間沒有明確邊界,就可能會造成一個程式儲存在本來屬於另外一個程式的記憶體空間,覆蓋另外一個程式中的內容
作業系統的隔離性需要隔離使用者程式和作業系統,也需要隔離不同的程序
系統呼叫與隔離性
- 可以認為
exec
抽象了記憶體。當我們在執行 exec 系統呼叫的時候,我們會傳入一個檔名,而這個檔名對應了一個應用程式的記憶體映象。記憶體映象裡面包括了程式對應的指令,全域性的資料。應用程式可以逐漸擴充套件自己的記憶體,但是應用程式並沒有直接訪問實體記憶體的許可權,例如應用程式不能直接訪問實體記憶體的 1000-2000 這段地址。不能直接訪問的原因是,作業系統會提供記憶體隔離並控制記憶體,作業系統會在應用程式和硬體資源之間提供一箇中間層。exec 是這樣一種系統呼叫,它表明了應用程式不能直接訪問實體記憶體。
files
基本上是抽象了磁碟。應用程式不會直接讀取磁碟,在 Unix 中它與儲存系統互動的唯一方式就是透過files
。作業系統會決定如何將檔案與磁碟中的塊對應,確保一個磁碟塊只出現在一個檔案中,並保證使用者 A 不能操作使用者 B 的檔案。files 實現了不同使用者之間以及同一使用者不同程序之間的檔案隔離。
硬體實現強隔離性
實現強隔離性的硬體支援包括了兩部分:
-
user/kernel mode:處理器有兩種操作模式:user mode 和 kernel mode:
- 當執行在 kernel model 時,CPU 能執行特定許可權的指令(直接操作硬體的指令和設定保護的指令,如設定 page table 暫存器、關閉時鐘中斷)
- 當執行在 user mode 時,CPU 只能執行普通許可權的指令
RISC-V 實際上有三種許可權:user/kernel/machine mode
-
在 RISC-V 中,如果你在使用者空間(user space)嘗試執行一條特殊許可權指令,使用者程式會透過系統呼叫來切換到 kernel mode。當使用者程式執行系統呼叫,會透過 ECALL 觸發一個軟中斷(software interrupt),軟中斷會查詢作業系統預先設定的中斷向量表,並執行中斷向量表中包含的中斷處理程式。中斷處理程式在核心中,這樣就完成了 user mode 到 kernel mode 的切換,並執行使用者程式想要執行的特殊許可權指令
-
虛擬記憶體:每個程序都有自己獨立的 page table,page table 將虛擬記憶體地址和實體記憶體地址做了對應
ECALL 指令
- 在 RISC-V 中,
ECALL
指令可以讓使用者程式將控制權轉移給核心,並傳入一個數字,這個數字表示了應用程式想要呼叫的 System Call ECALL
會跳轉到核心中一個特定的位置,在核心側,有一個位於syscall.c
的函式syscall
,每一個從應用程式發起的系統呼叫都會呼叫到這個syscall
函式,syscall
函式會檢查ECALL
的引數
核心是如何奪回控制權
- 核心會透過硬體設定一個定時器,定時器到期之後會將控制許可權從使用者空間轉移到核心空間,之後核心就有了控制能力並可以重新排程 CPU 到另一個程序中
單核心 vs 微核心
單核心
XV6 中,所有的作業系統服務都在 kernel mode 中,這種形式被稱為單核心
- 從安全的角度來說,這種方式不太好,在一個單核心中,任何一個作業系統的 Bug 都有可能成為漏洞,如果有許多行程式碼執行在核心中,那麼出現嚴重 Bug 的可能性也變得更大
- 單核心的優勢在於可以將檔案系統、虛擬記憶體、程序管理這些實現特定功能的子模組緊密地整合在一起,這樣可以提供很好的效能
微核心
微核心模式下,在核心中只有非常少的模組,將核心中的其他部分作為普通的使用者程式來執行
- 這樣做的好處是核心中的程式碼數量較小,降低了 bug 出現的可能
- 如果使用者程式想要使用核心的功能,但由於核心的程式作為普通的使用者程式,比如說某個系統想要使用檔案系統,需要完成從使用者空間到核心空間,再從核心到使用者空間來訪問檔案系統,檔案系統也需要經過同樣的路徑將訊息返回給使用者系統,使得在微核心從使用者到核心的跳轉是單核心的兩倍,而反覆跳轉帶來了效能的損耗,且微核心的核心程式被隔離開,難以實現共享,降低了系統的效能。
XV6 程式碼結構
程式碼主要由三部分組成:
kernel
:包含了基本上所有的核心檔案user
:這基本上是執行在 user mode 的程式mkfs
:會建立一個空的檔案映象,將這個映象存在磁碟上,就可以直接使用一個空的檔案系統
kernel 編譯過程
Makefile
讀取一個 C 檔案,比如proc.c
,然後呼叫 gcc 編譯器,生成一個叫proc.s
的檔案,這是 RISC-V 組合語言檔案,然後再走到彙編直譯器,生成proc.o
,這是組合語言的二進位制格式,Makefile
會為所有核心檔案做相同的操作- 系統載入器收集所有的.o 檔案,將它們連結在一起並生成核心檔案