1. 程式人生 > >深入淺出記憶體管理-虛擬地址和實體地址轉換

深入淺出記憶體管理-虛擬地址和實體地址轉換

談起記憶體管理,首先我們就要搞清楚虛擬地址和實體地址的關係。本文就是簡單介紹下這兩個基礎概念。

實體地址

實體地址指實際存在的實體記憶體地址,比我有一個2G的記憶體晶片,那麼系統的實體記憶體就是2G,我要訪問該記憶體中的一個地址,那就需要對應的實體記憶體。

虛擬地址

虛擬地址,就是就是一種邏輯意義上的地址,而當我們想要訪問這個虛擬地址時,是需要轉換到實體地址才能夠真實的訪問到,比如我一個2G的記憶體,那麼虛擬地址可能會超過2G的範圍,那麼直接去實體記憶體中尋找該地址是根本不存在的,因此我們需要一個虛實的轉換,這個動作是由MMU記憶體管理單元來做的。

虛擬地址和實體地址對映

ARM32 Linux系統中每個程序都享有4G大小的虛擬地址空間,而實體地址大小要看裝置配置了多大的實體記憶體,這個是實際存在的實體記憶體,比如2G。那麼這就存在一個衝突,每個程序4G虛擬地址,怎麼對映到僅僅只有2G的實體記憶體上去呢?這就是人類聰明才智體現的地方。MMU的設計就是為了解決這種虛實對映關係的,當然MMU並非單純靠硬體即可完成關係的對映,它需要軟體的參與和配合。MMU通過頁表來對我們的地址進行轉換,那麼我們需要在MMU使能之前,把需要訪問的地址轉換頁表配置完成並告知MMU,這樣MMU就可以按照配置的頁表來自動進行地址轉換了這一點是最終靠硬體完成。

32 bit的Linux核心軟體最大支援3級對映,而ARM32硬體上最大隻支援2級對映關係,Linux核心是完全可以支援的,還是以它為例,:

 +-----------+------------+----------+
 |31       20|19        12|11       0|
 +-----------+------------+----------+
          |         |         |       
          |         |         |        
          |         |         |        
          |         |         |         
          |         |         +-----------> [0:11] in-page offset
          |         +---------------------> [19:12] 二級 index
          +-------------------------------> [31:20] 一級 index
  
TTBRx暫存器存放頁表基地址

首先我們需要建立一級頁表到一塊記憶體區域內,每個頁表項4Byte,12個bit定址,那麼這塊記憶體區域大小不能超過16K。那麼一個虛擬地址的[31:20]這12個bit就可以直接找到對應的一級頁表項(PGD)

TTBRx
存放PGD頁表基地址,4096個一級PGD頁表項,虛擬地址的[31:20] 12 bit一共能定址4096個PGD頁表項

+------+
|4 byte| PGD entry
+------+
|4 byte| PGD entry
+------+
|4 byte| PGD entry
+------+

PGD頁表項
存放二級PTE頁表基地址,一個二級頁表中存放有256個二級PTE頁表項,可以通過虛擬地址的[19:12]來進行定址二級頁表項。

 +-----------+------------+----------+
 |31                    10|9        0|           PGD entry
 +-----------+------------+----------+
            |                  |       
            |                  |        
            |                  |        
            |                  |         
            |                  +-----------> [9:0] 各種標誌位
            |         
            +-------------------------------> [31:10] 二級頁表基地址(二級頁表頁存放的實體地址範圍是12個bit)
                                                                      +------+
                                                                      |4 byte| PTE entry
                                     虛擬地址的[19:12] offset--------> +------+
                                                                      |4 byte| PTE entry
                                                                      +------+
                                                                      |4 byte| PTE entry
                                                                      +------+

PTE頁表項
存放4KB物理頁的基地址,這裡是最終要訪問的物理頁。

 +-----------+------------+----------+
 |31                    12|11        0|           PTE entry
 +-----------+------------+----------+
            |                  |       
            |                  |        
            |                  |        
            |                  |         
            |                  +-----------> [11:0] 各種標誌位
            |         
            +-------------------------------> [31:12] 實體記憶體頁基地址

由於我們配置的是4KB大小的物理頁面,那麼一個4K大小的記憶體頁,定址特定的地址就需要12個bit,恰好我們的虛擬記憶體,剩餘[11:0]是用來定址最後實體記憶體頁中的offset的。

通過這種轉換後,可以看到最後訪問的實體記憶體地址也是一個32 bit大小的地址,所以在這種平臺,最大支援4G的物理訪問地址空間。但是這種轉換帶來的好處時,我們的虛擬地址和實際的實體地址,並不是相等的,我們可以通過這種頁表的對映來把不同的實體地址對映到虛擬地址上,這樣通過核心的記憶體管理機制,我們可以給每個程序分配4G的虛擬記憶體,並且只在對應虛擬地址需要訪問時才進行實際的對映,這樣就能保證各個程序感覺自己真有4G的記憶體一樣。

如何把4G虛擬地址對映到2G的記憶體上來

這個問題我想肯定困擾過很多剛剛接觸記憶體管理的技術人員,筆者當初也是百思不得其解,經過後來的逐漸深入才見的真章。
缺頁異常
一個是4G,一個是2G,怎麼算都不會相等,其實核心的記憶體管理機制並不會立刻對這些地址進行對映,有基礎的人應該都聽說過直接對映和動態對映的概念,核心中是會劃分一部分實體記憶體作為直接對映區,這部分是會在系統啟動時直接對映到虛擬記憶體區域中,比如我們的kernel程式碼執行區域,除此之外,其餘的實體記憶體都會保留下來供核心統一管理,這些同一個管理的記憶體只會在需要的時候才會進行對映,比如當我們的程序訪問一個虛擬地址,而此時該虛擬地址恰好沒有對映,那麼就會產生缺頁異常,此異常會被核心接收到,然後記憶體管理機制就開始進行處理,把需要訪問的虛擬地址進行對映,對映到實際的實體記憶體上去。
swap機制
除了這個之外還有一個問題,系統中運行了那麼多的程序,如果不停的訪問不存在的虛擬記憶體,觸發核心的缺頁異常進行動態對映,那麼總有一刻,2G的記憶體都被對映完了,那麼此時還要繼續訪問更多虛擬地址,那麼改怎麼辦呢?此時就涉及到核心的另一個記憶體管理機制,那就是swap機制,我們知道當安裝一個Linux系統時,我們都要制定一個swap分割槽,這個分割槽就是在此時時候的,核心會把一些不常使用的記憶體頁置換出來,資料儲存到我們的硬碟上,這樣就會有空餘出來的記憶體繼續被系統映射了,那麼當其他程序要訪問被置換的資料時,系統同樣再從swap分割槽把對應資料恢復到記憶體中來。就是利用這種方式實現了2G實體記憶體當4G使用的目的。

不同程序4G的虛擬地址空間如何切換

前面我們介紹,MMU進行虛擬地址到實體地址轉換時需要頁表的,那麼不同程序都有各自的4G虛擬地址空間,那麼這些不同的區間如何劃分和切換呢?實際上針對不同的程序,核心會維護各自程序的記憶體頁表,我們核心程式碼是通過配置不同程序的記憶體頁表來完成不同程序虛擬地址切換的。當我們的CPU在進行程序排程的時候,有一個節點就是要重新設定對應程序的頁表。這樣切換到不同的程序執行不用的程式時,就能保證各自空間的獨立性。