linux內存映射

  內存管理分爲對連續物理內存區管理和非連續內存區管理,本文主要講解連續的物理內存區管理的技術中所涉及到的內核線性地址空間映射的相關知識。涉及到的東西有:頁框,管理區(高端內存,低端內存),高端內存映射等,這些知識是掌握夥伴系統算法和slab分配器的基礎。html

1、頁框   linux

  頁框爲物理內存分配的基本單元,現代32位計算機通常設置爲4KB(見上文計算機內存尋址)。內核必須記錄每一個頁框的當前狀態,例如:頁框屬於哪一個常常的頁,哪些頁框抱哈的是內核代碼或內核數據等,由此linux定義了一個頁框的描述符:page。linux用page來保存頁框的狀態信息,全部的page都存放在mem_map數組中,以便管理內存。算法

2、管理區  數組

  很顯然,若是計算機對內存的使用沒有區別,那麼直接使用頁框管理內存是最爲方便的。可是,因爲計算機體系結構的的硬件限制:app

  (1)ISA總線的直接內存存取(DMA)處理器只能對隨機存儲器(RAM)的前16MB尋址。ide

  (2)具備大容量的RAM的32位計算機中,CPU不能直接訪問全部的物理內存!函數

  因此,因爲上述的緣由,Linux將物理內存劃分爲3個管理區:this

  (1)ZONE_DMA:包含低於16M的內存頁框,只有它可以做用於DMA;atom

  (2)ZONE_NORMAL:包含高於16M且低於896M的內存頁框;spa

  (3)ZONE_HIGHMEM:包含從896M開始高於896M的內存頁框。

  ZONE_DMA和ZONE_NORMAL咱們通常稱之爲低端內存(896MB),ZONE_HIGHMEM稱之爲高端內存。高、低端內存的分類主要在於區分物理內存地址是否能夠直接映射到內核線性地址空間中。問題來了,高、低端內存和內核的線性空間的映射是什麼樣的呢?

  在32位系統中,線性地址空間是4G,其中規定3~4G的範圍是內核空間(1G),0~3G是用戶空間(3G)。若是把這1G的內核線性地址空間所有拿來直接一一映射物理內存的話,在內核態的全部進程(線程)能使用的物理內存總共最多隻有1G,很顯然,若是你有4G內存,3G都不能用來作內核空間,太浪費了!

  爲了能使在內核態的全部進程能使用更多的物理內存,linux採起了一種變通的形式:它將1G內核線性地址空間分爲2部分,第一部分爲1G的前896M,這部份內核線性空間與物理內存的0~896M一一映射,第二部分爲1G的後128M的線性空間,拿來動態映射剩下的全部物理內存。

  看到這裏應該幾乎懂了爲何要有高端內存和低端內存的區分了!內核線性地址空間的前896M映射的就是咱們的低端頁框內存(ZONE_DMA和ZONG_NORMAL),並且是直接映射;內核線性地址空間的後128M映射的是咱們的高端頁框內存(ZONG_HIGHMEME)。

  上述的三個管理區,每一個管理區都有本身的管理區描述符,管理區描述符的地址存放在zone_table數組中。內核調用一個內存分配函數時,必須指明請求頁框所在的管理區。

  爲知足內存分配請求,內存管理區使用兩種方式來保證:

  (1)當空閒內存不足時,發出請求的內核控制路徑阻塞,知道有內存釋放,才分配。

  (2)若是內核控制路徑不能被阻塞(原子內存分配請求),就從頁框池中分配內存。

  頁框池爲內存保留的內存,由ZONE_DMA和ZONE_NORMAL來共同分配,分配的頁框池大小爲:sqrt(16*直接映射內存) KB

3、高端內存頁框的內核映射

  在前面有提到,內核線性地址空間的最後128M用來映射高端內存頁框,其方式有三種:永久內核映射,臨時內核映射,非連續內存分配。因爲本文只講連續內存分配,最後一種映射方式就在下一篇blog中講。

