一步一圖帶你深入理解 Linux 虛擬內存管理( 二 )


說了這么多,那么到底虛擬內存地址長什么樣子呢?
我們還是以日常生活中的收貨地址為例做出類比,我們都很熟悉收貨地址的格式:xx省xx市xx區xx街道xx小區xx室,它是按照地區層次遞進的 。同樣,在計算機世界中的虛擬內存地址也有這樣的遞進關系 。
這里我們以 Intel Core i7 處理器為例,64 位虛擬地址的格式為:全局頁目錄項(9位)+ 上層頁目錄項(9位)+ 中間頁目錄項(9位)+ 頁內偏移(12位) 。共 48 位組成的虛擬內存地址 。

一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
虛擬內存地址中的全局頁目錄項就類比我們日常生活中收獲地址里的?。喜鬩襯柯枷罹屠啾仁?,中間層頁目錄項類比區縣 , 頁表項類比街道小區,頁內偏移類比我們所在的樓棟和幾層幾號 。
這里大家只需要大體明白虛擬內存地址到底長什么樣子,它的格式是什么,能夠和日常生活中的收貨地址對比理解起來就可以了,至于頁目錄項 , 頁表項以及頁內偏移這些計算機世界中的概念,大家暫時先不用管,后續文章中筆者會慢慢給大家解釋清楚 。
32 位虛擬地址的格式為:頁目錄項(10位)+ 頁表項(10位) + 頁內偏移(12位) 。共 32 位組成的虛擬內存地址 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
進程虛擬內存空間中的每一個字節都有與其對應的虛擬內存地址,一個虛擬內存地址表示進程虛擬內存空間中的一個特定的字節 。
2. 為什么要使用虛擬地址訪問內存經過第一小節的介紹,我們現在明白了計算機世界中的虛擬內存地址的含義及其展現形式 。那么大家可能會問了,既然物理內存地址可以直接定位到數據在內存中的存儲位置,那為什么我們不直接使用物理內存地址去訪問內存而是選擇用虛擬內存地址去訪問內存呢?
在回答大家的這個疑問之前 , 讓我們先來看下,如果在程序中直接使用物理內存地址會發生什么情況?
假設現在沒有虛擬內存地址,我們在程序中對內存的操作全都都是使用物理內存地址,在這種情況下,程序員就需要精確的知道每一個變量在內存中的具體位置,我們需要手動對物理內存進行布局,明確哪些數據存儲在內存的哪些位置,除此之外我們還需要考慮為每個進程究竟要分配多少內存?內存緊張的時候該怎么辦?如何避免進程與進程之間的地址沖突?等等一系列復雜且瑣碎的細節 。
如果我們在單進程系統中比如嵌入式設備上開發應用程序,系統中只有一個進程,這單個進程獨享所有的物理資源包括內存資源 。在這種情況下 , 上述提到的這些直接使用物理內存的問題可能還好處理一些,但是仍然具有很高的開發門檻 。
然而在現代操作系統中往往支持多個進程,需要處理多進程之間的協同問題,在多進程系統中直接使用物理內存地址操作內存所帶來的上述問題就變得非常復雜了 。
這里筆者為大家舉一個簡單的例子來說明在多進程系統中直接使用物理內存地址的復雜性 。
比如我們現在有這樣一個簡單的 Java 程序 。
public static void main(String[] args) throws Exception {string i = args[0];..........}在程序代碼相同的情況下,我們用這份代碼同時啟動三個 JVM 進程,我們暫時將進程依次命名為 a , b , c。
這三個進程用到的代碼是一樣的,都是我們提前寫好的 , 可以被多次運行 。由于我們是直接操作物理內存地址,假設變量 i 保存在 0x354 這個物理地址上 。這三個進程運行起來之后,同時操作這個 0x354 物理地址,這樣這個變量 i 的值不就混亂了嗎? 三個進程就會出現變量的地址沖突 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
所以在直接操作物理內存的情況下 , 我們需要知道每一個變量的位置都被安排在了哪里,而且還要注意和多個進程同時運行的時候,不能共用同一個地址 , 否則就會造成地址沖突 。
現實中一個程序會有很多的變量和函數,這樣一來我們給它們都需要計算一個合理的位置,還不能與其他進程沖突 , 這就很復雜了 。
那么我們該如何解決這個問題呢?程序的局部性原理再一次救了我們~~
程序局部性原理表現為:時間局部性和空間局部性 。時間局部性是指如果程序中的某條指令一旦執行,則不久之后該指令可能再次被執行;如果某塊數據被訪問,則不久之后該數據可能再次被訪問 ??臻g局部性是指一旦程序訪問了某個存儲單元 , 則不久之后,其附近的存儲單元也將被訪問 。

推薦閱讀