linux內存管理之malloc

http://blog.chinaunix.net/uid-20786208-id-4979967.html

 

對於內核的內存管理,像kmalloc,vmalloc,kmap,ioremap等比較熟悉。而對用戶層的管理機制不是很熟悉,下面就從malloc的實現入手.( 這裏不探討linux系統調用的實現機制. ) ,參考了《深入理解計算機系統》和一些網上的資料.
首先從http://ftp.gnu.org/gnu/glibc下載glibc庫2.21,
通常我們用的bsp或者sdk裏面的工具鏈都是編譯好的,而這個是源碼,需要自己編譯(常用的有定製交叉編譯工具鏈).有時候我們需要添加自定義庫.     

Linux中malloc的早期版本是由Doug Lea實現的,它有一個重要問題就是在並行處理時多個線程共享進程的內存空間,各線程可能併發請求內存,在這種情況下應該如何保證分配和回收的正確和有效。Wolfram Gloger在Doug Lea的基礎上改進使得glibc的malloc可以支持多線程——ptmalloc,在glibc-2.3.x.中已經集成了ptmalloc2,這就是我們平時使用的malloc.

其做法是,爲了支持多線程並行處理時對於內存的併發請求操作,malloc的實現中把全局用戶堆(heap)劃分成很多子堆(sub-heap)。這些子堆是按照循環單鏈表的形式組織起來的。每一個子堆利用互斥鎖(mutex)使線程對於該子堆的訪問互斥。當某一線程需要調用malloc分配內存空間時,該線程搜索循環鏈表試圖獲得一個沒有加鎖的子堆。如果所有的子堆都已經加鎖,那麼malloc會開闢一塊新的子堆,對於新開闢的子堆默認情況下是不加鎖的,因此線程不需要阻塞就可以獲得一個新的子堆並進行分配操作。在回收free操作中,線程同樣試圖獲得待回收塊所在子堆的鎖,如果該子堆正在被別的線程使用,則需要等待直到其他線程釋放該子堆的互斥鎖之後纔可以進行回收操作。

         申請小塊內存時會產生很多內存碎片,ptmalloc在整理時需要對子堆做加鎖操作,每個加鎖操作大概需要5~10個cpu指令,而且程序線程數很高的情況下,鎖等待的時間就會延長,導致malloc性能下降。

因此很多大型的服務端應用會自己實現內存池,以降低向系統malloc的開銷。HoardTCmalloc是在glibc和應用程序之間實現的內存管理。Hoard的作者是美國麻省的Amherst College的一名老師,理論角度對hoard的研究和優化比較多,相關的文獻可以hoard主頁下載到到。從我自己項目中的系統使用來看,Hoard確實能夠很大程度的提高程序的性能和穩定性。TCMalloc(Thread-Caching Malloc)是google開發的開源工具──“google-perftools”中的成員。這裏有它的系統的介紹安裝方法。這個只是對它歷史發展的一個簡單介紹,具體改動還需去官網查看.
  下面我們就看看malloc:

malloc的全稱是memory allocation,中文叫動態內存分配,當無法知道內存具體位置的時候,想要綁定真正的內存空間,就需要用到動態的分配內存。 

原型爲: extern void *malloc(unsigned int num_bytes)。
  具體聲明在malloc.h中:

點擊(此處)摺疊或打開

  1. /* Allocate SIZE bytes of memory. */
  2. extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;

返回值:

如果分配成功則返回指向被分配內存的指針(此存儲區中的初始值不確定),否則返回空指針NULL。當內存不再使用時,應使用free()函數將內存塊釋放。函數返回的指針一定要適當對齊,使其可以用於任何數據對象。 

注意:

malloc(0) 返回不爲空。  Free(p) 後p不爲空。

那麼malloc到底是從哪裏獲取的內存呢? 
   答案是從堆裏面獲得空間;malloc的應用必然是某一個進程調用,而每一個進程在啓動的時候,系統默認給它分配了heap。下面我們就看看進程的內存空間佈局:
Anyway, here is the standard segment layout in a Linux process:(這個是x86 虛擬地址空間的默認佈局)

 

在glibc庫中找到malloc.c文件:

點擊(此處)摺疊或打開

  1. strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)

即malloc別名爲__libc_malloc,__malloc.並且在malloc.c中我們不能找到malloc的直接實現,而是有__libc_malloc:

