首页 > 一种内存池管理技术

一种内存池管理技术

本文介绍一种内存池管理技术。

在m公司工作了4年多,一直负责内存池模块问题的处理,比如内存越界,data abort 系统异常的处理,本文加以总结,以便后续参考。

读本文之前,先有个约定,本文中提到的pool指的就是内存池,buffer就是内存池中的一个存储单元,一个pool包含多个buffer。

1. 内存池整体规划

首先介绍下内存池的布局,pool共12个,pool[0]包含930buffer,每个buffer的大小是 32 bytes, 12个pool的详细信息见下图:

在这里插入图片描述

2. buffer 的布局

2.1 buffer示意图

以pool[0] 为例介绍一下buffer的布局,示意图(将来画一个漂亮的图替代之)如下:

在这里插入图片描述

在这里插入图片描述

上图左半部分: pool[0]包含930个buffer,每个buffer 的大小是32bytes.

上图右半部分:是buffer[3]的内存布局,可见除了用户申请的32个bytes之外,还有20个bytes的额外开销(管理成本),这20个bytes的额外开销包含5个4bytes: 4x4bytes 位于分配的内存之前(图中用1,2,3,4表示),1x4bytes位于分配的内存之后。

头部的4x4bytes作用解释
1(POOL_NEXT)Next available buffer address如果当前buffer的状态为free,指的是本pool中下一个可用buffer的地址.如果当前状态的状态是allocated, 为magic number:0x50555345
2(POOL_ID)指向当前buffer所属pool 的info当前pool中的所有buffer 中的这一地址存储一样的地址。(后面会详细介绍pool info)
3(HEAD)固定魔数: 0XF1F1F1F1Header
4(TASK)当前buffer所属的owner当前buffer是被那个task申请的
尾部的1x4bytes作用解释
1Buffer的footer后2个buffer固定0xF2F2,前2个buffer位当前buffer所在pool的index.上图为:0x03F2F2

要注意一点如果用户申请的内存小于32个bytes, 我们也会分配一个32个bytes的buffer给用户。 用户申请n个bytes, n<32, 系统会将[n+1,n+4]这4个bytes设置为0xF2F2F2F2, 我们把这个魔数叫做Footer. 上图中用户实际申请16个bytes,内存系统会将17直20这4个bytes设置为0xF2F2F2F2.

2.2 Trace32 恢复出的buffer布局

0x02F1F2F2: 是第0x02F1个buffer

接下来的52个bytes[0x649AA870, 0x649AA8A4]为第0x02F2个buffer的地址范围,可以对照上面的解释详细分析下这52个bytes:

0x50555345: 这个buffer的状态为allocated.

0x649A0EFC: 这个地址存储的是此buffer所属内存池的pool info.

0xF1F1F1F1: Header,

0x64B1B448: 这个buffer属于哪个task分配的。

[0x649A A880, 0x649A A89F]:这32个bytes 为实际分配的内存,其中有2个0xF2F2F2F2, 所以可以推测出用户实际申请的real size 为16 bytes 或者 24 bytes, 但是从目前的信息无法确实是16还是24. (参考第六节:内存系统的不足)

0x02F2F2F2:指示这是第0x02F2个buffer.

在这里插入图片描述

2.3 申请和释放buffer时的错误检查

通过以上分析,每个buffer有20 bytes的额外开销,这些开销对后续定位内存越界有很大的帮助。内存管理系统在用户申请/释放buffer时,会对buffer的布局进行检查。

Pool初始化完后内部buffer如下。在分配内存时会检查 PM_NEXT/POOL_ID/HEAD信息是否正确,如果存在问题,会报异常出来,我们称简称这种错误为HEAD被踩.

在这里插入图片描述

Buffer被分配出去(Allocated)后的布局如下,在释放buffer时除了检查上述的HEAD被踩的情况,还需要检查是否FOOTER被踩

在这里插入图片描述

