Linux簡易APR內存池學習筆記(帶源碼和實例)

  先給個內存池的實現代碼,裏面帶有個應用小例子和畫的流程圖,方便了解運行原理,代碼 GCC 編譯可用。能夠本身上網下APR源碼,參考代碼下載連接:html

  http://pan.baidu.com/s/1hq6A20G
node

  貼兩個以前學習的時候參考的文章地址,你們能夠參考:數組

  http://www.cnblogs.com/bangerlee/archive/2011/09/01/2161437.htmlpost

  http://blog.csdn.net/flyingfalcon/article/details/2627965學習

一.引言

  簡單介紹下內存池。使用內存池技術是爲了不用戶向系統申請和釋放內存不當而形成內存泄露問題,且頻繁的內存分配均可能致使效率的降低甚至程序崩潰。使用內存池來管理內存就能很好地避免上面的問題,而且不會產生或不多產生內存碎片。this

  內存池一般是一塊很大的內存空 間,一次性被分配成功,而後須要的時候直接去池中取,而不須要從新分配,這樣避免的頻繁的malloc操做,並且另外一方面,即時內存的使用者忘記釋放內存 或者根本就不想分配,那麼這些內存也不會丟失,它們仍然保存在內存池中,當內存池被銷燬的時候這些內存將自動的被銷燬。spa

  內存池的實現方法有不少,這裏只是就我手頭上代碼資料的學習筆記,但內存池的原理應該都是相近的,可作參考。操作系統

二.內存池結構

2.1. 內存分配結點

  內是APR內存池中最基本的單元,爲了可以方便的對分配的內存進行管理,APR內存池中使用了內存結點的概念來描述每次分配的內存塊。對應的結構名爲apr_memnode_t,定義以下:.net

複製代碼
1 /*內存分配結點*/
 2 struct apr_memnode_t 
 3 {
 4     pthread_mutex_t        m_tLock;    /**< Lock */
 5     apr_memnode_t        *next;        /**< next memnode */
 6     apr_memnode_t        **ref;        /**< reference to self */
 7     int            index;        /**< size */
 8     apr_allocator_t        *m_pool;    /**< pointer to memory pool*/
 9     char            *m_bData;    /**< pointer to free memory address*/
10 #ifdef PRINTF
11     int            m_iFreeFlg;    /**< 重複釋放標識 */
12 #endif
13 };
複製代碼

  部分結構成員說明:線程

  next:結點鏈表上的下一個內存分配結點(內存單元)

  ref:指向內存分配結點自己,該變量主要用來記錄當前結點的首地址,即便身在結點內部,也能夠經過ref指針獲得該結點並對該結點進行操做(手頭上的代碼沒使用這個成員,預留)

  index:指示了內存分配結點的大小,同時指示了該結點所在結點鏈表的索引下標值,索引值轉換爲實際內存大小爲:index*apr_allocator_t->m_BoundarySize 字節,即索引值和增量大小相乘;index的本意是索引,但這裏更重要的另外一種概念是指內存大小,這二者含義是相結合共存的!

  m_pool:指向所屬的內存分配器

  m_bData:實際分配給用戶使用的內存地址

  內存分配結點結構以下:

 

2.2. 內存分配器

  內存池中,使用內存分配器對內存分配結點進行管理,包括內存的申請、釋放、銷燬等。其結構名爲apr_allocator_t,定義以下:

複製代碼
1 /*內存分配器*/
 2 struct apr_allocator_t 
 3 {
 4     int    max_index;        /**< 當前內存池中已有的最大內存結點鏈表索引 */
 5     int    max_free_index;        /**< 內存池的內存空間所能容納的最大數值 */
 6     int    current_free_index;    /**< 當前內存池空間還能容納的數值 */
 7     pthread_mutex_t    m_tLock;    /**< 內存池互斥鎖 */
 8     int    *owner;            /**< 指示該分配器屬於哪一個內存池 */
 9     apr_memnode_t    **free;        /**< 指向一組鏈表的頭結點,該鏈表中每一個結點指向內存結點組成的鏈表 */
10 
11     int    m_MinAlloc;        /**< 限定分配的最小規則內存結點大小 */
12     int    m_MaxIndex;        /**< 限定可以分配的最大規則內存鏈表索引 */
13     int    m_ArpUint32Max;        /**< 限定單次所能分配的最大內存結點 */
14     int    m_BoundaryIndex;    /**< 限定內存結點大小的遞增指數,以2爲底的指數 */
15     int    m_BoundarySize;        /**< 限定內存結點大小的遞增值 */
16 
17     /*內存池銷燬*/
18     void (*Destruct)(void *pthis);
19     /*從內存池申請內存*/
20     void *(*mempool_alloc)(void *pthis, int  _size);
21     /*從內存池釋放內存*/
22     void (*mempool_free)(void* _node);
23     /*給內存結點加鎖*/
24     void (*node_lock)(void* _node);
25     /*給內存結點解鎖*/
26     void (*node_unlock)(void* _node);    
27 }
複製代碼

   部分結構成員說明:

  max_index:free指針數組的下標索引,free[max_index-1]指向已有的最大內存結點鏈表;

  max_free_index:內存池的內存空間所能容納的最大數值,指的是全部內存分配結點apr_memnode_t->index的加權值;

  m_MaxIndex:最大內存索引不表明最大內存大小,超過該內存大小的內存結點被分配到free[0]鏈表中; 