點擊(此處)摺疊或打開

  1. /*------------------------ Public wrappers. --------------------------------*/
  2.  
  3. void *
  4. __libc_malloc (size_t bytes)
  5. {
  6.   mstate ar_ptr;
  7.   void *victim;
  8.  
  9.   void *(*hook) (size_t, const void *)
  10.     = atomic_forced_read (__malloc_hook);
  11.   if (__builtin_expect (hook != NULL, 0))
  12.     return (*hook)(bytes, RETURN_ADDRESS (0));
  13.  
  14.   arena_lookup (ar_ptr);
  15.  
  16.   arena_lock (ar_ptr, bytes);
  17.   if (!ar_ptr)
  18.     return 0;
  19.  
  20.   victim = _int_malloc (ar_ptr, bytes);
  21.   if (!victim)
  22.     {
  23.       LIBC_PROBE (memory_malloc_retry, 1, bytes);
  24.       ar_ptr = arena_get_retry (ar_ptr, bytes);
  25.       if (__builtin_expect (ar_ptr != NULL, 1))
  26.         {
  27.           victim = _int_malloc (ar_ptr, bytes);
  28.           (void) mutex_unlock (&ar_ptr->mutex);
  29.         }
  30.     }
  31.   else
  32.     (void) mutex_unlock (&ar_ptr->mutex);
  33.   assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
  34.           ar_ptr == arena_for_chunk (mem2chunk (victim)));
  35.   return victim;
  36. }

在這個函數的第一行是關於hook的,我們先看一個定義:

點擊(此處)摺疊或打開

  1. void *weak_variable (*__malloc_hook)
  2.   (size_t __size, const void *) = malloc_hook_ini;

它是gcc  attribute weak的特性,可以查資料進一步瞭解.這裏說明一下由於是弱屬性,所以當有具體的實現的時候,就以外部實現爲準.

點擊(此處)摺疊或打開

  1. static void *
  2. malloc_hook_ini (size_t sz, const void *caller)
  3. {
  4.   __malloc_hook = NULL;
  5.   ptmalloc_init ();
  6.   return __libc_malloc (sz);
  7. }

__libc_malloc中首先判斷hook函數指針是否爲空,不爲空則調用它,並返回。glibc2.21裏默認malloc_hook是初始化爲malloc_hook_ini的
但是我們發現在malloc_hook_ini中把__malloc_hook賦值爲NULl,這樣就避免了遞歸調用.
同理在最後部分也有一個__malloc_initialize_hook的:默認爲空.

點擊(此處)摺疊或打開

  1. void weak_variable (*__malloc_initialize_hook) (void) = NULL;

那麼ptmalloc_init到底又做了什麼工作呢?

點擊(此處)摺疊或打開

  1. static void
  2. ptmalloc_init (void)
  3. {
  4.   if (__malloc_initialized >= 0)
  5.     return;
  6.  
  7.   __malloc_initialized = 0;
  8.  
  9. #ifdef SHARED
  10.   /* In case this libc copy is in a non-default namespace, never use brk.
  11.      Likewise if dlopened from statically linked program. */
  12.   Dl_info di;
  13.   struct link_map *l;
  14.  
  15.   if (_dl_open_hook != NULL
  16.       || (_dl_addr (ptmalloc_init, &di, &l, NULL) != 0
  17.           && l->l_ns != LM_ID_BASE))
  18.     __morecore = __failing_morecore;
  19. #endif
  20.  
  21.   tsd_key_create (&arena_key, NULL);
  22.   tsd_setspecific (arena_key, (void *) &main_arena);
  23.   thread_atfork (ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);
  24.   const char *s = NULL;
  25.   if (__glibc_likely (_environ != NULL))
  26.     {
  27.       char **runp = _environ;
  28.       char *envline;
  29.  
  30.       while (__builtin_expect ((envline = next_env_entry (&runp)) != NULL,
  31.                                0))
  32.         {
  33.           size_t len = strcspn (envline, "=");
  34.  
  35.           if (envline[len] != '=')
  36.             /* This is a "MALLOC_" variable at the end of the string
  37.                without a '=' character. Ignore it since otherwise we
  38.                will access invalid memory below. */
  39.             continue;
  40.  
  41.           switch (len)
  42.             {
  43.             case 6:
  44.               if (memcmp (envline, "CHECK_", 6) == 0)
  45.                 s = &envline[7];
  46.               break;
  47.             case 8:
  48.               if (!__builtin_expect (__libc_enable_secure, 0))
  49.                 {
  50.                   if (memcmp (envline, "TOP_PAD_", 8) == 0)
  51.                     __libc_mallopt (M_TOP_PAD, atoi (&envline[9]));
  52.                   else if (memcmp (envline, "PERTURB_", 8) == 0)
  53.                     __libc_mallopt (M_PERTURB, atoi (&envline[9]));
  54.                 }
  55.               break;
  56.             case 9:
  57.               if (!__builtin_expect (__libc_enable_secure, 0))
  58.                 {
  59.                   if (memcmp (envline, "MMAP_MAX_", 9) == 0)
  60.                     __libc_mallopt (M_MMAP_MAX, atoi (&envline[10]));
  61.                   else if (memcmp (envline, "ARENA_MAX", 9) == 0)
  62.                     __libc_mallopt (M_ARENA_MAX, atoi (&envline[10]));
  63.                 }
  64.               break;
  65.             case 10:
  66.               if (!__builtin_expect (__libc_enable_secure, 0))
  67.                 {
  68.                   if (memcmp (envline, "ARENA_TEST", 10) == 0)
  69.                     __libc_mallopt (M_ARENA_TEST, atoi (&envline[11]));
  70.                 }
  71.               break;
  72.             case 15:
  73.               if (!__builtin_expect (__libc_enable_secure, 0))
  74.                 {
  75.                   if (memcmp (envline, "TRIM_THRESHOLD_", 15) == 0)
  76.                     __libc_mallopt (M_TRIM_THRESHOLD, atoi (&envline[16]));
  77.                   else if (memcmp (envline, "MMAP_THRESHOLD_", 15) == 0)
  78.                     __libc_mallopt (M_MMAP_THRESHOLD, atoi (&envline[16]));
  79.                 }
  80.               break;
  81.             default:
  82.               break;
  83.             }
  84.         }
  85.     }
  86.   if (s && s[0])
  87.     {
  88.       __libc_mallopt (M_CHECK_ACTION, (int) (s[0] - '0'));
  89.       if (check_action != 0)
  90.         __malloc_check_init ();
  91.     }
  92.   void (*hook) (void) = atomic_forced_read (__malloc_initialize_hook);
  93.   if (hook != NULL)
  94.     (*hook)();
  95.   __malloc_initialized = 1;
  96. }

