推薦:《linux影片教學》
# 1. 由來 | 當核心模組程式碼或執行緒存取記憶體時,程式碼中的記憶體位址都是邏輯位址,而對應到真正的實體記憶體位址,需要位址 |
實體位址= 邏輯位址– 0xC0000000 :這是核心位址空間的位址轉換關係,注意核心的虛擬位址在“高端”,但是ta映射的實體記憶體位址在低端。 | |
邏輯位址 | |
0xc0000000 |
假 設依照上述簡單的位址映射關係,那麼核心邏輯位址空間存取為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)
前面我們解釋了高階記憶體的由來。 Linux將核心位址空間分割為三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高階記憶體HIGH_MEM位址空間範圍為 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那麼如核心是如何借助128MB高階記憶體位址空間是如何實現存取可以所有實體記憶體?
當核心想存取高於896MB實體位址記憶體時,從0xF8000000 ~ 0xFFFFFFFF位址空間範圍內找一段對應大小空閒的邏輯位址空間,借用一會。借用這段邏輯位址空間,建立映射到想存取的那段實體記憶體(即填滿核心PTE頁面表),暫時用一會,用完後歸還。這樣別人也可以藉用這段位址空間來存取其他實體內存,實現了使用有限的位址空間,存取所有所有實體記憶體。如下圖。
範例 如核心想存取2G開始的一段大小為1MB的物理內存,即物理位址範圍為0×80000000 ~ 0x800FFFFF。造訪前先找到一段1MB大小的空閒位址空間,假設找到的空閒位址空間為0xF8700000 ~ 0xF87FFFFF,用這1MB的邏輯位址空間對應到物理位址空間0×80000000 ~ 0x800FFFFF的記憶體。映射關係如下:
邏輯位址 | #物理記憶體位址 |
0xF8700000 | ##0×80000000|
#0×80000001 | |
0xF870002 | |
… | |
0xF87FFFFF |
當核心存取完0×80000000 ~ 0x800FFFFF實體記憶體後,就將0xF8700000 ~ 0xF87FFFFF核心線性空間釋放。這樣其他進程或程式碼也可以使用0xF8700000 ~ 0xF87FFFFF這段位址存取其他實體記憶體。
從上面的描述,我們可以知道高階記憶體的最基本想法:借一段位址空間,建立臨時位址映射,用完後釋放,達到這段位址空間可以循環使用,存取所有實體記憶體。
看到這裡,不禁有人會問:萬一有核心行程或模組一直佔用某段邏輯位址空間不釋放,怎麼辦?若真的出現的這種情況,則核心的高端記憶體位址空間越來越緊張,若都被佔用不釋放,則沒有建立映射到實體記憶體都無法存取了。
核心將高階記憶體劃分為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 之間保留了一些線性空間用於特殊需求。這個空間稱為”固定映射空間」在這個空間中,有一部分用於高端記憶體的臨時映射。
這塊空間有以下特點:
(1)每個CPU 佔用一塊空間
(2)在每個CPU 佔用的那塊空間中,又分成多個小空間,每個小空間大小是1 個page,每個小空間用於一個目的,這些目的定義在kmap_types.h 中的km_type 中。
當要進行一次暫時映射的時候,需要指定映射的目的,根據映射目的,可以找到對應的小空間,然後把這個空間的位址當作映射位址。這意味著一次臨時映射會導致先前的映射被覆蓋。透過 kmap_atomic() 可實現臨時映射。
1、使用者空間(進程)是否有高階記憶體概念?
使用者進程沒有高階記憶體概念。只有在核心空間才存在高端記憶體。使用者進程最多只可以存取3G物理內存,而內核進程可以存取所有實體內存。
2、64位元核心中有高階記憶體嗎?
目前現實中,64位元Linux核心不存在高階內存,因為64位元核心可以支援超過512GB記憶體。若機器安裝的實體記憶體超過核心位址空間範圍,就會存在高階記憶體。
3、使用者行程能存取多少實體記憶體?內核程式碼能存取多少實體記憶體?
32位元系統使用者行程最大可以存取3GB,核心程式碼可以存取所有實體記憶體。
64位元系統使用者進程最大可以存取超過512GB,核心程式碼可以存取所有實體記憶體。
4、高階記憶體和實體位址、邏輯位址、線性位址的關係?
高階記憶體只和邏輯位址有關係,和邏輯位址、實體位址沒有直接關係。
以上是詳解Linux的使用者空間與核心空間的詳細內容。更多資訊請關注PHP中文網其他相關文章!