核心中的記憶體申請:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pa
在核心模組中申請分配記憶體需要使用核心中的專用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標誌能夠使用甚至最後一個空閒頁;如果這最後一個空閒頁不存在,那分配就會失敗;
本文永久更新連結:http://embeddedlinux.org.cn/emb-linux/kernel-driver/201905/08-8657.html