而__malloc_initialized在arena.c中默認初始化爲:即開始的時候小於0.

點擊(此處)摺疊或打開

  1. /* Already initialized? */
  2. int __malloc_initialized = -1;

函數開始把它賦值爲0,最後初始化完成賦值爲1. 所以這個函數完成了malloc的初始化工作.只有第一次調用的時候會用到.
接着是處理_environ即傳遞過來的環境變量,進行內存分配策略控制你可以定製內存管理函數的行爲,通過調整由mallopt()函數的參數。(默認環境變量爲空

內存分配調整甚至可以不在你的程序中引入mallopt()調用和重新編譯它。在你想快速測試一些值或者你沒有源代碼時,這非常有用。你僅需要做的是在運行程序前,設置合適的環境變量。表1展示mallopt()參數和環境變量的映射關係以及一些額外的信息。例如,如果你希望設置內存消減閾值爲64k,你可以運行這個程序:

<span style="color:#336699">#MALLOC_TRIM_THRESHOLD=65536 my_prog</span>


內存調試:連續性檢查 ,可以設置變量MALLOC_CHECK_=1
#MALLOC_CHECK_=1 my_prog
還有一個mtrace使用的例子:

點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <malloc.h>
  4.  
  5.  
  6. void main(void)
  7. {
  8.  
  9.  char *p;
  10.  mtrace();
  11.  p=(char *)malloc(100);
  12.  if(p ==NULL)
  13.      return 0;
  14.   memcpy(p,"helllllllllll",20);
  15.  printf("....1 %s.....\n",p);
  16.  //free(p);
  17.  
  18. }

運行: #MALLOC_TRACE="1.txt" ./a.out 
然後用mtrace查看結果:

點擊(此處)摺疊或打開

  1. mtrace 1.txt
  2.  
  3. Memory not freed:
  4. -----------------
  5.    Address Size Caller
  6. 0x09849378 0x64 at 0x804849e

  一些GNU C庫提供的標準調試工具可能並不適合你程序的特殊需求。在這種情況下,你可以藉助一個外部的內存調試工具(見 Resource)或者在你的庫內部作修改。做這件事中只是簡單的寫三個函數以及將它們與預先定義的變量相關聯:

  • __malloc_hook points to a function to be called when the user calls malloc(). You can do your own checks and accounting here, and then call the real malloc() to get the memory that was requested.

    __malloc_hook 指向一個函數,當用戶調用malloc()時,這個函數將被調用。你可以在這裏做你自己的檢查和計數,然後調用真實的malloc來得到被請求的內存。

  • __free_hook points to a function called instead of the standard free().

    __free_hook 指向一個函數,用來替換標準的free()

  • __malloc_initialize_hook points to a function called when the memory management system is initialized. This allows you to perform some operations, say, setting the values of the previous hooks, before any memory-related operation takes place.

    __malloc_initialize__hook 指向一個函數,當內存管理系統被初始化的時候,這個函數被調用。這允許你來實施一些操作,例如,在任何內存相關的操作生效前,設置前面的勾子值。

在其它的內存相關的調用中,Hooks()也有效,包括realloc(),calloc()等等。確保在調用malloc()或free()之前,保存先前的勾子的值,把它們存儲起來。如果你不這麼做,你的程序將陷入無盡的遞歸。看看libc info page給的一個內存調試的例子來看看相關細節,最後一點,勾子也被mcheck和mtrace系統使用。在使用所有它們的組合的時候,小心是沒錯的。

而下面的是關於多線程的:

創建線程私有實例 arena_key,該私有實例保存的是分配區( arena )的 malloc_state 實例指針。 arena_key 指向的可能是主分配區的指針,也可能是非主分配區的指針,這裏將調用 ptmalloc_init() 的線程的 arena_key 綁定到主分配區上。意味着本線程首選從主分配區分配內存。

然後調用 thread_atfork() 設置當前進程在 fork 子線程( linux 下線程是輕量級進程,使用類似 fork 進程的機制創建)時處理 mutex 的回調函數,在本進程 fork 子線程時,調用 ptmalloc_lock_all() 獲得所有分配區的鎖,禁止所有分配區分配內存,當子線程創建完畢,父進程調用 ptmalloc_unlock_all() 重新 unlock 每個分配區的鎖 mutex ,子線程調用 ptmalloc_unlock_all2() 重新初始化每個分配區的鎖 mutex

點擊(此處)摺疊或打開

  1. tsd_key_create (&arena_key, NULL);
  2.   tsd_setspecific (arena_key, (void *) &main_arena);
  3.   thread_atfork (ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);

當有多個線程同時申請訪問內存的時候,arena_key的main_arena處於保持互斥鎖狀態,那麼爲了提高效率即上面的代碼,保證了在獲取不到主分區的時候,調用arena_get2自動創建次分區state。見代碼:

點擊(此處)摺疊或打開

  1. #define arena_lookup(ptr) do { \
  2.       void *vptr = NULL;                         \
  3.       ptr = (mstate) tsd_getspecific (arena_key, vptr);             \
  4.   } while (0)