2.3. 內存池結構

  內存池的運行結構圖以下:

  上圖中內存單元和內存結點爲同一個概念,這裏羅列幾個要點:

  (1) 用戶正在使用的內存單元和內存池中的內存單元是不屬於同個區域的,用戶使用的是分配出去的內存單元,而內存池保存的是待分配的內存單元,即空閒的內存塊。(這一點容易和線程池的概念混淆)

  (2) 初始建立內存池時,內存池裏是沒有內存單元的。用戶申請內存單元時,會遍歷鏈表組尋找是否有適合的內存單元,有則從內存池中分配出去,沒有則從新malloc()一塊內存給用戶,用戶使用完後釋放到內存池中或直接釋放回系統。

  (3) free數組的下標從1到m_MaxIndex-1,分別指向一條內存結點大小固定的鏈表,下標增長1,結點的大小增長4k(m_BoundarySize),可根據實際設定。最小的內存分配結點爲free[1]所指向的鏈表,大小爲8k(m_MinAlloc),可設定。所以free[m_MaxIndex-1]所指向的鏈表的結點大小爲8+4*(m_MaxIndex-2)k,這也是內存池使用者所能申請的最大」規則結點「,超過該大小的內存結點將使用下標free[0]指向的鏈表進行管理。

三.內存池管理

3.1. 內存大小計算

  首先了解下內存是如何作到取整分配的,主要用到下面這個宏:

#define APR_ALIGN(size, boundary)    (((size) + ((boundary) - 1)) & ~((boundary) - 1))    /**< 將size往上"取整到"大於size的最小的boundary的倍數*/  

  宏用來計算最接近size的boundary的整數倍的整數,而且大於size,boundary必須爲2的倍數。其它使用的宏,如APR_ALIGN_DEFAULT (size)實際上轉化爲APR_ALIGN (size,8),即進行「8字節對齊」。

  另外兩個用來計算結構體對齊大小的宏:

#define SIZEOF_ALLOCATOR_T    APR_ALIGN_DEFAULT(sizeof(apr_allocator_t))    /**< 內存分配器結構大小*/
#define APR_MEMNODE_T_SIZE    APR_ALIGN_DEFAULT(sizeof(apr_memnode_t))    /**< 內存結點結構大小*/

  這兩個宏實際上就是將結構體內存分配結點apr_memnode_t和內存分配器apr_allocator_t的大小進行8字節對齊。

  對於每次空間申請,先對齊空間大小:

1 size = APR_ALIGN(_size + APR_MEMNODE_T_SIZE, allocator->m_BoundarySize);    /**< 轉換爲4K倍數 */
2 if (size < allocator->m_MinAlloc)
3 {
4     size = allocator->m_MinAlloc;    /**< 容許分配的最小內存 */
5 }

  結果是size的值變成4096(m_BoundarySize = 4k,2的12次方)的倍數,即咱們申請的內存結點的實際大小(結點頭+內存空間)。而後與最小分配規則內存進行對比,最小內存單元爲8K,即m_MinAlloc = 8K。  

index = (size >> allocator->m_BoundaryIndex) - 1;    /**< 換算內存大小對應的索引值 */

  最後,經過左移與鏈表組的下標索引進行關聯,即free[index]。m_BoundaryIndex = 12,即size除於4K。

3.2. 內存池建立    

