一、 簡介
- Linux作業系統和驅動程式運作在核心空間,應用程式運作在使用者空間。兩者不能簡單地使用指針傳遞資料,因為Linux使用的虛拟記憶體機制,使用者空間的資料可能被換出,當核心空間使用使用者空間指針時,對應的資料可能不在記憶體中。使用者空間的記憶體映射采用段頁式,而核心空間有自己的規則;本文旨在探讨核心空間的位址映射。
- os配置設定給每個程序一個獨立的、連續的、虛拟記憶體空間。該大小一般是4G(32位作業系統,即2的32次方),其中将高位址值的記憶體空間配置設定給os占用,linux os占用1G,window os占用2G;其餘記憶體位址空間配置設定給程序使用。
- 通常32位Linux核心虛拟位址空間劃分0~3G為使用者空間,3~4G為核心空間(注意,核心可以使用的線性位址隻有1G)。注意這裡是32位核心位址空間劃分,64位核心位址空間劃分是不同的。
- 程序尋址空間0~4G
- 程序在使用者态隻能通路0~3G,隻有進入核心态才能通路3G~4G
- 每個程序虛拟空間的3G~4G部分是相同的
- 程序從使用者态進入核心态不會引起CR3的改變但會引起堆棧的改變
二、Linux核心高端記憶體
1、由來
當核心子產品代碼或線程通路記憶體時,代碼中的記憶體位址都為邏輯位址,而對應到真正的實體記憶體位址,需要位址一對一的映射,如邏輯位址0xC0000003對應的實體位址為0x00000003,0xC0000004對應的實體位址為0x00000004,… …,邏輯位址與實體位址對應的關系為
實體位址 = 邏輯位址 – 0xC0000000:這是核心位址空間的位址轉換關系,注意核心的虛拟位址在“高端”,但是它映射的實體記憶體位址在低端。
邏輯位址 | 實體位址 |
---|---|
0xc0000000 | 0x00000000 |
0xc0000001 | 0x00000001 |
0xc0000002 | 0x00000002 |
… | … |
0xe0000000 | 0x20000000 |
… | … |
0xffffffff | 0x40000000 |
假設按照上述簡單的位址映射關系,那麼核心邏輯位址空間通路為0xc0000000 ~ 0xffffffff,那麼對應的實體記憶體範圍就為0x00000000 ~ 0x40000000,即隻能通路1G實體記憶體。若機器中安裝8G實體記憶體,那麼核心就隻能通路前1G實體記憶體,後面7G實體記憶體将會無法通路,因為核心 的位址空間已經全部映射到實體記憶體位址範圍0x00000000 ~ 0x40000000。即使安裝了8G實體記憶體,那麼實體位址為0×40000001的記憶體,核心該怎麼去通路呢?代碼中必須要有記憶體邏輯位址 的,0xc0000000 ~ 0xffffffff的位址空間已經被用完了,是以無法通路實體位址0x40000000以後的記憶體。
顯然不能将核心位址空間0xc0000000 ~ 0xfffffff全部用來簡單的位址映射。是以x86架構中将核心位址空間劃分三部分:
- ZONE_DMA
- ZONE_NORMAL
- ZONE_HIGHMEM
ZONE_HIGHMEM即為高端記憶體,這就是記憶體高端記憶體概念的由來。
在x86結構中,三種類型的區域(從3G開始計算)如下:
- ZONE_DMA 記憶體開始的16MB
- ZONE_NORMAL 16MB~896MB
- ZONE_HIGHMEM 896MB ~ 結束(1G)
2、了解
前 面我們解釋了高端記憶體的由來。 Linux将核心位址空間劃分為三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高端記憶體HIGH_MEM位址空間範圍為 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那麼如核心是如何借助128MB高端記憶體位址空間是如何實作通路可以所有實體記憶體?
當核心想通路高于896MB實體位址記憶體時,從0xF8000000 ~ 0xFFFFFFFF位址空間範圍内找一段相應大小空閑的邏輯位址空間,借用一會。借用這段邏輯位址空間,建立映射到想通路的那段實體記憶體(即填充核心PTE頁面表),臨時用一會,用完後歸還。這樣别人也可以借用這段位址空間通路其他實體記憶體,實作了使用有限的位址空間,通路所有所有實體記憶體。如下圖。
例 如核心想通路2G開始的一段大小為1MB的實體記憶體,即實體位址範圍為0x80000000 ~ 0x800FFFFF。通路之前先找到一段1MB大小的空閑位址空間,假設找到的空閑位址空間為0xF8700000 ~ 0xF87FFFFF,用這1MB的邏輯位址空間映射到實體位址空間0x80000000 ~ 0x800FFFFF的記憶體。映射關系如下:
邏輯位址 | 實體記憶體位址 |
---|---|
0xF8700000 | 0x80000000 |
0xF8700001 | 0x80000001 |
0xF8700002 | 0x80000002 |
… | … |
0xF87FFFFF | 0x800FFFFF |
當核心通路完0x80000000 ~ 0x800FFFFF實體記憶體後,就将0xF8700000 ~ 0xF87FFFFF核心線性空間釋放。這樣其他程序或代碼也可以使用0xF8700000 ~ 0xF87FFFFF這段位址通路其他實體記憶體。
從上面的描述,我們可以知道高端記憶體的最基本思想:借一段位址空間,建立臨時位址映射,用完後釋放,達到這段位址空間可以循環使用,通路所有實體記憶體。
看到這裡,不禁有人會問:萬一有核心程序或子產品一直占用某段邏輯位址空間不釋放,怎麼辦?若真的出現的這種情況,則核心的高端記憶體位址空間越來越緊張,若都被占用不釋放,則沒有建立映射到實體記憶體都無法通路了。
3、 劃分
核心将高端記憶體劃分為3部分:
- VMALLOC_START~VMALLOC_END
- KMAP_BASE~FIXADDR_START
- FIXADDR_START~4G
對 于高端記憶體,可以通過 alloc_page() 或者其它函數獲得對應的 page,但是要想通路實際實體記憶體,還得把 page 轉為線性位址才行(為什麼?想想 MMU 是如何通路實體記憶體的),也就是說,我們需要為高端記憶體對應的 page 找一個線性空間,這個過程稱為高端記憶體映射。
對應高端記憶體的3部分,高端記憶體映射有三種方式:
-
映射到”核心動态映射空間”(noncontiguous memory allocation)
這種方式很簡單,因為通過 vmalloc() ,在”核心動态映射空間”申請記憶體的時候,就可能從高端記憶體獲得頁面(參看 vmalloc 的實作),是以說高端記憶體有可能映射到”核心動态映射空間”中。
-
持久核心映射(permanent kernel mapping)
如果是通過 alloc_page() 獲得了高端記憶體對應的 page,如何給它找個線性空間?
核心專門為此留出一塊線性空間,從 PKMAP_BASE 到 FIXADDR_START ,用于映射高端記憶體。在 2.6核心上,這個位址範圍是 4G-8M 到 4G-4M 之間。這個空間起叫”核心永久映射空間”或者”永久核心映射空間”。這個空間和其它空間使用同樣的頁目錄表,對于核心來說,就是 swapper_pg_dir,對普通程序來說,通過 CR3 寄存器指向。通常情況下,這個空間是 4M 大小,是以僅僅需要一個頁表即可,核心通過來 pkmap_page_table 尋找這個頁表。通過 kmap(),可以把一個 page 映射到這個空間來。由于這個空間是 4M 大小,最多能同時映射 1024 個 page。是以,對于不使用的的 page,及應該時從這個空間釋放掉(也就是解除映射關系),通過 kunmap() ,可以把一個 page 對應的線性位址從這個空間釋放出來。
-
臨時映射(temporary kernel mapping)
核心在 FIXADDR_START 到 FIXADDR_TOP 之間保留了一些線性空間用于特殊需求。這個空間稱為”固定映射空間”在這個空間中,有一部分用于高端記憶體的臨時映射。
這塊空間具有如下特點:
- 每個 CPU 占用一塊空間
-
在每個 CPU 占用的那塊空間中,又分為多個小空間,每個小空間大小是 1 個 page,每個小空間用于一個目的,這些目的定義在
kmap_types.h 中的 km_type 中。
當要進行一次臨時映射的時候,需要指定映射的目的,根據映射目的,可以找到對應的小空間,然後把這個空間的位址作為映射位址。這意味着一次臨時映射會導緻以前的映射被覆寫。通過 kmap_atomic() 可實作臨時映射。
三、 其他
1、使用者空間(程序)是否有高端記憶體概念?
- 使用者程序沒有高端記憶體概念。隻有在核心空間才存在高端記憶體。使用者程序最多隻可以通路3G實體記憶體,而核心程序可以通路所有實體記憶體。
2、64位核心中有高端記憶體嗎?
- 目前現實中,64位Linux核心不存在高端記憶體,因為64位核心可以支援超過512GB記憶體。若機器安裝的實體記憶體超過核心位址空間範圍,就會存在高端記憶體。
3、使用者程序能通路多少實體記憶體?核心代碼能通路多少實體記憶體?
- 32位系統使用者程序最大可以通路3GB,核心代碼可以通路所有實體記憶體。
- 64位系統使用者程序最大可以通路超過512GB,核心代碼可以通路所有實體記憶體。
4、高端記憶體和實體位址、邏輯位址、線性位址的關系?
- 高端記憶體隻和邏輯位址有關系,和邏輯位址、實體位址沒有直接關系。