點擊(此處)摺疊或打開

  1. #define arena_lock(ptr, size) do {                     \
  2.       if (ptr)                                 \
  3.         (void) mutex_lock (&ptr->mutex);                 \
  4.       else                                 \
  5.         ptr = arena_get2 (ptr, (size), NULL);                 \
  6.   } while (0)

在繼續之前我們補一下關鍵的數據結構:

點擊(此處)摺疊或打開

  1. struct malloc_state
  2. {
  3.   /* Serialize access. */
  4.   mutex_t mutex;
  5.  
  6.   /* Flags (formerly in max_fast). */
  7.   int flags;
  8.  
  9.   /* Fastbins */
  10.   mfastbinptr fastbinsY[NFASTBINS];
  11.  
  12.   /* Base of the topmost chunk -- not otherwise kept in a bin */
  13.   mchunkptr top;
  14.  
  15.   /* The remainder from the most recent split of a small request */
  16.   mchunkptr last_remainder;
  17.  
  18.   /* Normal bins packed as described above */
  19.   mchunkptr bins[NBINS * 2 - 2];
  20.  
  21.   /* Bitmap of bins */
  22.   unsigned int binmap[BINMAPSIZE];
  23.  
  24.   /* Linked list */
  25.   struct malloc_state *next;
  26.  
  27.   /* Linked list for free arenas. */
  28.   struct malloc_state *next_free;
  29.  
  30.   /* Memory allocated from the system in this arena. */
  31.   INTERNAL_SIZE_T system_mem;
  32.   INTERNAL_SIZE_T max_system_mem;
  33. }

還有具體分配的chunk:關於它的註釋部分這麼就不翻譯了,但需要好好看看。

