機制:地址轉換
文章目錄
對於一個進程來說,它只能看到自己的地址空間。在進程的角度,對內存的每次訪問,都使用虛擬地址來訪問內存;當然,這也是爲了編程簡單,在編程時,不必考慮真實的內存,程序運行在虛擬內存系統之上,在進程眼裏,它使用的內存是地址空間。(實際的內存情況,交給虛擬內存系統將虛擬地址映射到物理地址)
然而,在內存的角度,要讀取內存,必須使用物理內存,所以,需要一種地址轉換機制,在每次使用虛擬地址訪問時,將這個虛擬地址轉換爲物理地址,從而對內存進行訪問。
如何實現虛擬內存系統(虛擬化內存)
重定位:重定位就是把程序的邏輯地址空間變換成內存中的實際物理地址空間的過程。(其實就是地址轉換的另一個名字)
如圖,是一個進程的地址空間(左),以及將將這個地址空間裝入物理內存(右)的情況(不考慮分段的情況,先分析簡單的情況):
地址空間裝入內存後,稱爲重定位的進程(Relocated Process)
如圖所示,整個個地址空間(虛擬地址爲0KB~16KB)都被裝入物理內存(物理地址32KB~48KB)。
現在問題來了,進程訪問內存通過虛擬地址,虛擬地址1KB對應的物理地址是多少?
整個地址空間裝入之後,虛擬地址和物理地址的值不相等,因此需要對地址進行轉換(重定位),注意,地址空間裝入內存後,雖然重定位的進程的物理地址並不等於在地址空間中的虛擬地址,但是,裝入內存前後,相同的數據相對於地址空間首部和相對於重定位進程的首部,偏移量是一樣的
有了上面的對應關係,就可以解決上面的問題。地址空間首部的虛擬地址是0KB,虛擬地址1KB相對於地址空間首部的偏移量爲1KB-0KB=1KB。因此,同樣的數據在重定位進程中,相對於重定位進程首部(32KB)的偏移量也是1KB,所以虛擬地址1KB對應的物理地址爲32KB+1KB=33KB。
基於軟件的重定位(靜態重定位)
在早期,通過一個加載程序(loader)完成靜態重定位,loader將程序加載到內存,使它成爲一個進程。在加載的過程中,將虛擬地址重寫到物理內存中期望的偏移量。(也就是說,在加載過程中,將虛擬地址改爲物理地址,這也是基於偏移量不變實現的)。
舉個例子,程序中有一條指令,movl 1000, %eax
,這條指令是讀取虛擬地址爲1000的內存,將這個值放入%eax寄存器。假設進程/程序的地址空間從0開始,則偏移量爲1000;當整個地址空間被加載到從3000開始的物理地址中時,加載程序loader重寫這條指令movl 4000,%eax
。
靜態重定位有許多的問題(相比於動態重定位)。雖然使用靜態重定位可以實現虛擬內存的功能,但是沒有滿足安全和效率。
所以,需要通過動態重定位,它滿足虛擬內存的三個目標:對程序/進程透明;安全;高效率。
基於硬件的重定位(動態重定位)
動態重定位,需要依賴硬件實現地址轉換(重定位)。對於每個CPU來說,都需要兩個寄存器:基址(base)寄存器和界限(bound)寄存器。CPU中負責地址轉換的部分稱爲MMU(Memory Management Unit)。
假設程序的地址空間是從0開始(這樣,虛擬地址就是偏移量),當程序運行時,由操作系統決定這個地址空間的加載位置,並將這個位置的地址放入基址寄存器。進程每次對內存進行訪問時,會把偏移量(如果地址空間從0開始,則偏移量的值等於虛擬地址)與基址寄存器的值相加,這樣就得到了物理地址。
採用硬件支持的重定位,不僅效率更高,而且通過界限寄存器,保證內存訪問的安全。界限寄存器保存地址空間的長度,只有偏移量小於界限寄存器的虛擬地址纔可以被轉換爲物理地址,保證訪問不會越界。界限寄存器的另一種使用方式:記錄地址空間裝入內存後的邊界值,即先將偏移量和基址寄存器相加,再與界限寄存器比較。
舉個例子:
地址空間(從0開始)大小:128 KB,這個地址空間被裝入物理地址爲256KB開始的內存單元。裝入後,基址寄存器的值爲256KB,界限寄存器的值爲128KB(地址空間的大小)。如果要訪問虛擬地址爲200KB的內存單元,由於偏移量200KB-0KB超過了界限寄存器的值,所以越界了,這時,會拋出一個異常;如果要訪問虛擬地址爲100KB的內存單元,因爲偏移量100KB-0KB小於界限寄存器的值,所以沒有越界,則物理地址=偏移量+基址寄存器=100KB+256KB=356KB。
(動態重定位)硬件支持:總結
硬件要求 | 解釋 |
---|---|
特權模式 | 需要,以防用戶模式的進程執行特權操作 |
基址/界限寄存器 | 每個CPU都需要一對寄存器來支持地址轉換和界限檢查 |
能夠轉換虛擬地址並檢查是否越界 | 電路來完成地址轉換和檢查界限 |
修改基址/界限寄存器的特羣指令 | 在進程運行之前,操作系統必須能狗設置這些值 |
註冊異常處理程序的特權指令 | 操作系統必須告訴硬件,如果發生異常,執行哪些代碼 |
能夠觸發異常 | 如果進程試圖使用特權指令或越界的內存 |
硬件應該提供特權指令,用來修改基址寄存器和界限寄存器,允許操作系統在進程切換的時候改變他們。這些指令都是特權指令,只有在內核模式才能修改。(如果允許修改MMU,可能會越過自己的地址空間)
用戶程序嘗試非法訪問內存時,CPU必須能夠產生異常,並執行操作系統“預先安排”的異常處理程序。
(動態重定位)操作系統的支持
一個簡單的虛擬內存系統,除了有硬件支持外,也離不開操作系統的支持。
操作系統的要求 | 解釋 |
---|---|
內存管理 | 需要爲新進程分配內存 從終止的進程收回內存 一般通過空閒列表(free list)來管理內存 |
基址/界限管理 | 在上下文切換時正確設置和保存基址/界限寄存器 |
異常處理 | 當異常發生時執行的代碼 |
內部碎片
再來看這張圖:
如圖,將整個地址空間裝入內存後虛擬地址爲4KB~16KB的部分不會被使用,但是在物理內存中,物理地址爲36KB~46KB(虛擬地址爲4KB~16KB)的空間並不空閒,這一部分就是內部碎片。在地址空間中,內部碎片並沒有分配給進程/程序使用;但是,當整個地址空間裝入內存時,這部分不會使用的內部碎片也佔用了物理內存,因此造成了浪費。
避免內部碎片的方式就是對代碼分段,在下一篇博客介紹。
受限制的直接執行協議(動態重定位)
操作系統@啓動(內核模式) | 硬件 | 用戶空間(用戶模式) |
---|---|---|
初始化陷阱表 | 記住以下地址: 系統調用處理程序 時鐘處理程序 非法內存處理程序 非常指令處理程序 |
|
開啓中斷時鐘 | ||
開啓時鐘,在x ms後中斷 | ||
初始化進程表 初始化空閒列表 |
||
爲了啓動進程A: 在進程表中分配條目 爲進程分配內存 設置基址/界限寄存器 從陷阱返回(進入A) |
||
恢復A的寄存器 轉向用戶模式 |
||
進程A執行 獲取指令(需要讀內存) |
||
轉換虛擬地址並取指令 | ||
執行指令 | ||
如果顯式加載/保存: 確保地址不越界 轉換虛擬地址並執行 加載/保存 |
||
… | ||
時鐘中斷 轉向內核模式 跳到中斷處理程序 |
||
處理陷阱: 上下文切換(包括MMU的寄存器) 從陷阱返回(進入B) |
||
恢復B的寄存器 轉向用戶模式 |
||
進程B執行 對內存非法的訪問 |
||
觸發訪問越界 轉向內核模式 跳到陷阱處理程序 |
||
處理異常: 決定終止進程B 收回B的內存 移除進程B的PCB |