看了Linux內核內存管理,參考網上的意見整理了一下。
1.頁框管理
Linux采用4KB頁框大小作為標准的內存分配單元。內核必須記錄每個頁框的狀態,這種狀態信息保存在一個類型為page的頁描述符中,所有的頁描述存放在mem_map中。virt_to_page(addr)產生線性地址對應的頁描述符地址。pfn_to_page(pfn)產生對應頁框號的頁描述符地址。
在頁框描述符中,幾個關鍵的字段我認為:flags、_count、_mapcount。
由於CPU對內存的非一致性訪問,系統的物理內存被劃分為幾個節點(每個節點的描述符為pg_data_t),每個節點的物理內存又可以分為3個管理區:ZONE_DMA(低於16M的頁框地址),ZONE_NORMAL(16MB-896MB的頁框地址)和ZONE_HIGHMEM(高於896MB的頁框地址)。
每個管理區又有自己的描述符,描述了該管理區空閒的頁框,保留頁數目等。每個頁描述符都有到內存節點和到節點管理區的連接(被放在flag的高位字段)。
內核調用一個內存分配函數時,必須指明請求頁框所在的管理區,內核通常指明它願意使用哪個管理區。
2.保留的頁框池
如果有足夠的空閒內存可用、請求就會被立刻滿足。否則,必須回收一些內存,並且將發出請求的內核控制路徑阻塞,直到有內存被釋放。但是有些控制路徑不能被阻塞,例如一些內核路徑產生一些原子內存分配請求。盡管無法保證一個原子內存分配請求不失敗,但是內核會減少這中概率。為了做到如此,內核采取的方案為原子內存分配請求保留一個頁框池,只有在內存不足時才使用。頁框池有ZONE_DMA和ZONE_NORMAL兩個區貢獻出一些頁框。
常用的請求頁框和釋放頁框函數:
alloc_pages(gfp_mask, order): 獲得連續的頁框,返回頁描述符地址,是其他類型內存分配的基礎。
__get_free_pages(gfp_mask, order): 獲得連續的頁框,返回頁框對應的線性地址。線性地址與物理地址是內核直接映射方式。不能用於大於896M的高端內存。
__free_pages(page,order);
__free_pages(addr,order);
3.高端內存頁框的內核映射
高端內存是指物理地址大於 896M 的內存。對於這樣的內存,無法在“內核直接映射空間”進行映射。因為“內核直接映射空間”最多只能從 3G 到 4G,只能直接映射 1G 物理內存,對於大於 1G 的物理內存,無能為力。實際上,“內核直接映射空間”也達不到 1G, 還得留點線性空間給“內核動態映射空間” 呢。因此,Linux 規定“內核直接映射空間” 最多映射 896M 物理內存。
對於高端內存,可以通過 alloc_page() 或者其它函數獲得對應的 page,但是要想訪問實際物理內存,還得把 page 轉為線性地址才行(為什麼?想想 MMU 是如何訪問物理內存的),也就是說,我們需要為高端內存對應的 page 找一個線性空間,這個過程稱為高端內存映射。高端內存映射有三種方式:
(1)永久內核映射
永久內核映射允許內核建立到高端頁框內核地址空間的長期映射。當空閒頁表項不存在時,也就是高端內存中沒有頁表項用用作頁框的“窗口”時,永久內核映射可能阻塞當前進程。因此永久內核映射不能用用於中斷處理程序和可延遲函數。
永久內核映射使用主內核頁表中的一個專門頁表,其地址存放在pkmap_page_table中。頁表的表項有LAST_PKMAP產生。該頁表映射的線性地址為從PKMAP_BASE開始,即內核專門為此留出一塊線性空間,從 PKMAP_BASE 到 FIXADDR_START ,用於映射高端內存。在 2.4 內核上,這個地址范圍是 4G-8M 到 4G-4M 之間。這個空間起叫“內核永久映射空間”或者“永久內核映射空間”
如果是通過 alloc_page() 獲得了高端內存對應的 page,如何給它找個線性空間?(就是上面的PKMAP_BASE 到 FIXADDR_START)。
這個空間和其它空間使用同樣的頁目錄表,對於內核來說,就是 swapper_pg_dir,對普通進程來說,通過 CR3 寄存器指向。
通常情況下,這個空間是 4M 大小,因此僅僅需要一個頁表即可,內核通過來 pkmap_page_table 尋找這個頁表。
通過 kmap(), 可以把一個 page 映射到這個空間來。
由於這個空間是 4M 大小,最多能同時映射 1024 個 page。因此,對於不使用的的 page,及應該時從這個空間釋放掉(也就是解除映射關系),通過 kunmap() ,可以把一個 page 對應的線性地址從這個空間釋放出來。
(2)臨時內核映射
建立臨時映射決不會要求阻塞檔期進程,不過,它的缺點就是只有很少的臨時內核映射可以同時建立起來。使用臨時內核映射必須保證沒有其他的內核控制路徑使用同樣的映射。
內核在 FIXADDR_START 到 FIXADDR_TOP 之間保留了一些線性空間用於特殊需求。這個空間稱為“固定映射空間“, 在這個空間中,有一部分用於高端內存的臨時映射。這塊空間具有如下特點: 每個 CPU 占用一塊空間;在每個 CPU 占用的那塊空間中,又分為多個小空間,每個小空間大小是 1 個 page,每個小空間用於一個目的,這些目的定義在 kmap_types.h 中的 km_type 中。
當要進行一次臨時映射的時候,需要指定映射的目的,根據映射目的,可以找到對應的小空間,然後把這個空間的地址作為映射地址。這意味著一次臨時映射會導致以前的映射被覆蓋。
通過kmap_atomic()可實現臨時映射。
(3)映射到“內核動態映射空間”
這種方式很簡單,因為通過 vmalloc() ,在“內核動態映射空間”申請內存的時候,就可能從高端內存獲得頁面(參看 vmalloc 的實現),因此說高端內存有可能映射到“內核動態映射空間” 中。
4.下圖簡單簡單表達如何對高端內存進行映射
注:Linux內核中采用了一種同時適用於32位和64位系統的內存分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表