複製代碼
1 apr_allocator_t * AllocatorPoolCreate()
 2 {
 3     apr_allocator_t *new_allocator;
 4     if ((new_allocator = (apr_allocator_t*)malloc(SIZEOF_ALLOCATOR_T)) == NULL)
 5     {
 6         return NULL;
 7     }
 8     
 9     memset(new_allocator, 0, SIZEOF_ALLOCATOR_T);
10     new_allocator->max_free_index = APR_ALLOCATOR_MAX_FREE_UNLIMITED;    /**< 內存空間不做限制 */
11     if ((new_allocator->free = (apr_memnode_t**)malloc(APR_MEMNODE_T_SIZE * DEFAULT_MAX_INDEX)) == NULL)
12     { 
13         free(new_allocator);
14         return NULL;
15     }
16     
17     memset(new_allocator->free, 0, APR_MEMNODE_T_SIZE * DEFAULT_MAX_INDEX);
18     ......    //略
19     pthread_mutex_init(&(new_allocator->m_tLock), NULL);
20     return new_allocator;
21 }
複製代碼

  首先,建立內存分配器併爲其申請內存空間,一樣建立結點鏈表組並分配內存空間。初始化成員變量,其中,須要注意的是new_allocator->max_free_index被賦值爲APR_ALLOCATOR_MAX_FREE_UNLIMITED,值爲0。這條代碼的意思是,內存池的最大可容納內存空間不做限制,即不存在內存池空間受限將內存單元直接返回給系統。

3.3. 內存池銷燬

  內存池的銷燬較簡單,就是將內存池申請的內存空間所有釋放給系統。須要注意的是空間釋放的順序,先釋放內存結點,再釋放鏈表組,最後釋放內存分配器。

3.4. 內存申請 

  內存申請就是用戶向內存池申請內存空間,內存池查找自身內存空間中合適的內存單元並分配給用戶,也可能不存在知足要求的內存單元,則須要從新從系統申請。內存申請的策略以下:

  (1) 根據申請空間的大小size,生成索引index,若是索引數值在1~max_index範圍內,那就在index~ max_index範圍內的鏈表中返回一塊內存;

  (2) 若是索引數值index > max_index-1,則在free[0]鏈表中查找一塊合適的內存;

  (3) 經上兩步仍未找到空閒內存塊,則經過malloc(size)返回一塊新生成的內存;

  在這裏說明一下max_index 和 m_MaxIndex這兩個變量之間的關係:max_index <= m_MaxIndexm_MaxIndex 指的是最大規則內存結點鏈表組的索引大小,即建立內存池時申請的鏈表組大小,剛建立時全部的鏈表都是爲空,free[1~ m_MaxIndex-1] == NULL;max_index 指的是當前鏈表組存在的最大可用索引,即 free[max_index]!=NULL,不過 free[0~ max_index-1] 之間不必定存在可用內存結點,free[max_index+1~ m_MaxIndex]==NULL。

  下面就內存申請的3個策略代碼進行分析:

