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


從程序局部性原理的描述中我們可以得出這樣一個結論:進程在運行之后,對于內存的訪問不會一下子就要訪問全部的內存,相反進程對于內存的訪問會表現出明顯的傾向性,更加傾向于訪問最近訪問過的數據以及熱點數據附近的數據 。
根據這個結論我們就清楚了 , 無論一個進程實際可以占用的內存資源有多大,根據程序局部性原理,在某一段時間內,進程真正需要的物理內存其實是很少的一部分,我們只需要為每個進程分配很少的物理內存就可以保證進程的正常執行運轉 。
而虛擬內存的引入正是要解決上述的問題,虛擬內存引入之后,進程的視角就會變得非常開闊,每個進程都擁有自己獨立的虛擬地址空間,進程與進程之間的虛擬內存地址空間是相互隔離 , 互不干擾的 。每個進程都認為自己獨占所有內存空間,自己想干什么就干什么 。

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

文章插圖
系統上還運行了哪些進程和我沒有任何關系 。這樣一來我們就可以將多進程之間協同的相關復雜細節統統交給內核中的內存管理模塊來處理,極大地解放了程序員的心智負擔 。這一切都是因為虛擬內存能夠提供內存地址空間的隔離 , 極大地擴展了可用空間 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
這樣進程就以為自己獨占了整個內存空間資源,給進程產生了所有內存資源都屬于它自己的幻覺,這其實是 CPU 和操作系統使用的一個障眼法罷了,任何一個虛擬內存里所存儲的數據,本質上還是保存在真實的物理內存里的 。只不過內核幫我們做了虛擬內存到物理內存的這一層映射,將不同進程的虛擬地址和不同內存的物理地址映射起來 。
當 CPU 訪問進程的虛擬地址時,經過地址翻譯硬件將虛擬地址轉換成不同的物理地址,這樣不同的進程運行的時候,雖然操作的是同一虛擬地址,但其實背后寫入的是不同的物理地址 , 這樣就不會沖突了 。
3. 進程虛擬內存空間上小節中,我們介紹了為了防止多進程運行時造成的內存地址沖突 , 內核引入了虛擬內存地址,為每個進程提供了一個獨立的虛擬內存空間,使得進程以為自己獨占全部內存資源 。
那么這個進程獨占的虛擬內存空間到底是什么樣子呢?在本小節中,筆者就為大家揭開這層神秘的面紗~~~
在本小節內容開始之前 , 我們先想象一下,如果我們是內核的設計人員,我們該從哪些方面來規劃進程的虛擬內存空間呢?
本小節我們只討論進程用戶態虛擬內存空間的布局 , 我們先把內核態的虛擬內存空間當做一個黑盒來看待,在后面的小節中筆者再來詳細介紹內核態相關內容 。
首先我們會想到的是一個進程運行起來是為了執行我們交代給進程的工作 , 執行這些工作的步驟我們通過程序代碼事先編寫好,然后編譯成二進制文件存放在磁盤中,CPU 會執行二進制文件中的機器碼來驅動進程的運行 。所以在進程運行之前,這些存放在二進制文件中的機器碼需要被加載進內存中,而用于存放這些機器碼的虛擬內存空間叫做代碼段 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
在程序運行起來之后,總要操作變量吧,在程序代碼中我們通常會定義大量的全局變量和靜態變量,這些全局變量在程序編譯之后也會存儲在二進制文件中,在程序運行之前 , 這些全局變量也需要被加載進內存中供程序訪問 。所以在虛擬內存空間中也需要一段區域來存儲這些全局變量 。
  • 那些在代碼中被我們指定了初始值的全局變量和靜態變量在虛擬內存空間中的存儲區域我們叫做數據段 。
  • 那些沒有指定初始值的全局變量和靜態變量在虛擬內存空間中的存儲區域我們叫做 BSS 段 。這些未初始化的全局變量被加載進內存之后會被初始化為 0 值 。

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

文章插圖
上面介紹的這些全局變量和靜態變量都是在編譯期間就確定的,但是我們程序在運行期間往往需要動態的申請內存,所以在虛擬內存空間中也需要一塊區域來存放這些動態申請的內存,這塊區域就叫做堆 。注意這里的堆指的是 OS 堆并不是 JVM 中的堆 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖

推薦閱讀