被踩类型原因
HEAD 被踩前一个buffer发生越界,需要检查前一个buffer的内存布局是否正确。如果前一个buffer的HEAD也被踩,需要继续向前查前一个buffer.
FOOTER被踩(HEAD未被踩) 当前buffer越界,需要检查当前buffer所属task的上下文。

3. pool info介绍

第2节中有介绍每个buffer的头部的4*4bytes中,有4个bytes(POOL_ID)存储的是当前buffer所属pool 的信息,pool结构体细节如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4. buffer statistics

第3节中提到了在pool的信息中存储了buffer的统计信息,也就是buffer的history,存储了3个数组。这3个数组中也包含了当前buffer的使用信息,这3个数组可以理解为一个环形队列,由于buffer只有2中状态:ALLOCATED/FREE, 所以这3个数组中的buffer_status依次只有如下可能:ALLOCATED/FREE/ ALLOCATED 或者FREE/ALLOCATED/FREE, 根据owner_task或buffer_status就可以推导出那个node 属于当前buffer的信息。

下图为buffer[860]对应的buf_statistics中的3个历史节点,通过以上分析,可知node[2]为当前buffer 对应的节点。

在这里插入图片描述

如果这个队列中出现了2个ALLOCATED或者FREE依次出现,就表明程序存在bug。举例:History node[0].buffer_status等于ALLOCATED,History node[1].buffer_status也等于ALLOCATED, 则程序存在bug.

5. pool中可用的buffer链表

第3节中提到了在pool的信息中存储了可用buffer的链表available_list,另外在第2节中提到的buffer头中的POOL_NEXT,这2者之间存在关联,我们摘取Pool中的buffer初始化状态:

在这里插入图片描述

user_ptr是buffer中实际内存的地址(不包含buffer头部的16 bytes (0x10)的额外开销),PM_NEXT是buffer中开头地址(包含buffer头部的16bytes的额外开销)

buffer[n]中的PM_NEXT+0x10 指向buffer[n-1]中user_ptr

比如,buf[8]中的PM_NEXT(0x635E 3824 + 0x10)指向buf[7]中的user_ptr(0x635E 3834)

通过上图可以推测,以pool[0]为例(930个buffer), pool[0]初始化时,pool info中的available_list存储的是buffer[929]中的user_ptr, 当用户第1次申请buffer(小于等于32 bytes)时, 会将buffer[929]中PM_NEXT+0x10赋值给available_list,依次类推….,所以pool中的buffer是从后往前依次使用的。

扩展:释放内存时,内存系统怎么处理呢?

一种可能是:假设buffer[x]要释放,当前available_list.head 指向的是buffer[800], 伪代码如下:

buffer[x].PM_NEXT = buffer[800].user_ptr – 0x10;

avaialble_list.head = buffer[x].user_ptr;

这种做法的特点是:被释放的内存会很快被再次分配出去

还有一种可能是:假设buffer[x]要释放, available_list.tail 指向的是buffer[0], 伪代码如下:

buffer[0].PM_NEXT = buffer[x].user_ptr – 0x10;

buffer[x].PM_NEXT = NULL;

这种做法的特点是:被释放的内存会最后被分配出去,符合FIFO. 还记得pool_info.pm_fifo_suspend吗?推测和内存释放的策略有关系。

6. 内存系统中的不足和可改善之处

 第5章提到的available_list,其实可以不用链表,如果为了节省空间的话,只需要存储一个地址即可:指向下一个可用的buffer的user_ptr. 但是这样做的缺点时,如果buffer中的PM_NEXT由于内存越界被踩的话,可用buffer链表可能会断掉。

 此内存系统在free buffer时,没有clean buffer. 从已经分配buffer中存在多个0xF2F2F2F2就证明了这一点。 难道是为了提高程序的运行效率?

尽可能少的报异常出来

