MySQL系列:innodb引擎分析之内存管理
在innodb中实现了自己的内存池系统和内存堆分配系统,在innodb的内存管理系统中,大致分为三个部分:基础的内存块分配管理、内存伙伴分配器和内存堆分配器。innodb定义和实现内存池的主要目的是提供内存的使用率和效率,防止内存碎片和内存分配跟踪和调试。我们先来看看他们的关系和结构。
以下是它的关系结构图:
上图中的:
ut_mem_block块是基础内存管理
Buddy allocator是内存伙伴分配器
mem_heap是内存堆分配器
1.基础内存管理
typedef ut_mem_block_struct { ulint size; /*这个被分配block的内存大小*/ ulint magic_n; /*节点魔法字,用于校验所用*/ UT_LIST_NODE_T(ut_mem_block_t) mem_block_list; /*block list node,指定prev node和next node*/ };关于block的list定义是个全局的变量,UT_LIST_BASE_NODE_T(ut_mem_block_t) ut_mem_block_list;所有分配的block都会加入到这个list当中。在ut_malloc_low函数分配内存的时候会将分配的block加入到list当中,在ut_free的时候会所释放的内存所在的block从list当中删除。除了这两个函数以外,innodb还提供ut_free_all_mem函数来释放所有分配的block和统计分配内存的总数ut_total_allocated_memory功能。
ut_malloc_low 分配一个n长度的内存块,并将分配的块记录到ut_mem_block_list当中.
ut_malloc 与ut_malloc_low功能相同,但是会用0初始化所分配的内存。
ut_free 释放一个分配的内存块,并将其从ut_mem_block_list当中删除。
ut_free_all_mem 释放ut_mem_block_list所有的内存块并清空ut_mem_block_list
以上函数是支持多线程并发操作的,也就是说是线程安全的。
2.伙伴分配器
struct mem_pool_struct { byte* buf; /*整体内存的句柄*/ ulint size; /*整体内存大小*/ ulint reserved; /*当前分配出去的总内存大小*/ mutex mutex; /*多线程互斥量*/ UT_LIST_BASE_NODE_T(mem_area_t) free_list[64]; /*area_t链表数组,每个数组单元能管理2的i次方内存块列表,i是数组的下标*/ };
struct mem_area_struct { ulint size_and_free; /*area的内存大小(一定是2的次方),最后一个bit表示是否已经释放*/ UT_LIST_NODE_T(mem_area_t) free_list; /*area链表的上下area,因为buddy area是会分裂的,有可能多个*/ };mem_area_t是一个buddy的内存区域,也就是mem_area_struct。以下是一个32位机器管理1024字节内存块的buddy list分布:
2.1mem_area_t的分裂
2.2mem_area_t的合并
1.使用者归还伙伴分配的内存,首先会根据area_t的信息去找到自己的buddy,也就是第8层另外一个没有被分配的area.
2.找到buddy area后,判断buddy area是否是释放状态,如果是,触发合并,将自己和buddy area从第8层删除,合并成一个512大小的第9层area,
3.在重复1 ~ 2步,又会将自己和第九层另外一个buddy area合并成一个1024大小的第10层area.
2.3buddy allocator的接口函数:
mem_area_alloc 用buddy allocator分配一块内存
mem_area_free 将一块内存归还给buddy allocator
mem_pool_get_reserved 获得buddy allocator已经使用的内存大小
3内存分配堆(memory heap)
struct mem_block_info_struct { ulint magic_n; /*魔法字*/ char file_name[8]; /*分配内存的文件*/ ulint line; /*分配内存的文件所在行*/ ulint len; /*block的长度*/ ulint type; /*依赖的底层分配类型,有DYNAMIC、BUFFER、BTR_SEARCH三种类型*/ ibool init_block; /*是否是外部分配的内存块*/ ulint free; /*被占用的空间大小*/ ulint start; /*可分配内存的起始位置*/ byte* free_block; /*备用block,仅仅在BTR_SEARCH方式可用*/ UT_LIST_BASE_NODE_T(mem_block_t) base; UT_LIST_NODE_T(mem_block_t) list; };备注:mem_block_info_struct/mem_block_info_t/mem_block_t/mem_heap_t是等价
mem_heap_t的内存结构如下:
1.一个mem_block_t最小空间不小于64字节,标准的大小是8KB,在非MEM_HEAP_BUFFER模式下分配的空间不大于page size - 200(page size一般为16KB)
2.mem_heap_t有三种类型,分别是DYNAMIC、BUFFER、BTR_SEARCH,在DYNAMIC模式下都是基于buddy allocator进行mem_block_t分配的,在BTR_SEARCH模式下,使用free_block来作为内存分配,在BUFFER模式下比较复杂,如果分配的内存大小< page size的一半时,使用buddy alloc,否则使用buf_frame的内存分配方式(这个是属于buf0buf.XX里面的方式,还未开始分析)。
3.mem_heap_t在分配新的mem_block_t的时候一定是分配一个heap最后节点大小的两倍,如果分配的大小超过MEM_MAX_ALLOC_IN_BUF(相当于一个page size)的时候,heap 类型判断,在不是DYNAMIC模式下,最大就是一个MEM_MAX_ALLOC_IN_BUF大小。如果其他模式下就是设置成MEM_BLOCK_STANDARD_SIZE标准大小,在这些限制外,如果需要分配的内存大于这些限制,以分配内存大小为准进行mem_block_t分配。分配好的mem_block_t总是加入到heap base list的最后,也就是heap堆栈的顶端。
4.mem_heap_t在释放mem_block_t时候总是从顶端开始释放,直到不能释放为止(mem_block_t没有被使用者归还)。在mem_block_t释放的时候也是需要参考DYNAMIC、BUFFER、BTR_SEARCH类型进行相对于的归还规则(和2要点是相对应的)。
mem_heap_create 用DYNAMIC模式创建一个mem_heap_t
mem_heap_create_in_buffer 用BUFFER模式创建一个mem_heap_t
mem_heap_create_in_btr_search 用BTR_SEARCH模式创建一个mem_heap_t
mem_heap_free 释放mem_heap_t对象
mem_alloc 创建在MEM_HEAP_DYNAMIC模式下,并分配一块指定大小的内存(在这种方式下mem_heap_t只会有一个mem_block_t)
mem_free 归还mem_heap_t分配的内存,并释放mem_heap_t
mem_heap_alloc 在指定的mem_heap_t上分配一块内存
mem_heap_get_heap_top 获得heap顶端块可使用内存的地址
mem_heap_empty 清空指定的mem_heap_t
mem_heap_get_top 获得heap顶部的指定n大小的mem_block_t指针
mem_heap_free_top 释放heap顶部N大小的mem_block_t块
4总结
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。