轉載:各種內存申請函數解析

轉載自:

http://blog.csdn.net/gxfan/article/details/2723455

http://blog.csdn.net/xiaojsj111/article/details/11817587

http://blog.csdn.net/decload/article/details/8080533

內存申請函數解析


//+++++++++++++++++++++++++++++++++++++++++++

1、  kmalloc()是內核中最常見的內存分配方式,它最終調用夥伴系統的__get_free_pages()函數分配,根據傳遞給這個函數的flags參數,決定這個函數的分配適合什麼場合,如果標誌是GFP_KERNEL則僅僅可以用於進程上下文中,如果標誌GFP_ATOMIC則可以用於中斷上下文或者持有鎖的代碼段中。

kmalloc返回的線形地址是直接映射的,而且用連續物理頁滿足分配請求,且內置了最大請求數(2**5=32頁)。

 

2、  kmap()是主要用在高端存儲器頁框的內核映射中,一般是這麼使用的:
使用alloc_pages()在高端存儲器區得到struct page結構,然後調用kmap(struct *page)在內核地址空間PAGE_OFFSET+896M之後的地址空間中(PKMAP_BASE到FIXADDR_STAR)建立永久映射(如果page結構對應的是低端物理內存的頁,該函數僅僅返回該頁對應的虛擬地址
)
kmap()也可能引起睡眠,所以不能用在中斷和持有鎖的代碼中

不過kmap 只能對一個物理頁進行分配,所以儘量少用。

 

3、  vmalloc優先使用高端物理內存,但性能上會打些折扣。

vmalloc分配的物理頁不會被交換出去;
vmalloc返回的虛地址大於(PAGE_OFFSET + SIZEOF(phys memory) + GAP),爲VMALLOC_START----VMALLOC_END之間的線形地址
;
vmalloc使用的是vmlist鏈表,與管理用戶進程的vm_area_struct要區別,而後者會swapped;

 

4、  使用kmap的原因:
對於高端物理內存(896M之後),並沒有和內核地址空間建立一一對應的關係(即虛擬地址=物理地址+PAGE_OFFSET這樣的關係),所以不能使用get_free_pages()這樣的頁分配器進行內存的分配,而必須使用alloc_pages()這樣的夥伴系統算法的接口得到struct *page結構,然後將其映射到內核地址空間,注意這個時候映射後的地址並非和物理地址相差PAGE_OFFSET.


//+++++++++++++++++++++++++++++++++++++++++++

kmap的實現分析

kmap/unkmap系統調用是用來映射高端物理內存頁到內核地址空間的api函數,他們分配的內核虛擬地址範圍屬於[PKMAP_BASE,PAGE_OFFSET]即[0xbfe00000,0xc0000000]範圍,大小是2M的虛擬空間,爲了映射該塊虛擬地址,所使用的二級頁表的大小剛好是一個物理page的總計是兩個pte table(4KB)
kmap的調用流程分析:
arch/arm/mm/highmem.c
  1. <span style="font-size:24px">void *kmap(struct page *page)  
  2. {  
  3.     might_sleep();  
  4.     if (!PageHighMem(page)){//如果是低端內存,則直接返內存頁對應的直接映射虛擬地址  
  5.         //printk("low mem page\n");  
  6.         return page_address(page);//所有的低端內存,在內核初始化時就已經映射好了,並且是不變得,且物理到虛擬相差0xc0000000  
  7.     }else{  
  8.         //printk("high mem page\n");  
  9.     }  
  10.     return kmap_high(page);//高端內存頁  
  11. }</span>  

進入/trunk/mm/highmem.c的kmap_high

  1. <span style="font-size:24px">/** 
  2.  * kmap_high - map a highmem page into memory 
  3.  * @page: &struct page to map 
  4.  * 
  5.  * Returns the page's virtual memory address. 
  6.  * 
  7.  * We cannot call this from interrupts, as it may block. 
  8.  */  
  9. void *kmap_high(struct page *page)  
  10. {  
  11.     unsigned long vaddr;  
  12.   
  13.     /* 
  14.      * For highmem pages, we can't trust "virtual" until 
  15.      * after we have the lock. 
  16.      */  
  17.     lock_kmap();  
  18.     vaddr = (unsigned long)page_address(page);  
  19.     if (!vaddr)//如果該頁的映射還未建立  
  20.         vaddr = map_new_virtual(page);//開始建立新的映射  
  21.     pkmap_count[PKMAP_NR(vaddr)]++;//該數組的值爲1,說明映射已經建立,爲2表明該應聲存在着引用  
  22.     BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);  
  23.     unlock_kmap();  
  24.     return (void*) vaddr;  
  25. }</span>  