一、永久內核映射

  永久內核映射容許內核創建高端頁框到內核地址空間的長期映射。他使用主內核頁表中一個專門的頁表,其頁表地址存放在pkmap_page_table中,頁表包含512項或1024項,所以,內核一次最多訪問2M或4M的高端內存(地址範圍是 4G-8M 到 4G-4M 之間,這個地址空間起叫「內核永久映射空間」或者「永久內核映射空間」)。

  永久內核映射經過kmap()函數創建,代碼:

 1 void *kmap(struct page* page)  2 {  3     if(!PageHighMem(page))//判斷是不是高端內存
 4        return page_address(page);  5     return kmap_high(page);  6 }  7 
 8 void *kmap_high(struct page *page)  9 { 10     unsigned long vaddr; 11     spin_lock(&kmap_lock);//自旋鎖
12    vaddr = (unsigned long) page_address(page); 13     if(vaddr) 14         vaddr = map_new_virtual(page); 15     pkmap_count[(vaddr - PKMAP_BASE) >> PAGE_SHIFT]++;//pkamp_count數組包含LAST_PKAMP個計數器
16     spin_unlock(&kmap_lock); 17      return (void *) vaddr; 18 }
kmap()

  對於不使用的的 page,及應該時從這個空間釋放掉(也就是解除映射關係),經過 kunmap() ,能夠把一個 page 對應的線性地址從這個空間釋放出來。

二、臨時內核映射
  臨時內核映射可用在中斷處理程序和可延遲函數的內部,它從不阻塞當前進程。

  內核在 FIXADDR_START 到 FIXADDR_TOP 之間保留了一些線性空間用於特殊需求。這個空間稱爲「固定映射空間」,在這個空間中,有一部分用於高端內存的臨時映射。這塊空間具備以下特色:

  (1) 每一個 CPU 佔用一塊空間;

  (2) 在每一個 CPU 佔用的那塊空間中,又分爲多個小空間,每一個小空間大小是 1 個 page,每一個小空間用於一個目的,這些目的定義在 kmap_types.h 中的km_type 中。

  當要進行一次臨時映射的時候,須要指定映射的目的,根據映射目的,能夠找到對應的小空間,而後把這個空間的地址做爲映射地址。這意味着一次臨時映射會致使之前的映射被覆蓋。

  經過 kmap_atomic() 可實現臨時映射。代碼

 

 1 view plaincopy to clipboardprint?用數學公式來避免混亂,他空間有限且虛擬地址固定,這意味着他映射的內存空間不能被  
 2 長時間佔用,而不被unmap,在效率上比kmap提高很多,然而他和kmap不是用於統一  
 3 場合的,  
 4 **/  
 5 void *kmap_atomic(struct page *page, enum km_type type)  
 6 {  
 7     return kmap_atomic_prot(page, type, kmap_prot);  
 8 }  
 9 用數學公式來避免混亂,他空間有限且虛擬地址固定,這意味着他映射的內存空間不能被
