内存管理:内存堆管理
内存堆管理用于管理一段连续的内存空间,如下图所示:
内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要使用这些内存块时,又可以释放回堆中供其他应用分配使用。通常,操作系统为了满足不同的需求,以RT-Thread为例,它提供了不同的内存管理算法,分别是之前所说的小内存管理算法、Slab内存管理算法和memheap内存管理算法。
小内存管理算法主要针对系统资源比较少,一般用于小于2MB内存空间的系统;而Slab内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。除上述之外,RT-Thread还有一种针对多内存堆的管理算法,即memheap管理算法。Memheap方法适用于系统存在多个内存堆的情况,它可以将多个内存“粘贴”在一起,形成一个大的内存堆,用户使用起来会非常方便。
目录
一、小内存管理算法
1)magic
2)used
1)Pool_ptr
二、Slab内存管理算法
1)内存分配
2)内存释放
三、Memheap内存管理算法
四、常见的动态内存错误
五、内存碎片是如何产生的?
注:因为内存堆管理器要满足多线程情况下的安全分配,会考虑多线程间互斥问题,所以要注意不要在中断服务例程中分配或释放动态内存块。因为它们可能会引起当前上下文被挂起等待。
一、小内存管理算法
小内存管理算法是一个简单的内存分配算法,初始时,它是一块大的内存。当需要分配内存块时,将从这个内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来。如下图所示:
每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括:
1)magic
变数(或称为幻数),它会被初始化成0x1ea0(即英文单词heap),用于标记这个内存块是一个内存管理用的内存数据块;变数不仅仅用于标记这个数据块是一个内存管理用的内存数据快,实质也是一个内存保护字:
如果这个区域被改写,那么也意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。
2)used
指示出当前内存块是否已被分配。内存管理的表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。如图所示:
堆开始:
堆起始地址存储内存使用信息,堆起始地址指针,堆结束地址指针,最小堆空闲地址指针和内存大小。
每个内存块(无论是已分配的内存块还是空闲的内存块)都包含一个数据头,包括:
1)Pool_ptr
小内存对象地址。如果内存块的最后一位为1,则标记为使用。可以通过该地址计算快速获得小内存算法结构体成员。
如下图所示的内存分配情况,空闲链表指针Ifree初始指向32字节的内存块。当用户线程要再分配一个64字节的内存块时,但此Ifree指针指向的内存块只有32字节,所以并不满足需求,所以内存管理器会继续寻找下一块内存块,当找到下一块内存块128字节时,它满足分配需求,因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52字节)继续留在Ifree链表中,如下图分配64字节后的链表结构所示:
另外,在每次分配内存块前,都会留出12字节数据头用于magic、used信息及链表节点使用。返回给应用的地址实际上是这块内存快12字节以后的地址,前面的12字节数据头是用户永不应该碰的部分(注:12字节数据头长度会与系统对齐差异而有所不同)。
释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲,则合并成一个大的空闲内存块。
二、Slab内存管理算法
Slab分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:
一个zone的大小在32K到128K字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中的zone最多包括72钟对象,一次最大能够分配16K的内存空间,如果超出了16K,那么直接从页分配器中分配。每个zone上分配的内存块大小是固定的,能够分配相同大小内存块的zone会链接再一个链表中,而72中对象的zone链表则放在一个(zone_array[])数组中统一管理。
下面是内存分配在主要的两种操作:
1)内存分配
假设分配一个32字节的内存,Slab内存分配器会先按照32字节的值,从zone_array链表表头数组中找到相应的zone链表。如果这个链表是空的,则向页分配器分配一个新的zone,然后从zone中返回第一个空闲内存块。如果链表非空,则这个zone链表中的第一个zone节点必然有空闲块存在(否则它就不应该再这个链表中),那么就取相应的空闲块。如果分配完成后,zone中所有空闲内存块都是用完毕,那么分配器需要再把这个zone节点从链表中删除。
2)内存释放
分配器需要找到内存块所在的zone节点,然后把内存块链接到zone的空闲内存块链表中。如果此时zone的空闲链表指示出zone的所有内存块都已经释放,即zone是完全空闲的,那么当zone链表中全空闲zone达到一定数目后,系统就会把这个全空闲的zone释放到页面分配器中。
三、Memheap内存管理算法
Memheap管理算法适用于系统含有多个地址可不连续的内存堆。使用memheap内存管理可以简化系统存在多个内存堆时的使用:
当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的memheap初始化,并开启memheap功能就可以很方便地把多个memheap(地址可不连续)粘合起来用于系统的heap分配。
Memheap工作机制如下图所示,首先将多块内存加入memheap_item链表进行粘合。当分配内存块时,会先默认内存堆去分配,当分配不到的时候会查找memheap_item链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆一般。
四、常见的动态内存错误
1)对NULL指针进行解引用
2)对分配的内存进行操作时越界
3)释放并非动态分配的内存
4)释放一块动态分配内存的一部分
5)动态内存被释放后继续使用
五、内存碎片是如何产生的?
频繁的调用内存分配和释放接口会导致内存碎片,一个避免内存碎片的策略是使用内存池+内存堆混用的方法。