點擊(此處)摺疊或打開

  1. /*
  2.   ----------------------- Chunk representations -----------------------
  3. */
  4.  
  5.  
  6. /*
  7.   This struct declaration is misleading (but accurate and necessary).
  8.   It declares a "view" into memory allowing access to necessary
  9.   fields at known offsets from a given base. See explanation below.
  10. */
  11.  
  12. struct malloc_chunk {
  13.  
  14.   INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
  15.   INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
  16.  
  17.   struct malloc_chunk* fd; /* double links -- used only if free. */
  18.   struct malloc_chunk* bk;
  19.  
  20.   /* Only used for large blocks: pointer to next larger size. */
  21.   struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  22.   struct malloc_chunk* bk_nextsize;
  23. };
  24.  
  25.  
  26. /*
  27.    malloc_chunk details:
  28.  
  29.     (The following includes lightly edited explanations by Colin Plumb.)
  30.  
  31.     Chunks of memory are maintained using a `boundary tag' method as
  32.     described in e.g., Knuth or Standish. (See the paper by Paul
  33.     Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a
  34.     survey of such techniques.) Sizes of free chunks are stored both
  35.     in the front of each chunk and at the end. This makes
  36.     consolidating fragmented chunks into bigger chunks very fast. The
  37.     size fields also hold bits representing whether chunks are free or
  38.     in use.
  39.  
  40.     An allocated chunk looks like this:
  41.  
  42.  
  43.     chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  44.      | Size of previous chunk, if allocated | |
  45.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  46.      | Size of chunk, in bytes |M|P|
  47.       mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  48.      | User data starts here... .
  49.      . .
  50.      . (malloc_usable_size() bytes) .
  51.      . |
  52. nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  53.      | Size of chunk |
  54.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55.  
  56.  
  57.     Where "chunk" is the front of the chunk for the purpose of most of
  58.     the malloc code, but "mem" is the pointer that is returned to the
  59.     user. "Nextchunk" is the beginning of the next contiguous chunk.
  60.  
  61.     Chunks always begin on even word boundaries, so the mem portion
  62.     (which is returned to the user) is also on an even word boundary, and
  63.     thus at least double-word aligned.
  64.  
  65.     Free chunks are stored in circular doubly-linked lists, and look like this:
  66.  
  67.     chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  68.      | Size of previous chunk |
  69.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  70.     `head:' | Size of chunk, in bytes |P|
  71.       mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  72.      | Forward pointer to next chunk in list |
  73.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  74.      | Back pointer to previous chunk in list |
  75.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  76.      | Unused space (may be 0 bytes long) .
  77.      . .
  78.      . |
  79. nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  80.     `foot:' | Size of chunk, in bytes |
  81.      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  82.  
  83.     The P (PREV_INUSE) bit, stored in the unused low-order bit of the
  84.     chunk size (which is always a multiple of two words), is an in-use
  85.     bit for the *previous* chunk. If that bit is *clear*, then the
  86.     word before the current chunk size contains the previous chunk
  87.     size, and can be used to find the front of the previous chunk.
  88.     The very first chunk allocated always has this bit set,
  89.     preventing access to non-existent (or non-owned) memory. If
  90.     prev_inuse is set for any given chunk, then you CANNOT determine
  91.     the size of the previous chunk, and might even get a memory
  92.     addressing fault when trying to do so.
  93.  
  94.     Note that the `foot' of the current chunk is actually represented
  95.     as the prev_size of the NEXT chunk. This makes it easier to
  96.     deal with alignments etc but can be very confusing when trying
  97.     to extend or adapt this code.
  98.  
  99.     The two exceptions to all this are
  100.  
  101.      1. The special chunk `top' doesn't bother using the
  102.     trailing size field since there is no next contiguous chunk
  103.     that would have to index off it. After initialization, `top'
  104.     is forced to always exist. If it would become less than
  105.     MINSIZE bytes long, it is replenished.
  106.  
  107.      2. Chunks allocated via mmap, which have the second-lowest-order
  108.     bit M (IS_MMAPPED) set in their size fields. Because they are
  109.     allocated one-by-one, each must contain its own trailing size field.
  110.  
  111. */

實際分配的chunk圖,空間裏如何佈局的:

而空的chunk結構如下圖:

因爲後邊代碼就是按照這個結構來操作的.
繼續回到__libc_malloc函數,hook處理完之後,Arena_lookup查詢arena_key,當然不爲空,前面第一次調用hook時已經賦值。(如果多線程下,獲取不到main_arena,則分配次分區,這個前面也討論過)

那麼獲得互斥鎖。

進入內存分配的核心函數_int_malloc,它是malloc分配的核心代碼和實現.代碼挺多,自行分析.

   1.       判斷申請的空間是否在fastbin,如果在則申請返回,否則繼續(<64B,一般小字節chunk釋放後放在這裏)

   2.       判斷申請的空間是否在smallbin(小於512B),如果是申請返回否則在largebin中

   3.       前兩個都不在,那麼肯定在largebin,計算索引,繼續

   4.       進入for(;;)後續處理.主要是垃圾回收工作。如果垃圾回收也不行,則進入use_top chunk
