InnoDB的內存管理分爲3層。1、在底層InnoDB建立一個通用內存池,負責爲系統提供小塊內存,另外InnoDB還建立緩衝池,能夠爲系統提供更大塊的內存。二者都是向系統申請內存,只申請一次。其中,通用內存池只由中間層內存堆直接使用。2、在中間層建立一個內存堆對象,內存堆能夠調用mem_area_alloc函數從通用內存池申請內存,稱爲動態申請,也能夠一次調用buf_frame_alloc函數從緩衝池快速申請一頁(默認16KB)內存,稱爲緩衝池申請。實現上,內存堆就像是一個棧,每次申請的內存塊由雙鏈表連接,它能夠無限增加,可是它在釋放時,每次只能釋放棧頂的內存塊,或者一次性釋放整個內存堆的全部內存塊。3、最上層爲InnoDB的各個模塊,當某個模塊須要使用動態申請內存時,其向內存堆申請內存。使用模塊分層設計的好處在於:(1)內存池一次從系統申請釋放一大塊內存,避免頻繁調用malloc、free,提升性能。(2)一次申請一塊大內存,減小內存外碎片。(3)容許內存堆從緩衝池快速申請一頁內存。(4)分層設計實現功能的拆分,便於管理與實現。 node
InnoDB內存管理層次圖 數據結構
下面說明InnoDB內存管理是如何工做的,使用內存塊指明在內存堆中的由指針連接的一個個內存塊,使用內存區指明內存池中由free_list列表管理的大小爲2^i的多個連續內存區域。首先須要說明幾個數據結構: 函數
1、TYPE類型雙向鏈表 性能
#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函數根據所申請的內存塊大小計算該內存塊須要被第i個free_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元素管理的空閒內存區由該結構中的兩個指針prev、next連接起來。
內存池和內存堆建立完成以後,內存堆就可使用了,當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元素管理。某個須要釋放內存的函數調用順序以下圖所示。
釋放內存的函數調用順序圖