Linux核心地址空間劃分
通常32位Linux核心虛擬位址空間劃分0~3G為使用者空間,3~4G為核心空間(注意,核心可以使用的線性地址只有1G)。注意這裡是32位核心地址空間劃分,64位核心地址空間劃分是不同的。
通常32位Linux核心虛擬位址空間劃分0~3G為使用者空間,3~4G為核心空間(注意,核心可以使用的線性地址只有1G)。注意這裡是32位核心地址空間劃分,64位核心地址空間劃分是不同的。
Linux核心高端記憶體的由來
當核心模組代碼或線程訪問記憶體時,代碼中的記憶體位址都為邏輯地址,而對應到真正的實體記憶體地址,需要地址一對一的映射,如邏輯地址0xc0000003對應的物理地址為0×3,0xc0000004對應的物理地址為0×4,… …,邏輯地址與物理地址對應的關係為
物理地址 = 邏輯地址 – 0xC0000000:這是核心地址空間的地址轉換關係,注意核心的虛擬位址在“高端”,但是ta映射的實體記憶體地址在低端。
實際上,“核心直接映射空間”也達不到 1G, 還得留點線性空間給“核心動態映射空間” 呢。
因此,Linux 規定“核心直接映射空間” 最多映射 896M 實體記憶體。
對於高端記憶體,可以通過 alloc_page() 或者其它函數獲得對應的 page,但是要想訪問實際實體記憶體,還得把 page 轉為線性地址才行(為什麼?想想 MMU 是如何訪問實體記憶體的),也就是說,我們需要為高端記憶體對應的 page 找一個線性空間,這個過程稱為高端記憶體映射。
假 設按照上述簡單的地址映射關係,那麼核心邏輯地址空間訪問為0xc0000000 ~ 0xffffffff,那麼對應的實體記憶體範圍就為0×0 ~ 0×40000000,即只能訪問1G實體記憶體。若機器中安裝8G實體記憶體,那麼核心就只能訪問前1G實體記憶體,後面7G實體記憶體將會無法訪問,因為核心 的地址空間已經全部映射到實體記憶體位址範圍0×0 ~ 0×40000000。即使安裝了8G實體記憶體,那麼物理地址為0×40000001的記憶體,核心該怎麼去訪問呢?代碼中必須要有記憶體邏輯地址 的,0xc0000000 ~ 0xffffffff的地址空間已經被用完了,所以無法訪問物理地址0×40000000以後的記憶體。
顯 然不能將核心地址空間0xc0000000 ~ 0xfffffff全部用來簡單的地址映射。因此x86架構中將核心地址空間劃分三部分:ZONE_DMA、ZONE_NORMAL和 ZONE_HIGHMEM。ZONE_HIGHMEM即為高端記憶體,這就是記憶體高端記憶體概念的由來。
在x86結構中,三種類型的地區(從3G開始計算)如下:
ZONE_DMA 記憶體開始的16MB
ZONE_NORMAL 16MB~896MB
ZONE_HIGHMEM 896MB ~ 結束(1G)
高端記憶體是指物理地址大於 896M 的記憶體。對於這樣的記憶體,無法在“核心直接映射空間”進行映射。
為什麼?
因為“核心直接映射空間”最多隻能從 3G 到 4G,只能直接映射 1G 實體記憶體,對於大於 1G 的實體記憶體,無能為力。
高端記憶體映射有三種方式:
1、映射到“核心動態映射空間”
這種方式很簡單,因為通過 vmalloc() ,在“核心動態映射空間”申請記憶體的時候,就可能從高端記憶體獲得頁面(參看 vmalloc 的實現),因此說高端記憶體有可能映射到“核心動態映射空間” 中。
2、永久核心映射
如果是通過 alloc_page() 獲得了高端記憶體對應的 page,如何給它找個線性空間?
核心專門為此留出一塊線性空間,從 PKMAP_BASE 到 FIXADDR_START ,用於映射高端記憶體。在 2.4 核心上,這個位址範圍是 4G-8M 到 4G-4M 之間。這個空間起叫“核心永久映射空間”或者“永久核心映射空間”
這個空間和其它空間使用同樣的頁目錄表,對於核心來說,就是 swapper_pg_dir,對普通進程來說,通過 CR3 寄存器指向。
通常情況下,這個空間是 4M 大小,因此僅僅需要一個頁表即可,核心通過來 pkmap_page_table 尋找這個頁表。
通過 kmap(), 可以把一個 page 映射到這個空間來
由於這個空間是 4M 大小,最多能同時映射 1024 個 page。因此,對於不使用的的 page,應該及時從這個空間釋放掉(也除映射關就是解系),通過 kunmap() ,可以把一個 page 對應的線性地址從這個空間釋放出來。
3、臨時映射
核心在 FIXADDR_START 到 FIXADDR_TOP 之間保留了一些線性空間用於特殊需求。這個空間稱為“固定映射空間”
在這個空間中,有一部分用於高端記憶體的臨時映射。
這塊空間具有如下特點:
1、 每個 CPU 佔用一塊空間
2、 在每個 CPU 佔用的那塊空間中,又分為多個小空間,每個小空間大小是 1 個 page,每個小空間用於一個目的,這些目的定義在 kmap_types.h 中的 km_type 中。
當要進行一次臨時映射的時候,需要指定映射的目的,根據映射目的,可以找到對應的小空間,然後把這個空間的地址作為映射地址。這意味著一次臨時映射會導致以前的映射被覆蓋。
通過 kmap_atomic() 可實現臨時映射。
下圖簡單簡單表達如何對高端記憶體進行映射
Linux記憶體線性地址空間大小為4GB,分為2個部分:使用者空間部分(通常是3G)和核心空間部分(通常是1G)。在此我們主要關注核心地址空間部分。
核心通過核心頁全域目錄來管理所有的實體記憶體,由於線性地址前3G空間為使用者使用,核心頁全域目錄前768項(剛好3G)除0、1兩項外全部為0,後256項(1G)用來管理所有的實體記憶體。核心頁全域目錄在編譯時間靜態地定義為swapper_pg_dir數組,該數組從實體記憶體地址0x101000處開始存放。
由圖可見,核心線性地址空間部分從PAGE_OFFSET(通常定義為3G)開始,為了將核心裝入記憶體,從PAGE_OFFSET開始8M線性地址用來映射核心所在的實體記憶體地址(也可以說是核心所在虛擬位址是從PAGE_OFFSET開始的);接下來是mem_map數組,mem_map的起始線性地址與體繫結構相關,比如對於UMA結構,由於從PAGE_OFFSET開始16M線性地址空間對應的16M物理地址空間是DMA區,mem_map數組通常開始於PAGE_OFFSET+16M的線性地址;從PAGE_OFFSET開始到VMALLOC_START – VMALLOC_OFFSET的線性地址空間直接映射到實體記憶體空間(一一對應影射,物理地址<==>線性地址-PAGE_OFFSET),這段地區的大小和機器實際擁有的實體記憶體大小有關,這兒VMALLOC_OFFSET在X86上為8M,主要用來防止越界錯誤;在記憶體比較小的系統上,餘下的線性地址空間(還要再減去空白區即VMALLOC_OFFSET)被vmalloc()函數用來把不連續的物理地址空間映射到連續的線性地址空間上,在記憶體比較大的系統上,vmalloc()使用從VMALLOC_START到VMALLOC_END(也即PKMAP_BASE減去2頁的空白頁大小PAGE_SIZE(解釋VMALLOC_END))的線性地址空間,此時餘下的線性地址空間(還要再減去2頁的空白區即VMALLOC_OFFSET)又可以分成2部分:第一部分從PKMAP_BASE到FIXADDR_START用來由kmap()函數來建立永久映射高端記憶體;第二部分,從FIXADDR_START到FIXADDR_TOP,這是一個固定大小的臨時映射線性地址空間,(引用:Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time such as the APIC),在X86體繫結構上,FIXADDR_TOP被靜態定義為0xFFFFE000,此時這個固定大小空間結束於整個線性地址空間最後4K前面,該固定大小空間大小是在編譯時間計算出來並儲存在__FIXADDR_SIZE變數中。
正是由於vmalloc()使用區、kmap()使用區及固定大小區(kmap_atomic()使用區)的存在才使ZONE_NORMAL區大小受到限制,由於核心在運行時需要這些函數,因此線上性地址空間中至少要VMALLOC_RESERVE大小的空間。VMALLOC_RESERVE的大小與體繫結構相關,在X86上,VMALLOC_RESERVE定義為128M,這就是為什麼ZONE_NORMAL大小通常是16M到896M的原因。