5.       use_top  chunk申請,如果沒有,則調用sysmalloc擴展heap,如下:

點擊(此處)摺疊或打開

  1. /*
  2.          Otherwise, relay to handle system-dependent cases
  3.        */
  4.       else
  5.         {
  6.           void *p = sysmalloc (nb, av);
  7.           if (p != NULL)
  8.             alloc_perturb (p, bytes);
  9.           return p;
  10.         }

對於第一次調用malloc它直接到sysmalloc來擴展heap,自測的例子是先申請200字節的空間.由於程序一開始fast_max爲0,所以肯定在smallbins分類中,但是由於初始化,所以
會調用malloc_consolidate來初始化bins,見malloc_init_state(av);:

點擊(此處)摺疊或打開

  1. /*
  2.    Initialize a malloc_state struct.
  3.  
  4.    This is called only from within malloc_consolidate, which needs
  5.    be called in the same contexts anyway. It is never called directly
  6.    outside of malloc_consolidate because some optimizing compilers try
  7.    to inline it at all call points, which turns out not to be an
  8.    optimization at all. (Inlining it in malloc_consolidate is fine though.)
  9.  */
  10.  
  11. static void
  12. malloc_init_state (mstate av)
  13. {
  14.   int i;
  15.   mbinptr bin;
  16.  
  17.   /* Establish circular links for normal bins */
  18.   for (i = 1; i < NBINS; ++i)
  19.     {
  20.       bin = bin_at (av, i);
  21.       bin->fd = bin->bk = bin;   //  鏈表初始化指向自己
  22.     }
  23.  
  24. #if MORECORE_CONTIGUOUS
  25.   if (av != &main_arena)
  26. #endif
  27.   set_noncontiguous (av);
  28.   if (av == &main_arena)
  29.     set_max_fast (DEFAULT_MXFAST);            //  設置fastbin max 爲 64B
  30.   av->flags |= FASTCHUNKS_BIT;
  31.  
  32.   av->top = initial_top (av);
  33. }

我們進入sysmalloc,一開始判斷申請的nb是否大於需要map的閥值,如果大於則進入mmap。一般大於128K,它可以動態調整

點擊(此處)摺疊或打開

  1. /*
  2.   MMAP_THRESHOLD_MAX and _MIN are the bounds on the dynamically
  3.   adjusted MMAP_THRESHOLD.
  4. */
  5.  
  6. #ifndef DEFAULT_MMAP_THRESHOLD_MIN
  7. #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)
  8. #endif
  9.  
  10. #ifndef DEFAULT_MMAP_THRESHOLD_MAX
  11.   /* For 32-bit platforms we cannot increase the maximum mmap
  12.      threshold much because it is also the minimum value for the
  13.      maximum heap size and its alignment. Going above 512k (i.e., 1M
  14.      for new heaps) wastes too much address space. */
  15. # if __WORDSIZE == 32
  16. # define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)
  17. # else
  18. # define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))
  19. # endif
  20. #endi

然後是判斷是主分配區或非主分配區,分別不同處理。
這裏進入主分配,我們看下部分核心分配代碼:

