現代操作系統普遍採用虛擬內存管理(Virtual Memory Management)機制,這需要處理器中的MMU(Memory Management Unit,內存管理單元)提供支持,本節簡要介紹MMU的作用。
首先引入兩個概念,虛擬地址和物理地址。如果處理器沒有MMU,或者有MMU但沒有啟用,CPU執行單元發出的內存地址將直接傳到晶片引腳上,被內存晶片(以下稱為物理內存,以便與虛擬內存區分)接收,這稱為物理地址(Physical Address,以下簡稱PA),如下圖所示。
如果處理器啟用了MMU,CPU執行單元發出的內存地址將被MMU截獲,從CPU到MMU的地址稱為虛擬地址(Virtual Address,以下簡稱VA),而MMU將這個地址翻譯成另一個地址發到CPU晶片的外部地址引腳上,也就是將VA映射成PA,如下圖所示。
如果是32位處理器,則內地址匯流排是32位的,與CPU執行單元相連(圖中只是示意性地畫了4條地址綫),而經過MMU轉換之後的外地址匯流排則不一定是32位的。也就是說,虛擬地址空間和物理地址空間是獨立的,32位處理器的虛擬地址空間是4GB,而物理地址空間既可以大於也可以小於4GB。
MMU將VA映射到PA是以頁(Page)為單位的,32位處理器的頁尺寸通常是4KB。例如,MMU可以通過一個映射項將VA的一頁0xb7001000~0xb7001fff映射到PA的一頁0x2000~0x2fff,如果CPU執行單元要訪問虛擬地址0xb7001008,則實際訪問到的物理地址是0x2008。物理內存中的頁稱為物理頁面或者頁幀(Page Frame)。虛擬內存的哪個頁面映射到物理內存的哪個頁幀是通過頁表(Page Table)來描述的,頁表保存在物理內存中,MMU會查找頁表來確定一個VA應該映射到什麼PA。
操作系統和MMU是這樣配合的:
操作系統在初始化或分配、釋放內存時會執行一些指令在物理內存中填寫頁表,然後用指令設置MMU,告訴MMU頁表在物理內存中的什麼位置。
設置好之後,CPU每次執行訪問內存的指令都會自動引發MMU做查表和地址轉換操作,地址轉換操作由硬件自動完成,不需要用指令控制MMU去做。
我們在程序中使用的變數和函數都有各自的地址,程序被編譯後,這些地址就成了指令中的地址,指令中的地址被CPU解釋執行,就成了CPU執行單元發出的內存地址,所以在啟用MMU的情況下,程序中使用的地址都是虛擬地址,都會引發MMU做查表和地址轉換操作。那為什麼要設計這麼複雜的內存管理機制呢?多了一層VA到PA的轉換到底換來了什麼好處?All problems in computer science can be solved by another level of indirection.還記得這句話嗎?多了一層間接必然是為瞭解決什麼問題的,等講完了必要的預備知識之後,將在第 5 節 “虛擬內存管理”討論虛擬內存管理機制的作用。
MMU除了做地址轉換之外,還提供內存保護機制。各種體繫結構都有用戶模式(User Mode)和特權模式(Privileged Mode)之分,操作系統可以在頁表中設置每個內存頁面的訪問權限,有些頁面不允許訪問,有些頁面只有在CPU處于特權模式時才允許訪問,有些頁面在用戶模式和特權模式都可以訪問,訪問權限又分為可讀、可寫和可執行三種。這樣設定好之後,當CPU要訪問一個VA時,MMU會檢查CPU當前處于用戶模式還是特權模式,訪問內存的目的是讀數據、寫數據還是取指令,如果和操作系統設定的頁面權限相符,就允許訪問,把它轉換成PA,否則不允許訪問,產生一個異常(Exception)。異常的處理過程和中斷類似,不同的是中斷由外部設備產生而異常由CPU內部產生,中斷產生的原因和CPU當前執行的指令無關,而異常的產生就是由於CPU當前執行的指令出了問題,例如訪問內存的指令被MMU檢查出權限錯誤,除法指令的除數為0等都會產生異常。
通常操作系統把虛擬地址空間劃分為用戶空間和內核空間,例如x86平台的Linux系統虛擬地址空間是0x00000000~0xffffffff,前3GB(0x00000000~0xbfffffff)是用戶空間,後1GB(0xc0000000~0xffffffff)是內核空間。用戶程序加載到用戶空間,在用戶模式下執行,不能訪問內核中的數據,也不能跳轉到內核代碼中執行。這樣可以保護內核,如果一個進程訪問了非法地址,頂多這一個進程崩潰,而不會影響到內核和整個系統的穩定性。CPU在產生中斷或異常時不僅會跳轉到中斷或異常服務程序,還會自動切換模式,從用戶模式切換到特權模式,因此從中斷或異常服務程序可以跳轉到內核代碼中執行。事實上,整個內核就是由各種中斷和異常處理程序組成的。總結一下:在正常情況下處理器在用戶模式執行用戶程序,在中斷或異常情況下處理器切換到特權模式執行內核程序,處理完中斷或異常之後再返回用戶模式繼續執行用戶程序。
段錯誤我們已經遇到過很多次了,它是這樣產生的:
用戶程序要訪問的一個VA,經MMU檢查無權訪問。
MMU產生一個異常,CPU從用戶模式切換到特權模式,跳轉到內核代碼中執行異常服務程序。
內核把這個異常解釋為段錯誤,把引發異常的進程終止掉。