為什麼要使用操作系統 使用操作系統的主要原因是為了實現 CPU 多進程分時復用以及記憶體隔離 如果沒有操作系統,應用程式會直接與硬體進行交互,這時應用程式會直接使用 CPU,比如假設只有一個 CPU 核,一個應用程式在這個 CPU 核上運行,但是同時其他程式也需要運行,因為沒有操作系統來幫助切換,就需 ...
為什麼要使用操作系統
- 使用操作系統的主要原因是為了實現 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 文件,將它們鏈接在一起並生成內核文件