點擊(此處)摺疊或打開

  1. else /* av == main_arena */
  2.  
  3.  
  4.     { /* Request enough space for nb + pad + overhead */
  5.       size = nb + mp_.top_pad + MINSIZE;
  6.  
  7.       /*
  8.          If contiguous, we can subtract out existing space that we hope to
  9.          combine with new space. We add it back later only if
  10.          we don't actually get contiguous space.
  11.        */
  12.  
  13.       if (contiguous (av))
  14.         size -= old_size;
  15.  
  16.       /*
  17.          Round to a multiple of page size.
  18.          If MORECORE is not contiguous, this ensures that we only call it
  19.          with whole-page arguments. And if MORECORE is contiguous and
  20.          this is not first time through, this preserves page-alignment of
  21.          previous calls. Otherwise, we correct to page-align below.
  22.        */
  23.  
  24.       size = (size + pagemask) & ~pagemask;
  25.  
  26.       /*
  27.          Don't try to call MORECORE if argument is so big as to appear
  28.          negative. Note that since mmap takes size_t arg, it may succeed
  29.          below even if we cannot call MORECORE.
  30.        */
  31.  
  32.       if (size > 0)
  33.         {
  34.           brk = (char *) (MORECORE (size));
  35.           LIBC_PROBE (memory_sbrk_more, 2, brk, size);
  36.         }
  37.  
  38.       if (brk != (char *) (MORECORE_FAILURE))
  39.         {
  40.           /* Call the `morecore' hook if necessary. */
  41.           void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
  42.           if (__builtin_expect (hook != NULL, 0))
  43.             (*hook)();
  44.         }
  45.       else
  46.         {

由於申請的是200B,8字節對齊爲208B,而mp_.top_pad用的默認值爲7個pages(0x20000),MINSIZE爲16B.後邊還需要page對齊,所以需要申請8個page。
下面我們看關鍵的代碼:

點擊(此處)摺疊或打開

  1. brk = (char *) (MORECORE (size));

這個是什麼?

點擊(此處)摺疊或打開

  1. /* Definition for getting more memory from the OS. */
  2. #define MORECORE (*__morecore)
  3. #define MORECORE_FAILURE 0
  4. void * __default_morecore (ptrdiff_t);
  5. void *(*__morecore)(ptrdiff_t) = __default_morecore;
  6.  
  7.  
  8. #include <string.h>
  9.  
  10. /*
  11.   MORECORE-related declarations. By default, rely on sbrk
  12. */
  13.  
  14.  
  15. /*
  16.   MORECORE is the name of the routine to call to obtain more memory
  17.   from the system. See below for general guidance on writing
  18.   alternative MORECORE functions, as well as a version for WIN32 and a
  19.   sample version for pre-OSX macos.
  20. */
  21.  
  22. #ifndef MORECORE
  23. #define MORECORE sbrk
  24. #endif

__default_morecore是:
 

點擊(此處)摺疊或打開

  1. /* Allocate INCREMENT more bytes of data space,
  2.    and return the start of data space, or NULL on errors.
  3.    If INCREMENT is negative, shrink data space. */
  4. void *
  5. __default_morecore (ptrdiff_t increment)
  6. {
  7.   void *result = (void *) __sbrk (increment);
  8.   if (result == (void *) -1)
  9.     return NULL;
  10.  
  11.   return result;
  12. }
  13. libc_hidden_def (__default_morecore)

這裏解釋下sbrk:

    sbrk不是系統調用,是C庫函數。系統調用通常提供一種最小功能,而庫函數通常提供比較複雜的功能。sbrk/brk是從堆中分配空間,本質是移動一個位置,向後移就是分配空間,向前移就是釋放空間,sbrk用相對的整數值確定位置,如果這個整數是正數,會從當前位置向後移若干字節,如果爲負數就向前若干字節。在任何情況下,返回值永遠是移動之前的位置。sbrk是brk的封裝。
默認mp_.sbrk_base爲空。所以需要:

點擊(此處)摺疊或打開

  1. if (mp_.sbrk_base == 0)
  2.             mp_.sbrk_base = brk;

 av->system_mem默認也爲0 

點擊(此處)摺疊或打開

  1. av->system_mem += size;

然後需要做一些調整:

點擊(此處)摺疊或打開

  1. /*
  2.              Otherwise, make adjustments:
  3.  
  4.            * If the first time through or noncontiguous, we need to call sbrk
  5.               just to find out where the end of memory lies.
  6.  
  7.            * We need to ensure that all returned chunks from malloc will meet
  8.               MALLOC_ALIGNMENT
  9.  
  10.            * If there was an intervening foreign sbrk, we need to adjust sbrk
  11.               request size to account for fact that we will not be able to
  12.               combine new space with existing space in old_top.
  13.  
  14.            * Almost all systems internally allocate whole pages at a time, in
  15.               which case we might as well use the whole last page of request.
  16.               So we allocate enough more memory to hit a page boundary now,
  17.               which in turn causes future contiguous calls to page-align.
  18.            */
  19.  
  20.           else
  21.             {
  22.               front_misalign = 0;
  23.               end_misalign = 0;
  24.               correction = 0;
  25.               aligned_brk = brk;
  26.  
  27.               /* handle contiguous cases */
  28.               if (contiguous (av))
  29.                 {
  30.                   /* Count foreign sbrk as system_mem. */

後面有這麼一句:由於correction爲0,所以返回當前的值.

點擊(此處)摺疊或打開

  1. snd_brk = (char *) (MORECORE (correction));

最後來分配空間:
 

點擊(此處)摺疊或打開

  1. /* finally, do the allocation */
  2.   p = av->top;    
  3.   size = chunksize (p);
  4.  
  5.   /* check that one of the above allocation paths succeeded */
  6.   if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))    // size 爲top->size  ,爲剛申請空間的大小. 我們的例子是0x21000 (8pages)而nb爲208B
  7.     {
  8.       remainder_size = size - nb;// 0x2100 - 208(0xd0)
  9.       remainder = chunk_at_offset (p, nb);
  10.       av->top = remainder;                                 // 改變top的指針                                              
  11.       set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
  12.       set_head (remainder, remainder_size | PREV_INUSE);
  13.       check_malloced_chunk (av, p, nb);
  14.       return chunk2mem (p);
  15.     }

av->top是什麼值呢?

點擊(此處)摺疊或打開

  1. /* Adjust top based on results of second sbrk */
  2.               if (snd_brk != (char *) (MORECORE_FAILURE))
  3.                 {
  4.                   av->top = (mchunkptr) aligned_brk;    //      aligned_brk = brk;  當然如果需要對齊,aligned_brk會偏移一些字節

也就是top就是一個指向heap開始的指針.並轉換爲struct malloc_chunk 指針.和我們上面的 圖就對應起來了。

然後重新設置top指針,和size的標誌位,偏移過pre_size和size,就是實際數據地址即return chunk2mem (p);

如果我們緊接着申請了200B後,馬上申請16B,由於fastbins雖然設置了max 爲64B但是它裏面的chunk是free的時候放置進來的,目前爲空。
所以繼續進入smallbin。同理由於沒有free的small chunk 。所以進入top chunk 分配成功:

點擊(此處)摺疊或打開

  1. use_top:
  2.       /*
  3.          If large enough, split off the chunk bordering the end of memory
  4.          (held in av->top). Note that this is in accord with the best-fit
  5.          search rule. In effect, av->top is treated as larger (and thus
  6.          less well fitting) than any other available chunk since it can
  7.          be extended to be as large as necessary (up to system
  8.          limitations).
  9.  
  10.          We require that av->top always exists (i.e., has size >=
  11.          MINSIZE) after initialization, so if it would otherwise be
  12.          exhausted by current request, it is replenished. (The main
  13.          reason for ensuring it exists is that we may need MINSIZE space
  14.          to put in fenceposts in sysmalloc.)
  15.        */
  16.  
  17.       victim = av->top;
  18.       size = chunksize (victim);
  19.  
  20.       if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
  21.         {
  22.           remainder_size = size - nb;
  23.           remainder = chunk_at_offset (victim, nb);
  24.           av->top = remainder;
  25.           set_head (victim, nb | PREV_INUSE |
  26.                     (av != &main_arena ? NON_MAIN_ARENA : 0));
  27.           set_head (remainder, remainder_size | PREV_INUSE);
  28.  
  29.           check_malloced_chunk (av, victim, nb);
  30.           void *p = chunk2mem (victim);
  31.           alloc_perturb (p, bytes);
  32.           return p;
  33.         }

當然如果我們釋放了16B後,有馬上申請16B,那麼它會直接進入fastbin並申請返回成功。,這裏我們知道當第一次使用的時候不論什麼bin都是空的,只有當多次使用多次釋放的時候纔會體會出來它的優勢和效率來.
這裏附上自己測試的小程序:

點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. int main(void)
  6. {
  7.  
  8.   char *p,*q;
  9.   void * brk;
  10.  brk=sbrk(0);
  11. printf("brk is ....%p...\n",brk);
  12.   p = (char *)malloc(200);
  13.  if(p==NULL)
  14.   return 1;
  15.  
  16.  strcpy(p,"hello");
  17.  
  18.   brk=sbrk(100);
  19. printf("111....brk is ....%p...\n",brk);
  20.  printf("200,p is %s...\n",p);
  21.  
  22.   q= (char *)malloc(16);
  23.   if(q==NULL)
  24.    return 1;
  25.  
  26.  strcpy(q,"nihao");
  27.  
  28.  printf("16,q is %s...\n",q);
  29.  
  30.  free(q);
  31.  free(p);
  32.  
  33.   p= (char *)malloc(16);
  34.  
  35.  return 0;
  36.  
  37. }

關於不論fastbin還是smallbin的機制,或許我們記得在《深入理解計算機系統》中,講到垃圾回收的時候,腳註法。書和代碼一起看效果會不錯.
內存的延遲分配,只有在真正訪問一個地址的時候才建立這個地址的物理映射,這是Linux內存管理的基本思想之一
還有就是:

內核默認配置下,進程的棧和mmap映射區域並不是從一個固定地址開始,並且每次啓動時的值都不一樣,這是程序在啓動時隨機改變這些值的設置,使得使用緩衝區溢出進行攻擊更加困難。當然也可以讓進程的棧和mmap映射區域從一個固定位置開始,只需要設置全局變量randomize_va_space值爲0,這個變量默認值爲1。用戶可以通過設置/proc/sys/kernel/randomize_va_space來停用該特性,也可以用如下命令:  sudo sysctl -w kernel.randomize_va_space=0

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 4574
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章