漫畫解說 “內存映射”
本文轉載自微信公眾號「Linux內核那些事」,作者songsong001。轉載本文請聯系Linux內核那些事公眾號。
虛擬內存空間與物理內存空間
虛擬內存地址就好比每個班的學號,而物理內存地址就好比真實的學生。因為每個學號都對應不同的學生,所以虛擬內存地址也要映射到物理內存地址。
虛擬內存與物理內存的映射關系是通過 頁表 來關聯的,如下圖:
但 頁表 并不是按字節來進行映射的,而是按照 內存頁 為單位進行映射,一般一個 內存頁 的大小為 4KB(為什么要加一般呢,這是因為除了4KB,還有其他大小的內存頁,如2MB,4MB,1GB等),頁表 的每一個 頁表項 都保存著物理內存頁的地址。
所以,4GB 的虛擬內存空間需要 1MB 大小的頁表來關聯(因為 4GB / 4KB = 1MB)。也就是說,0 ~ 4095 的虛擬內存地址都是使用 頁表 的第一個 頁表項 來映射的,而 4096 ~ 8191 的虛擬內存地址使用 頁表 的第二個 頁表項 來映射的,以此類推...
那么,通過什么樣的算法能把 0 ~ 4095 的虛擬內存地址轉換為頁表的第一個頁表項呢?其實很簡單,只需要把虛擬內存地址的高端 20 位作為頁表的索引,而把低端 12 位作為內存頁中的偏移量即可,如下圖:
在上圖中,還看到了一個 cr3 的東西,這是 CPU 中的一個寄存器,用于保存 頁表 的物理內存地址,通過這個寄存器就能找到進程的 頁表 了。
現在對內存映射的原理有了比較清晰的了解了,但現在有個問題,每個進程都要 1MB 大小的頁表,那不是很浪費內存嗎?的確是,因為進程很多虛擬內存地址并不會用到,為了節省頁表使用的內存,x86 CPU 把頁表分為 2 級,如下圖:
如上圖所示,把原來的 頁表 劃分為 頁目錄 和 頁表,它們的大小均為 4KB。而虛擬內存地址的高 10 位作為頁目錄的索引,而中間 10 位作為頁表的索引,低 12 位還是作為物理內存頁的偏移量。
把原來的 頁表 劃分為兩級后,進程有些不使用的虛擬內存地址就不需要進行映射,從而節省了內存的使用。