進入到map_new_virtual函數:
  1. <span style="font-size:24px">static inline unsigned long map_new_virtual(struct page *page)  
  2. {  
  3.     unsigned long vaddr;  
  4.     int count;  
  5.   
  6. start:  
  7.     count = LAST_PKMAP; // 2MB/4096KB=512 entries = <span style="font-family:Arial,Helvetica,sans-serif">LAST_PKMAP</span>  
  8.   
  9.     /* Find an empty entry */  
  10.     for (;;) {  
  11.         last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;  
  12.         if (!last_pkmap_nr) {  
  13.             flush_all_zero_pkmaps();  
  14.             count = LAST_PKMAP;  
  15.         }  
  16.         if (!pkmap_count[last_pkmap_nr])//爲0,說明該虛擬地址不存在映射,沒人使用  
  17.             break;  /* Found a usable entry */  
  18.         if (--count)//如果遍歷了整個kmap虛擬空間,都不能找到空閒的虛擬地址,則休眠等待unkmap釋放虛擬地址  
  19.             continue;  
  20.   
  21.         /* 
  22.          * Sleep for somebody else to unmap their entries 
  23.          */  
  24.         {  
  25.             DECLARE_WAITQUEUE(wait, current);  
  26.   
  27.             __set_current_state(TASK_UNINTERRUPTIBLE);  
  28.             add_wait_queue(&pkmap_map_wait, &wait);  
  29.             unlock_kmap();  
  30.             schedule();  
  31.             remove_wait_queue(&pkmap_map_wait, &wait);  
  32.             lock_kmap();  
  33.   
  34.             /* Somebody else might have mapped it while we slept */  
  35.             if (page_address(page))  
  36.                 return (unsigned long)page_address(page);  
  37.   
  38.             /* Re-start */  
  39.             goto start;  
  40.         }  
  41.     }  
  42.     vaddr = PKMAP_ADDR(last_pkmap_nr);<span style="font-family:Arial,Helvetica,sans-serif">//#define PKMAP_ADDR(nr)</span><span style="font-family:Arial,Helvetica,sans-serif">     </span><span style="font-family:Arial,Helvetica,sans-serif">(PKMAP_BASE + ((nr) << PAGE_SHIFT))</span>  
  43.     set_pte_at(&init_mm, vaddr,  
  44.            &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));  
  45.   
  46.     pkmap_count[last_pkmap_nr] = 1;  
  47.     set_page_address(page, (void *)vaddr);  
  48.   
  49.     return vaddr;  
  50. }</span>  

上面代碼中的pkmap_page_table是kmap所對應的虛擬地址[PKMAP_BASE,PAGE_OFFSET]所對應的二級映射表,即pte table,該映射表剛好是4KB用來映射2MB的虛擬到物理地址
pkmap_page_table使在trunk/arch/arm/mm/mmu.c文件中設置的:

  1. <span style="font-size:24px">static void __init kmap_init(void)  
  2. {  
  3. #ifdef CONFIG_HIGHMEM</span>  
  1. <span style="font-size:24px">        //獲取kmap所對應的虛擬地址[PKMAP_BASE,PAGE_OFFSET]所對應的二級映射表的開始地址。該二級映射表剛好就是一個物理頁的大小  
  2.     pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),  
  3.         PKMAP_BASE, _PAGE_KERNEL_TABLE);  
  4.     printk("************************************************\n");  
  5.     printk("pkmap_page_table:%x, phy of pkmap_page_table:%x\n",pkmap_page_table,virt_to_phys(pkmap_page_table));  
  6.     printk("************************************************\n");  
  7. #endif  
  8. }  
  9. </span>  
上述函數中的pmd_off_k(PKMAP_BASE)是獲取PKMAP_BASE虛擬地址對應的一級映射表中所對應的頁表項地址,

  1. <span style="font-size:24px">static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd,  
  2.     unsigned long addr, unsigned long prot)  
  3. {  
  4.     if (pmd_none(*pmd)) {//如果一級頁表項無效,即還未分配該表項所指向二級頁表,即pte table  
  5.         pte_t *pte = early_pte_alloc(pmd);//分配二級頁表,即pte tabble  
  6.         early_pte_install(pmd, pte, prot);//將pte table的hw/pte page0,<span style="font-family:Arial,Helvetica,sans-serif">hw/pte page1分別填充到一級頁表項的低4byte和高4byte</span>  
  7.   
  8.     }  
  9.     BUG_ON(pmd_bad(*pmd));  
  10.     return pte_offset_kernel(pmd, addr);//返回二級頁表中對應的頁表項地址。  
  11. }  
  12. </span>  

