linux的內存開闢

 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等一起使用,用來申請用於直接內存訪問的內存頁。

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);    --如果不是常量的話
}
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);
 if (unlikely(ZERO_OR_NULL_PTR(s)))  -- unlikely這個東西有沒有是一樣的,只不過說明大多數情況下條件是爲否的。這個是用於編譯加速的。這個是告訴編譯器,這個條件大多數是否
  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)
{
 if (size <= KMALLOC_MIN_SIZE)
  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的則採用從已經預創建好的高速緩存對象中開闢。
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);
}
__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;
 size = PAGE_ALIGN(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;
 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;
 nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
 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;
 }
 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開闢的內存並不連續。其主要是對軟件有意義。
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給映射到內存中,不過這個還是需要自己去管理的。
 
以上就是一些內存分配的常用的機制。至於具體的實現辦法以後再學習內存管理機制的時候,在去看看。

http://blog.chinaunix.net/uid-24517893-id-107458.html

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