10 長時間佔用,而不被unmap,在效率上比kmap提高很多,然而他和kmap不是用於統一
11 場合的,
12 **/
13 void *kmap_atomic(struct page *page, enum km_type type)
14 {
15         return kmap_atomic_prot(page, type, kmap_prot);
16 }view plaincopy to clipboardprint?/* 
17 * kmap_atomic/kunmap_atomic is significantly faster than kmap/kunmap because 
18 * no global lock is needed and because the kmap code must perform a global TLB 
19 * invalidation when the kmap pool wraps. 
20 * 
21 * However when holding an atomic kmap it is not legal to sleep, so atomic 
22 * kmaps are appropriate for short, tight code paths only. 
23 */  
24 void *kmap_atomic_prot(struct page *page, enum km_type type, pgprot_t prot)  
25 {  
26     enum fixed_addresses idx;  
27     unsigned long vaddr;  
28   
29     /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */  
30     /**原子映射是基於每一個cpu的,所以在當前cpu上應用搶佔,直到unmap的時候才 
31     開啓,這樣不會致使原子映射的重入了, 
32     */  
33     pagefault_disable();  
34   
35     if (!PageHighMem(page))  
36         return page_address(page);  
37   
38     /* 遞增type,保證下面公式起做用 */  
39     debug_kmap_atomic(type);  
40     /* 
41     kernel能夠在多個cpu上同時運行不一樣的task,然而他們共同使用一個內存地址空間, 
42     也就是說,內存空間對於多個cpu看到的是同一個,該函數使用的是地址空間中頂部的 
43     一小段地址空間,也就是臨時映射區,內核邏輯將這一小段地址空間分紅若干各節 
44     每一節的大小是一個頁面的大小,能夠映射一個頁面,根據公用地址空間的原理 
45     全部的cpu共同使用這些節,所以如何能保證N個cpu調用此函數不會將page映射到   一個地址呢,這就是這個數學公式所起到的做用 
46     */  
47     idx = type + KM_TYPE_NR*smp_processor_id();  
48     vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);  
49     /*這裏爲何是減法 
50     越靠前的枚舉項對應的線性地址越靠後*/  
51     BUG_ON(!pte_none(*(kmap_pte-idx)));  
52     /*設置pte*/  
53     set_pte(kmap_pte-idx, mk_pte(page, prot));  
54   
55     return (void *)vaddr;  
56 }  
57 /*
58 * kmap_atomic/kunmap_atomic is significantly faster than kmap/kunmap because
59 * no global lock is needed and because the kmap code must perform a global TLB
60 * invalidation when the kmap pool wraps.
61 *
62 * However when holding an atomic kmap it is not legal to sleep, so atomic
63 * kmaps are appropriate for short, tight code paths only.
64 */
65 void *kmap_atomic_prot(struct page *page, enum km_type type, pgprot_t prot)
66 {
67         enum fixed_addresses idx;
68         unsigned long vaddr;
69 
70         /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */
71         /**原子映射是基於每一個cpu的,所以在當前cpu上應用搶佔,直到unmap的時候才
72         開啓,這樣不會致使原子映射的重入了,
73         */
74         pagefault_disable();
75 
76         if (!PageHighMem(page))
77                 return page_address(page);
78 
79         /* 遞增type,保證下面公式起做用 */
80         debug_kmap_atomic(type);
81         /*
82         kernel能夠在多個cpu上同時運行不一樣的task,然而他們共同使用一個內存地址空間,
83         也就是說,內存空間對於多個cpu看到的是同一個,該函數使用的是地址空間中頂部的
84         一小段地址空間,也就是臨時映射區,內核邏輯將這一小段地址空間分紅若干各節
85         每一節的大小是一個頁面的大小,能夠映射一個頁面,根據公用地址空間的原理
86         全部的cpu共同使用這些節,所以如何能保證N個cpu調用此函數不會將page映射到        一個地址呢,這就是這個數學公式所起到的做用
87         */
88         idx = type + KM_TYPE_NR*smp_processor_id();
89         vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
90         /*這裏爲何是減法
91         越靠前的枚舉項對應的線性地址越靠後*/
92         BUG_ON(!pte_none(*(kmap_pte-idx)));
93         /*設置pte*/
94         set_pte(kmap_pte-idx, mk_pte(page, prot));
95 
96         return (void *)vaddr;
97 }view plaincopy to clipboardprint?#define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT))  
98 #define __fix_to_virt(x)        (FIXADDR_TOP - ((x) << PAGE_SHIFT)) 
kmap_atomic

 

 

 

 最後,給一張頁框頁管理區的圖,加深內核線性地址空間映射的印象:

 

參考:

一、深刻理解Linux內核

二、Linux 內存管理 -- 高端內存的映射方式 http://blog.csdn.net/chenziwen/article/details/5932396

三、Linux內核--內核地址空間分佈和進程地址空間 http://www.360doc.com/content/12/1022/13/6828497_243054228.shtml

四、linux內存管理淺析 http://hi.baidu.com/_kouu/item/4c73532902a05299b73263d0

相關文章
相關標籤/搜索