InnoDB內存管理源碼剖析

InnoDB的內存管理分爲3層。1、在底層InnoDB建立一個通用內存池,負責爲系統提供小塊內存,另外InnoDB還建立緩衝池,能夠爲系統提供更大塊的內存。二者都是向系統申請內存,只申請一次。其中,通用內存池只由中間層內存堆直接使用。2、在中間層建立一個內存堆對象,內存堆能夠調用mem_area_alloc函數從通用內存池申請內存,稱爲動態申請,也能夠一次調用buf_frame_alloc函數從緩衝池快速申請一頁(默認16KB)內存,稱爲緩衝池申請。實現上,內存堆就像是一個棧,每次申請的內存塊由雙鏈表連接,它能夠無限增加,可是它在釋放時,每次只能釋放棧頂的內存塊,或者一次性釋放整個內存堆的全部內存塊。3、最上層爲InnoDB的各個模塊,當某個模塊須要使用動態申請內存時,其向內存堆申請內存。使用模塊分層設計的好處在於:(1)內存池一次從系統申請釋放一大塊內存,避免頻繁調用mallocfree,提升性能。(2)一次申請一塊大內存,減小內存外碎片。(3)容許內存堆從緩衝池快速申請一頁內存。(4)分層設計實現功能的拆分,便於管理與實現。 node

          

InnoDB內存管理層次圖 數據結構

下面說明InnoDB內存管理是如何工做的,使用內存塊指明在內存堆中的由指針連接的一個個內存塊,使用內存區指明內存池中由free_list列表管理的大小爲2^i的多個連續內存區域。首先須要說明幾個數據結構: 函數

1TYPE類型雙向鏈表 性能

#define UT_LIST_NODE_T(TYPE)\ spa

struct {\ 操作系統

       TYPE *   prev;       /* pointer to the previous node,\ 設計

                     NULL if start of list */\ 指針

       TYPE *   next;       /* pointer to next node, NULL if end of list */\ 對象

}\ 遞歸

2、用於管理TYPE類型雙向鏈表的結構,指出鏈表包含的節點個數以及首尾節點

#define UT_LIST_BASE_NODE_T(TYPE)\

struct {\

       ulint count;     /* count of nodes in list */\

       TYPE *   start;       /* pointer to list start, NULL if empty */\

       TYPE *   end; /* pointer to list end, NULL if empty */\

}\

3、通用內存池結構

struct mem_pool_struct{

       byte*      buf; /* 指向從操做系統申請獲得的整個內存區域,全部的內存池操做都是在這一塊大的內存區域上完成 */

       ulint        size; //內存池的大小

       ulint        reserved; //已經分配(使用)的內存大小

       mutex_t          mutex; //保護內存池的互斥量

       UT_LIST_BASE_NODE_T(mem_area_t)

                     free_list[64];   /*一個用來管理內存池的列表,第i個負責管理2^i大小的內存區 */

};

4、內存區頭

struct mem_area_struct{
    
ulint  size_and_free;  /* memory area size is obtained by
                    anding with ~MEM_AREA_FREE; area in
                    a free list if ANDing with
                    MEM_AREA_FREE results in nonzero */
 
    
UT_LIST_NODE_T(mem_area_t)
            
free_list;  /* free list node */

};

InnoDB系統啓動時內存管理模塊的函數調用順序以下。

 

InnoDB進程啓動後內存管理模塊初始化函數調用圖

第一步,系統啓動函數首先調用mem_pool_create函數從操做系統申請(malloc)內存並建立一個類型爲mem_pool_struct的通用內存池實例mem_pool_struct *mem_comm_pool,其大小由參數srv_mem_pool_size定義,默認爲8MB. 完成建立以後的內存池示意圖:


初始狀態的通用內存池

初始化以後,通用內存池向系統申請8MB內存,由free_list[23]元素管理該內存區。

第二步,調用mem_heap_create_func函數建立內存堆結構,並調用mem_heap_create_block函數建立其中的首個內存塊。mem_heap_create_block調用mem_area_alloc函數從通用內存池申請內存,內部mem_area_alloc函數根據所申請的內存塊大小計算該內存塊須要被第ifree_list元素管理,若是free_list[i]上沒有空閒內存,則調用mem_pool_fill_free_list函數,其根據該所需內存塊所在free_list列表的位置,即i,遞歸調用本身,以從後續有空閒內存的free_list元素中切割它的內存區,而後將分半的內存區逐個向前傳遞,最終到達free_list[i],因而將該塊內存返回給mem_heap_create_block

例如,建立內存堆時須要申請一個大小爲1MB的內存塊(由於每次申請內存堆中的內存塊時大小會成倍增加,所以首次申請大小一般較小,系統默認首次申請大小爲MEM_BLOCK_START_SIZE,爲64B,此處設爲1MB,爲了示意方便。),建立完成後內存堆的示意圖以下。


初始狀態的內存堆,只有一個可用內存塊

上圖所示,二分切割8MB的內存區,分爲兩個4MB的內存區,將第一個4MB區切割成兩個2MB內存區,切割第一個2MB內存區獲得兩個空閒的1MB的內存區,將第一個空閒的1MB內存區分配和內存堆,每一個內存區的起始處包含一個mem_area_t結構,size_and_free記錄當前內存區大小和是否已分配,同一free_list元素管理的空閒內存區由該結構中的兩個指針prevnext連接起來。

內存池和內存堆建立完成以後,內存堆就可使用了,當InnoDB的某個模塊須要申請動態內存時,僅需其調用mem_heap_alloc向內存堆申請內存。mem_heap_alloc調用圖以下。

    

mem_heap_alloc函數調用圖

mem_heap_alloc每次先找到內存堆中的最後一個內存塊,檢查該內存塊的空閒空間是否足夠,若是足夠,就在該內存塊上劃出適量空間(將申請內存的size 8字節向上對齊),若是當前快空閒空間不足,則調用mem_heap_add_block向內存池申請內存,由mem_heap_create_block函數建立新的內存塊,再重新的內存塊中劃出空間返回給上層應用。

內存堆釋放內存時,與內存的分配相反。先釋放內存堆中的最後一個內存塊的空間,且在內存塊中釋放每小塊上層應用申請的內存時, mem_heap_free_heap_top只能自後向前釋放,每次釋放只需修改該內存塊結構的free值,以標誌當前塊中空閒空間的偏移,若是當前內存塊的空間所有釋放,則調用mem_heap_block_free從堆中刪除該內存塊,該函數調用mem_area_free將該內存塊的空間還給內存池。因爲內存池採用夥伴系統來管理內存,所以mem_area_free首先調用mem_area_get_buddy函數獲取當前內存區的夥伴buddy,而後判斷buddy是否是空閒的,若是不是,則只需將該塊內存區交給其對應的free_list元素管理;若是buddy是空閒的,則將該內存區與buddy合併,並遞歸調用mem_area_free函數,直到空閒內存區沒法與其夥伴合併爲大的空閒內存區,而後將獲得的大塊內存區交給對應的free_list元素管理。某個須要釋放內存的函數調用順序以下圖所示。


釋放內存的函數調用順序圖

相關文章
相關標籤/搜索