內核中的內存申請

在內核模塊中申請分配內存需要使用內核中的專用API:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pages;當然,設備驅動程序也不例外;
對於提供了MMU功能的處理器而言,Linux提供了複雜的內存管理系統,使得進程所能訪問到的地址空間可以達到4GB;而這4GB的空間又被劃分爲兩個部分:0GB~3GB(PAGE_OFFSET,x86中的值是0xC0000000)的區域被用作進程的用戶空間,3GB~4GB的區域被用作內核空間;
在內核空間中,從3GB到vmalloc_start之間的這段地址區域作爲物理內存映射區使用,該段映射區域內包含了內核鏡像、物理頁框表mem_map等等,比如,我們使用的系統物理內存爲160MB,那麼,3GB~3GB+vmalloc_start之間的區域就應該是映射的物理內存;在物理內存映射區域之後,就是虛擬內存vmalloc區域;對於160MB的系統而言,vmalloc_start的位置就應該在3GB+160MB位置附近(在物理內存映射區與vmalloc_start位置之間還存在一個8M的gap來防止越界),vmalloc_end的位置接近4GB的位置(系統會在最後的位置處保留一片128KB大小的區域專用於頁面映射);
一、kmalloc
#include <linux/slab.h>
static inline void *kmalloc(size_t size, gfp_t flags);
參數:size:指定要分配的塊的大小,單位是字節;flags:指定分配內存時的控制方式;
該函數用於在內核空間中分配內存使用,它的返回速度快(除非被阻塞),並且對其分配的內存不進行任何初始化(清零)操作,分配的內存區域仍然保留有他原有的內容;
kmalloc申請得到的是物理內存,位於物理內存映射區,而且在物理地址上是連續的;但是kmalloc返回的內存地址卻是虛擬地址(線性地址),返回的這個虛擬地址(線性地址)與真實的物理地址之間僅僅相差一個固定的偏移值;因此,kmalloc申請得到的物理內存塊的首地址與其返回的虛擬地址之間存在着比較簡單的轉換關係;通過內核提供的函數virt_to_phys()可以實現該虛擬地址到真實的內核物理地址之間的轉換:
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
static inline unsigned long virt_to_phys(volatile void* address)
{
 return __pa(address);
}
參數address是kmalloc返回的一個虛擬地址;該轉換過程就是虛擬地址減去3GB(PAGE_OFFSET=0xC0000000);
一般情況下,PAGE_OFFSET=3*1024*1024*1024=0xC0000000(3G);
與之對應的函數就是phys_to_virt()用於把內核物理地址轉換爲虛擬地址:
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
static inline void * phys_to_virt(unsigned long address)
{
 return __va(address);
}
這兩個函數都定義在include/asm-i386/io.h中;
kmalloc()函數用於小塊內存的申請,最小可以申請的內存是32字節或64字節,最大可以申請的內存是128KB-16,其中,被減掉的16個字節用於存儲頁描述符結構;這些都依賴於體系架構所使用的頁面大小;kmalloc申請的內存在物理地址上是連續的,這對於要進行DMA傳輸的設備來說,是非常重要的;
kmalloc()的內存分配是基於slab機制實現的,slab機制是爲分配小內存而提供的一種高效的機制;但是slab機制也不是獨立的,它本身也是在頁分配器的基礎上來劃分更細粒度的內存供調用者使用;也就是說,系統先使用頁分配器分配以頁爲最小單位的連續物理地址,然後,kmalloc()再在這個基礎上根據調用者的需要進行切分的;另外,slab機制分配的內存在物理地址和虛擬地址(線性地址/邏輯地址)上都是連續的;
對於kmalloc()申請的內存,需要使用kfree()函數來釋放;
備註:kmalloc是基於slab機制實現的;
二、get_free_pages
#include <asm/pages.h>
fastcall unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
 struct page * page;
 page = alloc_pages(gfp_mask, order);
 if (!page)
  return 0;
 return (unsigned long) page_address(page);
}
參數gfp_mask用於指定申請內存時的控制方式,order用於指定申請的頁數;它申請的內存位於(PAGE_OFFSET,HIGH_MEMORY)之間;
__get_free_pages()函數是頁面分配器提供給調用者的最底層的內存分配函數,它申請的內存也是連續的物理內存,同樣位於物理內存映射區;它是基於buddy機制實現的;在使用buddy機制實現的物理內存管理系統中,最小的分配粒度(單位)也是以頁爲單位的;在__get_free_pages()內部通過調用alloc_pages()來分配物理內存頁;
__get_free_page()函數分配的是連續的物理內存,處理的是連續的物理地址,但是返回的也是虛擬地址(線性地址);如果想要得到正確的物理地址,也需要使用virt_to_phys()可進行轉換;
對於__get_free_pages()函數申請的內存,需要使用__free_pages()函數來釋放;
備註:__get_free_pages是基於buddy機制實現的;
三、vmalloc
#include <linux/vmalloc.h>
void* vmalloc(unsigned long size)
{
 return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
}
void* __vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
{
 return __vmalloc_node(size, gfp_mask, prot, -1);
}
void* __vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot, int node)
{
 struct vm_struct *area;
 size = PAGE_ALIGN(size);
 if(!size || (size >> PAGE_SHIFT) > num_physpages)
   return NULL;
 area = get_vm_area_node(size, VM_ALLOC, node);
 if(!area)
   return NULL;
 return __vmalloc_area_node(area, gfp_mask, prot, node);
}
void* __vmalloc_area_node(struct vm_struct* area, gfp_t gfp_mask, pgprot_t prot, int node);
void* __vmalloc_area(struct vm_struct* area, gfp_t gfp_mask, pgprot_t prot)
{
 return __vmalloc_area_node(area, gfp_mask, prot, -1);
}
vmalloc()函數也是用於申請內存的,但是它申請的內存是位於vmalloc_start到vmalloc_end之間的虛擬內存;它申請的內存在虛擬地址(線性地址/邏輯地址)上是連續的,但是並不要求在物理地址上連續,並且返回的地址與物理地址之間沒有簡單的轉換關係;
vmalloc()函數適用於大塊內存的申請環境中;但是它申請的內存不能直接用於DMA傳輸;因爲DMA傳輸需要使用物理地址連續的內存塊;
對於vmalloc()申請的內存,需要使用vfree()函數來釋放;
備註:vmalloc是基於slab機制實現的;
四、比較
1).kmalloc/__get_free_pages申請的內存塊都在物理內存映射區,即在(PAGE_OFFSET,HIGH_MEMORY)之間,處理的都是物理地址,且保證在物理地址空間上是連續的;二者返回的都是虛擬地址,如果需要得到正確的物理地址,需要使用virt_to_phys()進行轉換;但是,kmalloc和vmalloc都是以字節爲單位進行申請,而__get_free_pages()則是以頁爲單位進行申請;
2).vmalloc函數申請的內存塊位於虛擬內存映射區,即在(VMALLOC_START,VMALLOC_END)之間,處理的都是虛擬內存,且保證在虛擬地址空間上是連續的,但是在物理地址空間上不要求連續;一般作爲交換區、模塊的內存使用;
3).kmalloc和vmalloc都是基於slab機制實現的,但是kmalloc的速度比vmalloc的速度快;__get_free_pages是基於buddy機制實現的,速度也較快;
4).kmalloc用於小塊內存的申請,通常,一次所能申請的內存塊的大小在(32/64字節,128KB-16)之間;而vmalloc可以用於分配大塊內存的場合;
5).kmalloc申請的內存塊在物理地址空間上是連續的,所以它申請的內存塊可以直接用於DMA傳輸;vmalloc申請的內存塊在虛擬地址空間上連續,但是在物理地址空間上不要求連續,所以它申請的內存塊不能直接用於DMA傳輸;
6).kmalloc申請的內存塊用kfree釋放;vmalloc申請的內存塊用vfree釋放;__get_free_pages申請的內存頁用__free_pages釋放;
7).kmalloc申請得到的地址稱爲內核邏輯地址,vmalloc申請得到的地址稱爲內核虛擬地址;
五、其它函數
1).static inline void *kzalloc(size_t size, gfp_t flags);
 該函數比kmalloc多了一個功能,就是會把申請得到的內存塊初始化爲0;