以上過程,具體見下圖的映射關係圖1




kmap的實驗

kmap試驗目的:

a:kmap映射高端內存頁返回的地址是否屬於0xbfe00000 - 0xc0000000範圍。
b:kmap的二級映射表的虛擬地址:pkmap_page_table,在函數kmap_init中打印該虛擬地址和對應的物理地址,然後根據二級映射表的結構,找到kmap返回的虛擬地址對應的物理地址。
再根據該物理地址,使用mu內存查看工具,查看該物理頁的內容是否是我們之前在驅動中通過kmap返回的虛擬地址設置的特殊數值。

詳細的測試代碼如下:
  1. <span style="font-size:24px">#include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/mm.h>  
  4. #include <linux/fs.h>  
  5. #include <linux/types.h>  
  6. #include <linux/delay.h>  
  7. #include <linux/moduleparam.h>  
  8. #include <linux/slab.h>  
  9. #include <linux/errno.h>  
  10. #include <linux/ioctl.h>  
  11. #include <linux/cdev.h>  
  12. #include <linux/string.h>  
  13. #include <linux/list.h>  
  14. #include <linux/pci.h>  
  15. #include <linux/gpio.h>  
  16. #include <linux/gfp.h>  
  17. #include <asm/highmem.h></span>  
下面的函數,是通過kmap分配高端內存頁,並且將分配得到的內存頁,都特別的設置成特殊的數據,依次爲:0x5a,0x5b,0x5c
  1. <span style="font-size:24px">struct page * map_high_mem(int order)  
  2. {  
  3.     int i=0;  
  4.     static int poison = 0x5a;  
  5.     unsigned char *buf = NULL;  
  6.     struct page *high_page = alloc_pages(__GFP_HIGHMEM,order);  //指定可以從高端內存分配物理空閒頁  
  7.     //struct page *high_page = alloc_pages(GFP_HIGHUSER,order);  
  8.     if(high_page){  
  9.         printk("high_page alloc success\n");  
  10.     }else{  
  11.         printk("high_page alloc failed\n");  
  12.     }  
  13.     buf = kmap(high_page);//爲該高端內存頁,建立臨時映射,該函數可能休眠  
  14.     if(buf){  
  15.         printk("kmap success,buf addr:%x\n",buf);//如果映射成功,返回影射後的虛擬地址  
  16.         for(i=0;i<4096;i++)  
  17.             buf[i] = poison;  
  18.         poison++;  
  19.     }else{  
  20.         printk("kmap failed\n");  
  21.     }  
  22.     return high_page;  
  23. }  
  24.   
  25. void free_high_mem(struct page *page,int order)  
  26. {  
  27.     kunmap(page);//拆除映射  
  28.     __free_pages(page,order);//釋放對應物理頁  
  29. }</span>  


  1. <span style="font-size:24px">struct page *page_array[5];  
  2. #define NUM_ORDER 0  
  3. static int __init dev_init(void)  
  4. {  
  5.     int ret;  
  6.     int i;  
  7.     /*************************************************************/   
  8.     i = 0;  
  9.     page_array[i++] = map_high_mem(NUM_ORDER);//連續分配,映射三個物理頁  
  10.     page_array[i++] = map_high_mem(NUM_ORDER);  
  11.     page_array[i++] = map_high_mem(NUM_ORDER);  
  12.       
  13.     printk("module address,page_array:0x%x\n",page_array);  
  14.     return ret;  
  15. }</span>  


  1. <span style="font-size:24px">static void __exit dev_exit(void)  
  2. {  
  3.   
  4.     int i = 0;  
  5.     free_high_mem(page_array[i++],NUM_ORDER);  
  6.     free_high_mem(page_array[i++],NUM_ORDER);  
  7.     free_high_mem(page_array[i++],NUM_ORDER);  
  8. }  
  9. module_init(dev_init);  
  10. module_exit(dev_exit);  
  11. MODULE_LICENSE("GPL");  
  12. MODULE_AUTHOR("LKN@SCUT");</span>  

以上是我們的測試代碼,代碼編譯,加載執行。

以下log是得到pkmap_page_table的物理地址,即kmap的二級映射表的開始物理地址,該log是內核啓動階段在kmap_init函數打印出來的。
[    0.000000] ************************************************
[    0.000000] pkmap_page_table:ef7fc000, phy of pkmap_page_table:2f7fc000(759MB)
[    0.000000] ************************************************
由於kmap是2MB的虛擬空間,剛好一個page大小的二級映射表就可以完全覆蓋到。如上的log所顯示,這個二級映射表的開始物理地址是:2f7fc000。