複製代碼
//策略(1)
1 if (index <= allocator->max_index) 2 { /**< 代表當前的內存池內有內存結點可分配 */ 3 pthread_mutex_lock(&(allocator->m_tLock)); 4 5 max_index = allocator->max_index; 6 ref = &allocator->free[index]; /**< 取鏈表上的對應索引結點 */ 7 i = index; 8 9 while (*ref == NULL && i < max_index) 10 { /**< 遍歷鏈表直到找到可分配的內存結點 */ 11 ref++; 12 i++; 13 } 14 15 if ((node = *ref) != NULL) 16 { 17 if ((*ref = node->next) == NULL && i >= max_index) 18 { /**< 若是所分配的結點爲最大可用內存結點,且該鏈表上只有一個內存結點 */ 19 do 20 { 21 ref--; 22 max_index--; 23 } 24 while (*ref == NULL && max_index > 0); 25 allocator->max_index = max_index; /**< 從新設置最大可用內存索引 */ 26 } 27 28 allocator->current_free_index += node->index; /**< 更新內存池還能容納的內存大小 */ 29 if (allocator->current_free_index > allocator->max_free_index) 30 { 31 allocator->current_free_index = allocator->max_free_index; /**< 當前可容納的內存大小不能超過最大內存大小 */ 32 } 33 pthread_mutex_unlock(&(allocator->m_tLock)); 34 node->next = NULL; 35 node->m_bData = (char *)node + APR_MEMNODE_T_SIZE; /**< 用戶使用的內存地址 */ 36 37 #ifdef PRINTF 38 node->m_iFreeFlg = 0; 39 #endif 40 return node->m_bData; 41 } 42 pthread_mutex_unlock(&(allocator->m_tLock)); 43 }
複製代碼

  代碼19行的循環用來對鏈表組進行整理,當free[max_index]鏈表下的惟一內存結點被分配後,內存池中可用的最大索引max_index應該等待更新,循環就是用來尋找新的最大可用索引;

  代碼35行爲計算要分配內存的地址,這裏的地址不是內存結點的地址,而是實際給用戶使用的內存空間地址;

 

複製代碼
//策略(2)
1 else if (allocator->free[0]) 2 { /**< 超過限定的規則內存大小,則在這裏尋找合適的內存結點 */ 3 pthread_mutex_lock(&(allocator->m_tLock)); 4 ref = &allocator->free[0]; /**< 取free[0]頭結點 */ 5 while ((node = *ref) != NULL && index > node->index) 6 { /**< 遍歷free[0]鏈表,尋找合適的內存結點 */ 7 ref = &node->next; 8 } 9 10 if (node) 11 { /**< 找到可用的內存結點 */ 12 *ref = node->next; 13 allocator->current_free_index += node->index; 14 if (allocator->current_free_index > allocator->max_free_index) 15 { 16 allocator->current_free_index = allocator->max_free_index; 17 } 18 19 pthread_mutex_unlock(&(allocator->m_tLock)); 20 node->next = NULL; 21 node->m_bData = (char *)node + APR_MEMNODE_T_SIZE; /**< 用戶使用的內存地址 */ 22 23 #ifdef PRINTF 24 node->m_iFreeFlg = 0; 25 #endif 26 return node->m_bData; 27 } 28 pthread_mutex_unlock(&(allocator->m_tLock)); 29 }
複製代碼

  代碼分析看註釋。

 

複製代碼
//策略(3)
1 if ((node = (apr_memnode_t*)malloc(size)) == NULL) /**< 找不到可分配的內存結點則從新向系統申請 */ 2 { 3 return NULL; 4 } 5 node->next = NULL; 6 node->index = index; 7 node->m_bData = (char *)node + APR_MEMNODE_T_SIZE; 8 node->m_pool = allocator; 9 10 #ifdef PRINTF 11 node->m_iFreeFlg = 0; 12 #endif 13 14 pthread_mutex_init(&(node->m_tLock), NULL); /**< 初始化內存結點的互斥鎖 */ 15 return node->m_bData;
複製代碼

  代碼8行m_pool成員指向所屬的內存池。

3.5. 內存釋放

  內存釋放是用戶在使用完內存後將內存單元從新返還給內存池或系統,須要注意的是,在內存申請時從系統中新分配的內存是直接給用戶使用,不劃分在內存池裏面,即並不掛接到內存分配器鏈表free[index]中。內存釋放的策略以下:

  (1) 若是結點的大小超過了當前內存池所能容納的空間current_free_index,那麼就不能將其簡單的歸還到索引鏈表中,而必須將其徹底歸還給操做系統;

  (2) 若是index< m_MaxIndex,則意味着該結點屬於「規則結點」的範圍,所以能夠將該結點返回到對應的「規則鏈表free[1~ m_MaxIndex-1]」中;

  (3) 若是結點超過了「規則結點」的範圍,可是並無超過當前可以容納的空間current_free_index,此時咱們則能夠將其置於free[0]鏈表的首部中;

  下面就內存釋放的3個策略代碼進行分析:

複製代碼
//策略(1)
1 if (max_free_index != APR_ALLOCATOR_MAX_FREE_UNLIMITED && index > current_free_index) 2 { /**< 超過內存池所能容納的數值 */ 3 node->next = freelist; 4 freelist = node; 5 } 6 ...... //略 7 while (freelist != NULL) 8 { /**< 釋放內存 */ 9 node = freelist; 10 freelist = node->next; 11 pthread_mutex_destroy(&(node->m_tLock)); 12 free(node); 13 node = NULL; 14 }
複製代碼

  APR_ALLOCATOR_MAX_FREE_UNLIMITED 這個宏做爲內存池大小不做限制的標誌,代碼中 freelist 鏈表用來保存釋放給系統的內存結點。

 

複製代碼
//策略(2)
1 else if (index < allocator->m_MaxIndex) 2 { /**< 未超過最大規則內存結點大小 */ 3 if ((node->next = allocator->free[index]) == NULL && index > max_index) 4 { /**< 超過當前最大可分配內存結點 */ 5 max_index = index; 6 } 7 8 #ifdef PRINTF 9 node->m_iFreeFlg = 1; 10 #endif 11 allocator->free[index] = node; /**< 放入鏈表頭 */ 12 13 if (current_free_index >= index) 14 current_free_index -= index; /**< 更新可容納的內存大小 */ 15 else 16 current_free_index = 0; 17 }
複製代碼

  代碼分析看註釋。

 

複製代碼
//策略(3)
1 else 2 { /**< 超過最大規則內存結點大小 */ 3 #ifdef PRINTF 4 node->m_iFreeFlg = 1; 5 #endif 6 node->next = allocator->free[0]; 7 8 allocator->free[0] = node; /**< 放入free[0]鏈表 */ 9 if (current_free_index >= index) 10 current_free_index -= index; /**< 更新可容納的內存大小 */ 11 else 12 current_free_index = 0; 13 }
複製代碼

  代碼分析看註釋。 

相關文章
相關標籤/搜索