2).static inline void* kcalloc(size_t n, size_t size, gfp_t flags)
   {
     if(n != 0 && size > ULONG_MAX / n)
        return NULL;
     return kzalloc(n * size, flags);
   }
   該函數用於申請一個數組的內存空間,並把申請得到的內存都初始化爲0;
六、GFP標記
kmalloc、kzalloc、kcalloc、vmalloc、get_free_pages函數在調用時都有一個gfp_t類型的控制標記flags;這個標記用於控制申請內存時的內存分配控制方式; #include <linux/gfp.h>
GFP的標記有兩種:帶雙下劃線前綴的和不帶雙下劃線前綴的;
不帶雙下劃線前綴的GFP標誌:
GFP_ATOMIC:用於在中斷上下文和進程上下文之外的其它代碼中分配內存;從不睡眠;
GFP_KERNEL:內核正常分配內存;可能睡眠;
GFP_USER  :用於爲用戶空間頁分配內存;可能睡眠;
GFP_HIGHUSER:如同GFP_USER,但它是從高端內存中申請;
GFP_NOIO和GFP_NOFS:功能如同GFP_KERNEL,但是它倆增加限制到內核能做的來滿足請求;GFP_NOFS分配不允許進行任何文件系統調用,而GFP_NOIO分配根本不允許進行任何IO初始化;它倆主要用於文件系統和虛擬內存代碼,那裏允許一個分配睡眠,但是遞歸的文件系統調用會是個壞主意;
帶有雙下劃線前綴的GFP標誌:
__GFP_DMA:這個標誌要求分配的內存在能夠進行DMA的內存區;平臺依賴的;
__GFP_HIGHMEM:這個標誌指示分配的內存可以位於高端內存區;平臺依賴的;
__GFP_COLD:正常地,內存分配器盡力返回"緩衝熱"的頁---可能在處理器緩衝中找到的頁;相反,這個標誌請求一個"冷"頁---在一段時間內沒被使用的頁;它對分配頁做DMA讀是很有用的,此時在處理器緩衝中出現是沒用的;
__GFP_NOWARN:這個標誌用於分配內存時阻止內核發出警告,當一個分配請求無法滿足時;
__GFP_HIGH:這個標誌標識了一個高優先級請求,它被允許來消耗甚至被內核保留給緊急狀況的最後的內存頁;
__GFP_REPEAT:分配器的動作;當分配器有困難滿足一個分配請求時,通過重複嘗試的方式來"盡力嘗試",但是分配操作仍然有可能失敗;
__GFP_NOFAIL:分配器的動作;當分配器有困難滿足一個分配請求時,這個標誌告訴分配器不要失敗,盡最大努力來滿足分配請求;
__GFP_NORETRY:分配器的動作;當分配器有困難滿足一個分配請求時,這個標誌告訴分配器立即放棄,不再做任何嘗試;
通常,一個或多個帶雙下劃線前綴的標記相或,即可得到對應的不帶雙下劃線前綴的標記;
最常用的標記就是GFP_KERNEL,它的意思就是當前的這個分配代表運行在內核空間的進程而進行的;換句話說,這意味着調用函數是代表一個進程在執行一個系統調用;使用GFP_KERNEL標記,就意味着kmalloc能夠使當前進程在少內存的情況下通過睡眠來等待一個內存頁;因此,一個使用GFP_KERNEL的函數必須是可重入的,且不能在原子上下文中運行;噹噹前進程睡眠,內核採取正確的動作來定位一些空閒的內存頁,或者通過刷新緩存到磁盤或者交換出去一個用戶進程的內存頁;
如果一個內存分配動作發生在中斷處理或內核定時器的上下文中時,當前進程就不能被設置爲睡眠,也就不能再使用GFP_KERNEL標誌了,此時應該使用GFP_ATOMIC標誌來代替;正常地,內核試圖保持一些空閒頁以便來滿足原子的分配;當使用GFP_ATOMIC標誌時,kmalloc標誌能夠使用甚至最後一個空閒頁;如果這最後一個空閒頁不存在,那分配就會失敗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章