模塊加載時的log:
/storage/sdcard1 # insmod mymap.ko 
[   83.289132] kernel buffer virtial address:ee155000
[   83.293936] kernel buffer physical address:2e155000
[   83.298801] high_page alloc success
[   83.302264] kmap success,buf addr:bfeee000------------->(a)  //第一次kmap映射返回的虛擬地址,將該頁都初始化爲0x5a
[   83.306393] high_page alloc success
[   83.309809] kmap success,buf addr:bfeef000------------->(b)  //第二次kmap映射返回的虛擬地址,將該頁都初始化爲0x5b
[   83.313990] high_page alloc success
[   83.317414] kmap success,buf addr:bfef0000------------->(c)   //第三次kmap映射返回的虛擬地址,將該頁都初始化爲0x5c


可見三次kmap返回的虛擬地址都是屬於0xbfe00000 - 0xc0000000範圍。

根據他們返回的虛擬地址,我們再結合之前得出的二級映射表的物理地址,我們推算出該三個虛擬地址所對應的物理內存頁,分別如下:

由程序的虛擬地址得到對應的物理地址的公式爲:virt_addr----->phy_addr
二級映射表的開始物理地址+2KB的硬件頁表頁內偏移+virt_addr[bit20,bit12] * 4 上存儲的內容即爲虛擬頁對應的物理頁

case a: bfeee000---------->374ce000
2f7fc000 + 1024*2(800) + 238*4(3b8) = 2F7FCBB8(二級映射表項的物理地址)
0x37ce000即是虛擬頁bfeee000對應的物理頁,我們可以看到,該頁上的內容剛好就是我們之前設置的0x5a。




case b: bfeef000---------->37b0c000
2f7fc000 + 1024*2(800) + 238*4(3bc) = 2F7FCBBC(二級映射表項的物理地址)

37b0c000即是虛擬頁bfeef000對應的物理頁,我們可以看到,該頁上的內容剛好就是我們之前設置的0x5b。



case c: bfef0000---------->37b0b000
2f7fc000 + 1024*2(800) + 240*4(3C0) = 2F7FCBC0(二級映射表項的物理地址)



37b0b000即是虛擬頁bfef0000對應的物理頁,我們可以看到,該頁上的內容剛好就是我們之前設置的0x5c。

通過以上的實驗,我們驗證了自己對linux arm二級映射表結構的理解,同時也明白了kmap映射的原理。



//+++++++++++++++++++++++++++++++++++++++++++

Linux內核中分配頁面使用了以下六個函數:


alloc_pages(gfp_mask, order):用這個函數請求2order 個連續的頁框。他返回第一個所分配頁框描述符的地址,或者如果失敗,則返回NULL。

alloc_page(gfp_mask):用於獲得一個單獨頁框的宏,它其實只是alloc_pages(gfp_mask, 0)。它返回所分配頁框描述符的地址,或者如果分配失敗,則返回NULL。

__get_free_pages(gfp_mask, order):該函數類似於alloc_pages( ),只不過它返回第一個所分配頁對應的內存線性地址。

__get_free_page(gfp_mask):用於獲得一個單獨頁框的宏,它也只是__get_free_pages(gfp_mask, 0)

get_zeroed_page(gfp_mask):函數用來獲取滿是0的頁面,它調用alloc_pages(gfp_mask | __GFP_ZERO, 0),然後返回所獲取頁框的線性地址。

__get_dma_pages(gfp_mask, order):該宏獲取用於DMA的頁框,它擴展調用__get_free_pages(gfp_mask | _ _GFP_DMA, order)。

這六個函數之間的調用關係如下:





所以這六個函數最後調用的還是alloc_pages(),接下來就只需要研究alloc_pages()這個函數。

而alloc_pages調用過程爲:

alloc_pages()

   --> alloc_pages_node() 

      -->  __alloc_pages() 

         --> __alloc_pages_nodemask() 

               --> get_page_from_freelist() 

               --> buffered_rmqueue() 

                  --> __rmqueue()

具體爲:

在include/linux/gfp.h中有這定義:

  1. #define alloc_pages(gfp_mask, order) \               
  2.    alloc_pages_node(numa_node_id(), gfp_mask, order)  

其中numa_node_id() 返回0,指的是第0個節點。


之後的函數解析參考:(很詳細)文章
轉載:Linux夥伴系統(三)--分配頁



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章