InnoDB内存管理源码剖析

InnoDB的内存管理分为3层。1、在底层InnoDB创建一个通用内存池,负责为系统提供小块内存,另外InnoDB还创建缓冲池,可以为系统提供更大块的内存。两者都是向系统申请内存,只申请一次。其中,通用内存池只由中间层内存堆直接使用。2、在中间层创建一个内存堆对象,内存堆可以调用mem_area_alloc函数从通用内存池申请内存,称为动态申请,也可以一次调用buf_frame_alloc函数从缓冲池快速申请一页(默认16KB)内存,称为缓冲池申请。实现上,内存堆就像是一个栈,每次申请的内存块由双链表链接,它可以无限增长,但是它在释放时,每次只能释放栈顶的内存块,或者一次性释放整个内存堆的所有内存块。3、最上层为InnoDB的各个模块,当某个模块需要使用动态申请内存时,其向内存堆申请内存。使用模块分层设计的好处在于:(1)内存池一次从系统申请释放一大块内存,避免频繁调用mallocfree,提高性能。(2)一次申请一块大内存,减少内存外碎片。(3)允许内存堆从缓冲池快速申请一页内存。(4)分层设计实现功能的拆分,便于管理与实现。

          

InnoDB内存管理层次图

下面说明InnoDB内存管理是如何工作的,使用内存块指明在内存堆中的由指针链接的一个个内存块,使用内存区指明内存池中由free_list列表管理的大小为2^i的多个连续内存区域。首先需要说明几个数据结构:

1TYPE类型双向链表

#define UT_LIST_NODE_T(TYPE)\

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元素管理。某个需要释放内存的函数调用顺序如下图所示。


释放内存的函数调用顺序图

转载于:https://my.oschina.net/u/347565/blog/669367

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值