void
*kmalloc(size_t size, int flags)
kmalloc函數和malloc函數相似,它有兩個參數,一個參數是size,即申請內存塊的大小,這個參數比較簡單,就像malloc中的參數一樣。第二個參數是一個標誌,在裏面可以指定優先權之類的信息。在Linux中,有以下的一些優先權:
GFP_KERNEL,它的意思是該內存分配是由運行在內核模式的進程調用的,即當內存低於min_free_pages的時候可以讓該進程進入睡眠;
GFP_ATOMIC,原子性的內存分配允許在實際內存低於min_free_pages時繼續分配內存給進程。
GFP_DMA:此標誌位需要和GFP_KERNEL、GFP_ATOMIC等一起使用,用來申請用於直接內存訪問的內存頁。
kmalloc函數和malloc函數相似,它有兩個參數,一個參數是size,即申請內存塊的大小,這個參數比較簡單,就像malloc中的參數一樣。第二個參數是一個標誌,在裏面可以指定優先權之類的信息。在Linux中,有以下的一些優先權:
GFP_KERNEL,它的意思是該內存分配是由運行在內核模式的進程調用的,即當內存低於min_free_pages的時候可以讓該進程進入睡眠;
GFP_ATOMIC,原子性的內存分配允許在實際內存低於min_free_pages時繼續分配內存給進程。
GFP_DMA:此標誌位需要和GFP_KERNEL、GFP_ATOMIC等一起使用,用來申請用於直接內存訪問的內存頁。
1.kmalloc
在內存開闢中kmalloc的使用概率很高,在通常的內存開闢中均會使用該函數來開闢內存。但是分配的區域仍然保持原有的數據,一般需要清零。
函數原型:
void *kmalloc(size_t size ,int flags);
參數size很好理解,即是分配多大的內存,以字節爲單位。flags也很明白,即是分配的標誌,來控制kmalloc的行爲。
flags一般常用的標誌有GFP_KERNEL,GFP_ATOMIC,__GFP_DMA。簡單說一下,GFP_KERNEL指由內核進程來執行的。使用這個flag時,函數會被阻塞,因爲kmalloc允許當空閒頁比較少的時候,會睡眠等待空閒頁。而GFP_ATOMIC則不然,他會立即調用內核預留的一些空閒頁,如果所有的預留都沒有了,就會立即返回錯誤。
該標誌一般用於中斷處理中。kmalloc一般最下處理32或64字節,最大一般不超過128K。
kmalloc的源碼如下
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) { --檢測size是否爲常量
if (size > PAGE_SIZE) --如果開闢的空間大於1頁的話,則會採用(void *)__get_free_pages(flags | __GFP_COMP, get_order(size));的辦法開闢
return kmalloc_large(size, flags);
if (!(flags & SLUB_DMA)) { --如果flags並沒有被指定爲__GFP_DMA,即是在DMA區開闢的話,則採用從已經創建了對象的高速緩存中分配
struct kmem_cache *s = kmalloc_slab(size); --這個則是查詢適合大小的內存對象
if (!s)
return ZERO_SIZE_PTR;
return kmem_cache_alloc(s, flags); --分配對應的內存對象
}
}
return __kmalloc(size, flags); --如果不是常量的話
}
在內存開闢中kmalloc的使用概率很高,在通常的內存開闢中均會使用該函數來開闢內存。但是分配的區域仍然保持原有的數據,一般需要清零。
函數原型:
void *kmalloc(size_t size ,int flags);
參數size很好理解,即是分配多大的內存,以字節爲單位。flags也很明白,即是分配的標誌,來控制kmalloc的行爲。
flags一般常用的標誌有GFP_KERNEL,GFP_ATOMIC,__GFP_DMA。簡單說一下,GFP_KERNEL指由內核進程來執行的。使用這個flag時,函數會被阻塞,因爲kmalloc允許當空閒頁比較少的時候,會睡眠等待空閒頁。而GFP_ATOMIC則不然,他會立即調用內核預留的一些空閒頁,如果所有的預留都沒有了,就會立即返回錯誤。
該標誌一般用於中斷處理中。kmalloc一般最下處理32或64字節,最大一般不超過128K。
kmalloc的源碼如下
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) { --檢測size是否爲常量
if (size > PAGE_SIZE) --如果開闢的空間大於1頁的話,則會採用(void *)__get_free_pages(flags | __GFP_COMP, get_order(size));的辦法開闢
return kmalloc_large(size, flags);
if (!(flags & SLUB_DMA)) { --如果flags並沒有被指定爲__GFP_DMA,即是在DMA區開闢的話,則採用從已經創建了對象的高速緩存中分配
struct kmem_cache *s = kmalloc_slab(size); --這個則是查詢適合大小的內存對象
if (!s)
return ZERO_SIZE_PTR;
return kmem_cache_alloc(s, flags); --分配對應的內存對象
}
}
return __kmalloc(size, flags); --如果不是常量的話
}
void *__kmalloc(size_t size, gfp_t flags) -- 這個函數與上面的類似可以說幾乎是一樣的,不知道這樣設計的含義
{
struct kmem_cache *s;
if (unlikely(size > PAGE_SIZE))
return kmalloc_large(size, flags);
s = get_slab(size, flags);
{
struct kmem_cache *s;
if (unlikely(size > PAGE_SIZE))
return kmalloc_large(size, flags);
s = get_slab(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(s))) -- unlikely這個東西有沒有是一樣的,只不過說明大多數情況下條件是爲否的。這個是用於編譯加速的。這個是告訴編譯器,這個條件大多數是否
return s;
return s;
return slab_alloc(s, flags, -1, _RET_IP_); --kmem_cache_alloc的函數直接會調用這個函數。
}
至於__get_free_pages和如何從後備高速緩存中開闢對象,這個在理解內存管理的時候再去理解。到此可以說對於小頁的ARCH(即PAGE_SIZE=4K),kmalloc的size小於PAGE_SIZE時,會採用從已經創建好了的高速緩存中分配內存對象。如果size不是2^n的話,會多開闢內存,有時最多開闢兩倍的內存,比如size=2^n+1時,最後開闢的內存會是2^(n+1),不過這種情況出現在size大於256時。爲什麼這麼說呢?看下面的源碼就可以理解了在kmalloc_slab函數中會調用kmalloc_index來查詢size所對應的高速緩存對象。至於爲什麼會出現這種情況,以後在看內存預分配高速緩衝時再說。源碼如下:
static __always_inline int kmalloc_index(size_t size)
{
}
至於__get_free_pages和如何從後備高速緩存中開闢對象,這個在理解內存管理的時候再去理解。到此可以說對於小頁的ARCH(即PAGE_SIZE=4K),kmalloc的size小於PAGE_SIZE時,會採用從已經創建好了的高速緩存中分配內存對象。如果size不是2^n的話,會多開闢內存,有時最多開闢兩倍的內存,比如size=2^n+1時,最後開闢的內存會是2^(n+1),不過這種情況出現在size大於256時。爲什麼這麼說呢?看下面的源碼就可以理解了在kmalloc_slab函數中會調用kmalloc_index來查詢size所對應的高速緩存對象。至於爲什麼會出現這種情況,以後在看內存預分配高速緩衝時再說。源碼如下:
static __always_inline int kmalloc_index(size_t size)
{
if (size <= KMALLOC_MIN_SIZE)
return KMALLOC_SHIFT_LOW;
return KMALLOC_SHIFT_LOW;
#if KMALLOC_MIN_SIZE <= 64 KMALLOC_MIN_SIZE在arm平臺下爲 8
if (size > 64 && size <= 96)
return 1;
if (size > 128 && size <= 192)
return 2;
#endif
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
--下面是爲了支持大頁的
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
return -1;
}
這這裏主要是理解kmalloc時,size的大小與真實內核分配內存間的關係,以及對於不同的開闢不同的內存大小則採用不同的辦法去開闢。對於大於PAGE_SIZE的size採用__get_free_pages開闢,而小於PAGE_SIZE的則採用從已經預創建好的高速緩存對象中開闢。
if (size > 64 && size <= 96)
return 1;
if (size > 128 && size <= 192)
return 2;
#endif
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
--下面是爲了支持大頁的
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
return -1;
}
這這裏主要是理解kmalloc時,size的大小與真實內核分配內存間的關係,以及對於不同的開闢不同的內存大小則採用不同的辦法去開闢。對於大於PAGE_SIZE的size採用__get_free_pages開闢,而小於PAGE_SIZE的則採用從已經預創建好的高速緩存對象中開闢。
2.vmalloc
vmalloc分配的內存在虛擬空間是連續的,而在物理空間可能是不連續的。適用於開闢大塊連續的,僅僅在軟件中存在,用於緩衝的內存。vmalloc需要查詢內核虛擬空間、的空閒節點,在空閒區域內創建頁表。其所消耗的資源遠比kmalloc多。所以不適合適用vmalloc去開闢小的內存,這種情況下,效率會很低。至於vmalloc如何去開闢內存空間以及創建新的頁表,還是來看源碼的實現。在這裏不怎麼考慮標誌符的影響,先考慮一般情況下的vmalloc的實現 vmalloc函數實現在vmalloc.c中vmalloc->__vmalloc_node,__vmalloc_node除去檢測的代碼,其主要功能就是調用下面的兩個函數。
static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,int node, void *caller)
{
struct vm_struct *area;
area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,node, gfp_mask, caller);
--- #define VMALLOC_OFFSET (8*1024*1024)
--- #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) 查詢的虛擬地址主要是從高地址開始查詢,不過
arm平臺high_memory爲0。
--- VMALLOC_END 0xE0000000
return __vmalloc_area_node(area, gfp_mask, prot, node, caller);
}
vmalloc分配的內存在虛擬空間是連續的,而在物理空間可能是不連續的。適用於開闢大塊連續的,僅僅在軟件中存在,用於緩衝的內存。vmalloc需要查詢內核虛擬空間、的空閒節點,在空閒區域內創建頁表。其所消耗的資源遠比kmalloc多。所以不適合適用vmalloc去開闢小的內存,這種情況下,效率會很低。至於vmalloc如何去開闢內存空間以及創建新的頁表,還是來看源碼的實現。在這裏不怎麼考慮標誌符的影響,先考慮一般情況下的vmalloc的實現 vmalloc函數實現在vmalloc.c中vmalloc->__vmalloc_node,__vmalloc_node除去檢測的代碼,其主要功能就是調用下面的兩個函數。
static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,int node, void *caller)
{
struct vm_struct *area;
area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,node, gfp_mask, caller);
--- #define VMALLOC_OFFSET (8*1024*1024)
--- #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) 查詢的虛擬地址主要是從高地址開始查詢,不過
arm平臺high_memory爲0。
--- VMALLOC_END 0xE0000000
return __vmalloc_area_node(area, gfp_mask, prot, node, caller);
}
__get_vm_area_node主要是查詢內核虛擬空間中的空閒節點,從start到end之間查找一個空閒的vm塊
static struct vm_struct *__get_vm_area_node(unsigned long size,unsigned long flags, unsigned long start, unsigned long end,int node, gfp_t gfp_mask, void *caller)
{
static struct vmap_area *va;
struct vm_struct *area;
struct vm_struct *tmp, **p;
unsigned long align = 1;
static struct vm_struct *__get_vm_area_node(unsigned long size,unsigned long flags, unsigned long start, unsigned long end,int node, gfp_t gfp_mask, void *caller)
{
static struct vmap_area *va;
struct vm_struct *area;
struct vm_struct *tmp, **p;
unsigned long align = 1;
size = PAGE_ALIGN(size);
area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node); --開闢內存
--需要多開闢一段信息頁
size += PAGE_SIZE;
area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node); --開闢內存
--需要多開闢一段信息頁
size += PAGE_SIZE;
va = alloc_vmap_area(size, align, start, end, node, gfp_mask); --根據從內核虛擬空間中查詢出一段大小大於size的虛擬內存空間。這個是怎麼實現的會在理解虛擬內存的時候去理解。這兒就略過。
--將對虛擬空間塊的描述賦值 vm_struct
area->flags = flags;
area->addr = (void *)va->va_start;
area->size = size;
area->pages = NULL;
area->nr_pages = 0;
area->phys_addr = 0;
area->caller = caller;
va->private = area;
va->flags |= VM_VM_AREA;
--將對虛擬空間塊的描述賦值 vm_struct
area->flags = flags;
area->addr = (void *)va->va_start;
area->size = size;
area->pages = NULL;
area->nr_pages = 0;
area->phys_addr = 0;
area->caller = caller;
va->private = area;
va->flags |= VM_VM_AREA;
write_lock(&vmlist_lock);
for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
if (tmp->addr >= area->addr)
break;
}
area->next = *p; --將該vm塊添加到鏈表
*p = area;
write_unlock(&vmlist_lock);
return area;
}
__get_vm_area_node從內核空間查詢到大小滿足size,一般是大於size的,並且地址連續的一個vm塊,並且對vm_struct進行賦值。而在__vmalloc_area_node中則是將上面查找的vm_struct的內存進行創建新頁。在上面函數中只是查詢到一段連續的內存,並沒有將內存分頁,創建新的頁鏈表
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,pgprot_t prot, int node, void *caller)
{
struct page **pages;
unsigned int nr_pages, array_size, i;
for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
if (tmp->addr >= area->addr)
break;
}
area->next = *p; --將該vm塊添加到鏈表
*p = area;
write_unlock(&vmlist_lock);
return area;
}
__get_vm_area_node從內核空間查詢到大小滿足size,一般是大於size的,並且地址連續的一個vm塊,並且對vm_struct進行賦值。而在__vmalloc_area_node中則是將上面查找的vm_struct的內存進行創建新頁。在上面函數中只是查詢到一段連續的內存,並沒有將內存分頁,創建新的頁鏈表
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,pgprot_t prot, int node, void *caller)
{
struct page **pages;
unsigned int nr_pages, array_size, i;
nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
array_size = (nr_pages * sizeof(struct page *));
array_size = (nr_pages * sizeof(struct page *));
area->nr_pages = nr_pages;
/* Please note that the recursion is strictly bounded. */
if (array_size > PAGE_SIZE) { --如果所需要存儲頁的信息超過了4K,則需要對頁信息指針進行二級處理。因爲在查詢內核空間時,僅多開闢一頁的內存用於存儲頁信息,如果頁信息超過了一頁,則需要重新去內核虛擬內存中開闢一個新頁來查詢,這其實是一個嵌套,使用二級頁目錄。
pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,PAGE_KERNEL, node, caller);
area->flags |= VM_VPAGES;
} else {
pages = kmalloc_node(array_size,(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,node); --否則則直接開闢空間,在node節點內。
}
area->pages = pages;
area->caller = caller;
if (!area->pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}
/* Please note that the recursion is strictly bounded. */
if (array_size > PAGE_SIZE) { --如果所需要存儲頁的信息超過了4K,則需要對頁信息指針進行二級處理。因爲在查詢內核空間時,僅多開闢一頁的內存用於存儲頁信息,如果頁信息超過了一頁,則需要重新去內核虛擬內存中開闢一個新頁來查詢,這其實是一個嵌套,使用二級頁目錄。
pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,PAGE_KERNEL, node, caller);
area->flags |= VM_VPAGES;
} else {
pages = kmalloc_node(array_size,(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,node); --否則則直接開闢空間,在node節點內。
}
area->pages = pages;
area->caller = caller;
if (!area->pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}
for (i = 0; i < area->nr_pages; i++) {
struct page *page;
if (node < 0)
page = alloc_page(gfp_mask);
else
page = alloc_pages_node(node, gfp_mask, 0); --這兒就是在node節點中創建新頁。不過這兒的node應該不是0
area->pages[i] = page;
}
return area->addr;
}
到此vmalloc就開闢出新的虛擬並連續的內存。vmalloc其實是在內核虛擬空間中找出滿足size的內存節點,然後在該節點中創建新的頁表。這也是爲什麼在開闢小的內存時採用vmalloc是不划算的。而且vmalloc對於物理內存而言並沒有什麼意義,因爲在物理內存中,vmalloc開闢的內存並不連續。其主要是對軟件有意義。
struct page *page;
if (node < 0)
page = alloc_page(gfp_mask);
else
page = alloc_pages_node(node, gfp_mask, 0); --這兒就是在node節點中創建新頁。不過這兒的node應該不是0
area->pages[i] = page;
}
return area->addr;
}
到此vmalloc就開闢出新的虛擬並連續的內存。vmalloc其實是在內核虛擬空間中找出滿足size的內存節點,然後在該節點中創建新的頁表。這也是爲什麼在開闢小的內存時採用vmalloc是不划算的。而且vmalloc對於物理內存而言並沒有什麼意義,因爲在物理內存中,vmalloc開闢的內存並不連續。其主要是對軟件有意義。
3.__get_free_pages
在上面也提到__get_free_pages,該函數會在kmalloc開闢空間大於PAGE_SIZE時調用。
__get_free_pages ->alloc_pages ->alloc_pages_node->__alloc_pages->__alloc_pages_internal 經過七拐八拐的最終會分配新頁這個函數,這個函數是zone區分配器的核心。
__alloc_pages_internal這個函數以後再瞭解內存管理時,再好好理解,現在很難。
4.kmem_cache_create與kmem_cache_alloc
這兩個函數是組合起來一起用,kmem_cache_create用於創建一個高速緩衝對象,而kmem_cache_alloc用於分配高速緩衝對象內存。這個一般用於需要頻繁開闢和註銷同一種結構的對象,這個也是用於加速。
5.dma_alloc_coherent
這個函數分配緩衝區並且重新映射。分配的內存可以用於DMA的傳輸。
6. alloc_bootmem
有時候某些內核模塊需要開闢較大的內存如幾M的內存空間,如果在內存管理建立後去開闢容易流於失敗,通常會在引導程序時提前開闢,這樣會跳過內核的內存管理,對內存管理而言,這樣的空間是不可見的。這樣會減少留給操作系統的RAM。這段內存也需要自己去管理。這個我喜歡!!
7. ioremap
ioremap這個函數不能是一個真正的內存分配,不過他也是一個內存分配,爲什麼這麼說呢?因爲ioremap的內存可以將內存管理系統看不見的硬件內存映射到虛擬內存中,說他是內存分配,因爲他是分配純虛擬空間。說他不是,因爲他不分配真實的物理空間,他是一種映射。
例如:現在物理內存128M,但是在傳替給內核參數時卻是 “mem = 124M”,那對用linux系統而言,真實的內存就是124M那還有的4M就無法使用了。這個時候就可以採用ioremap的辦法,將在流離在外的4M給弄來,ioremap(0x7C00000,0x400000);這樣就可以將那4M給映射到內存中,不過這個還是需要自己去管理的。
在上面也提到__get_free_pages,該函數會在kmalloc開闢空間大於PAGE_SIZE時調用。
__get_free_pages ->alloc_pages ->alloc_pages_node->__alloc_pages->__alloc_pages_internal 經過七拐八拐的最終會分配新頁這個函數,這個函數是zone區分配器的核心。
__alloc_pages_internal這個函數以後再瞭解內存管理時,再好好理解,現在很難。
4.kmem_cache_create與kmem_cache_alloc
這兩個函數是組合起來一起用,kmem_cache_create用於創建一個高速緩衝對象,而kmem_cache_alloc用於分配高速緩衝對象內存。這個一般用於需要頻繁開闢和註銷同一種結構的對象,這個也是用於加速。
5.dma_alloc_coherent
這個函數分配緩衝區並且重新映射。分配的內存可以用於DMA的傳輸。
6. alloc_bootmem
有時候某些內核模塊需要開闢較大的內存如幾M的內存空間,如果在內存管理建立後去開闢容易流於失敗,通常會在引導程序時提前開闢,這樣會跳過內核的內存管理,對內存管理而言,這樣的空間是不可見的。這樣會減少留給操作系統的RAM。這段內存也需要自己去管理。這個我喜歡!!
7. ioremap
ioremap這個函數不能是一個真正的內存分配,不過他也是一個內存分配,爲什麼這麼說呢?因爲ioremap的內存可以將內存管理系統看不見的硬件內存映射到虛擬內存中,說他是內存分配,因爲他是分配純虛擬空間。說他不是,因爲他不分配真實的物理空間,他是一種映射。
例如:現在物理內存128M,但是在傳替給內核參數時卻是 “mem = 124M”,那對用linux系統而言,真實的內存就是124M那還有的4M就無法使用了。這個時候就可以採用ioremap的辦法,將在流離在外的4M給弄來,ioremap(0x7C00000,0x400000);這樣就可以將那4M給映射到內存中,不過這個還是需要自己去管理的。
以上就是一些內存分配的常用的機制。至於具體的實現辦法以後再學習內存管理機制的時候,在去看看。
http://blog.chinaunix.net/uid-24517893-id-107458.html