如果只是踩了buffer自己的Footer(0xF2F2F2F2),是不是可以考虑不报异常出来,尽可能让程序运行下去呢?提高用户体验。

 第1节提到的每个pool中的buffer个数是固定的,所以每一代产品都需要评估这个数量是否够用。如果buffer被申请完了,再申请的话,就会报错(pool_info.task_waiting推测与此有关,如果申请不到,task是否需要等待,此内存池系统无内存回收机制)。另外,pool_info.buf_statistic.time_stamp存储了buffer申请的时间,是不是可以考虑,如果某个buffer长久不释放的话,需要让buffer申请者检查下程序是否存在bug, 从而控制buffer的无限制扩大。

7. 扩展

use_after_free检查

buffer头中的PM_NEXT,如果不是0x50555345,则表示buffer为free状态,如果这时要访问此buffer,则发生use_after_free.

可以考虑在debug版本下打开此功能。release版本下打开会影响性能。

更多相关:

  • 实际上MySQL内存的组成和Oracle类似,也可以分为SGA(系统全局区)和PGA(程序缓存区)。 mysql>show variables like "%buffer%"; 一、SGA 1.innodb_buffer_bool 用来缓存Innodb表的数据、索引、插入缓冲、数据字典等信息。 2.innodb_log_buffer...

  • 简介   Buffer缓冲区,首先要弄明白的是,缓冲区是怎样一个概念。它其实是缓存的一种,我们常说的缓存,包括保存在硬盘上的浏览器缓存,保存在内存中的缓存(比如Redis、memcached)。Buffer是把数据保存在内存中,它本质上用来保存数据的数据结构是数组,例如ByteBuffer是byte数组,IntBuffer是int数组...

  • 更多内容,欢迎关注微信公众号:全菜工程师小辉~前言在笔者上一篇博客,详解了NIO,并总结NIO相比BIO的效率要高的三个原因,彻底搞懂NIO效率高的原理。这篇博客将针对第三个原因,进行更详细的讲解。首先澄清,零拷贝与内存直接映射并不是Java中独有的概念,并且这两个技术并不是等价的。零拷贝零拷贝是指避免在用户态(User-space)...

  • 一、预备知识—程序的内存分配  一个由c/C++编译的程序占用的内存分为以下几个部分  1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器...

  • 我的爱机是一台ThinkPad T420,原装三星DDR 1333 4G内存一根,还剩一根内存位置,最近趁京东6.18促销,准备增加一根物理内存。为了确保兼容性,觉得仍然选购DDR 1333 4G内存,于是购买了金士顿这款,比如DDR3 1600的还贵。 这个安装过程完全参照该内存的网页提示进行 这里简单记录一下,以备...

  • 陪伴我多年的老本ThinkPad T420渐渐垂垂老矣, 我想更新一下可以更新的部分, 比如将2.5寸HDD更换为SSD, 将单条4G内存再增加一根, 凡此种种想法, 可能最后归结为如何获取该笔记本的硬件配置信息, 在windows下面使用鲁大师之类的检测软件, 也许很好搞定,但是在Ubuntu 14.04平台上如果办到呢? 很简单...

  • 一.内存错误出现的场景 这几天在重构ATS插件代码的过程中遇到了烦人的内存泄露问题, 周五周六连续两天通过走查代码的方法,未能看出明显的导致内存错误的代码, 同时也觉得C和C++混合编程得到一个动态库, 在一个.cpp主文件中,即用new又用malloc来动态分配内存, 可能会导致内存错误.后来网上调研和查资料发现, new和mal...

  • 下面是一个网上转载的实现思路,经过验证,发现是可行的,就记录下来。 思路 python多线程中要响应Ctrl+C的信号以杀死整个进程,需要: 1.把所有子线程设为Daemon; 2.使用isAlive()函数判断所有子线程是否完成,而不是在主线程中用join()函数等待完成; 3.写一个响应Ctrl+C信号的函数,修改全局变...