这是个人编写的第三版内存管理器,主要用在单片机上。这一系列的内存管理器,由于本人对内存碎片的执念,于是前两版的使用非常反人类。举个例子:
第一版
void** malloc(uint32_t _size,uint8 _userid);
分配出来的是指向地址的地址,这对常规程序来说,用起来的感觉是相当酸爽。
第二版
void* malloc(uint32_t _size,uint8_t _userid,void **_address);
这就比较好看点了,但是还要给一个被分配的指针变量的地址。
(内存管理第二版)
上面两个版本,用的一种思路是——知道被分配的地址a的地址b,每当碎片整理时,直接将数据移动到上一块被使用的内存后面,更新地址b上的地址a,得到地址c。
碎片整理前
类型 |
内存块1 |
内存块2 |
内存块3 |
内存块4 |
内存块5 |
内存块6 |
数据
hello
world
!!!
内存池地址
0x40000000
0x40000010
0x40000020
0x40000030
0x40000040
0x40000050
被分配的地址
0x20001024
0x20003280
0x20007238
碎片整理后
类型 |
内存块1 |
内存块2 |
内存块3 |
内存块4 |
内存块5 |
内存块6 |
数据
hello
world
!!!
内存池地址
0x40000000
0x40000010
0x40000020
0x40000030
0x40000040
0x40000050
被分配的地址
0x20001024
0x20003280
0x20007238
将内存块 0x40000020 上的数据 “world” 复制到 内存块 0x40000010 上,并将新的地址更新到指针变量地址 0x20003280 上。
像这样做的优点是,当碎片整理完成后,不影响数据的使用。而缺点是,只能自己玩,并不适合一起用。
但是在某一天,翻资料,看到了linux的内存管理机制后,一下顿悟了,于是有了内存管理器的第三版。
原理
(n块内存组成一页,n页内存组成一组,n组内存)
1. 根据设定的内存大小(不超过物理内存)即内存池,分配 x 组(目前x最大值为1024),根据分配策略限定每组内存大小。
2. 按照
2i,i∈[0,x]" role="presentation" style="position: relative;">2i,i∈[0,x] 的规律,对
i,i∈[0,x]" role="presentation" style="position: relative;">i,i∈[0,x] 组进行分页。
3. 内存分配顺序,由小页到大页,游大端到大端。
4. 当要分配的
i,i∈[0,x)" role="presentation" style="position: relative;">i,i∈[0,x) 组的内存用完时,向
i+1,i∈[0,x)" role="presentation" style="position: relative;">i+1,i∈[0,x) 组内存借一页,使用递归模式,直至没有内存可以分配。
5. 内存释放后,若该内存为借用的,则尝试拼回。
6. 一块内存大小为
2i,i>0" role="presentation" style="position: relative;">2i,i>0 字节。
举个例子。
有定义一块内存池,大小为 327680 Bytes,定义一块内存块为 32 字节。将其分成10组,则每组大小为 32 KBytes。
组数 |
1组 |
2组 |
3组 |
4组 |
5组 |
6组 |
7组 |
8组 |
9组 |
10组 |
内存页大小
1
2
4
8
16
32
64
128
216
512
内存块数量
1024
1024
1024
1024
1024
1024
1024
1024
1024
1024
内存页数量
1024
512
216
128
64
32
16
8
4
2
- 当变量a需要分配的内存大小为 10 字节时,根据内存分配顺序,为该变量 a 分配页大小为 1 的内存,即变量 a 实际分配了 32 个字节。
- 当变量b需要分配的内存大小为 550 字节时,同理,为该变量分配页大小为页大小为 32 的内存,即变量b实际分配了 1024 个字节。
- 当变量c需要分配的内存大小为 100 字节,且大小为 4 的内存页分配完时,若第 4 组有剩余内存,将第 4 组的一块内存页分配给第 3 组,第三组内存页将其拆成 2 页,其中一页分配给变量 c,即变量 c 最终分配了 128 个字节。
方案
内存分配策略
假设内存大小为 m ,分组数量为n,其中最大分配组 11 组,即最大页为 1024,内存块大小为 32 字节 。
采用组大小均衡方法,使其符条件
(mn>2n∗32)∧(m(n+1)<2n+1∗32)" role="presentation" style="position: relative;">(mn>2n∗32)∧(m(n+1)<2n+1∗32) 。
模型
将一大块内存分为两部分,一部分为内存管理器,一部分为内存池。
内存管理器分两部分——总管理器和各分页管理器。
页管理器中,空闲内存页以单向链表的形式链接起来,每页内存并包含一字节内存页信息。
使用时,根据申请的内存页大小,由相应的页管理器从页链表中取出一块内存页进行分配;当该级内存没有可分配内存页,该级管理向更大的页管理器申请一块内存,添加到该级链表中,再对外分配内存;释放内存时,根据内存页信息直接插入到相应页管理器中。
优缺点
缺点:内存使用率不高;由于每页内存包含一字节内存页信息,实际每页内存少一个字节。
优点:无碎片内存;内存分配释放方式较前面两个版本更快更简单;可根据应用调整内分配策略。
Code
typedef struct
{
struct
{
uint8_t MemLeve : 4;
uint8_t MemFather : 4;
}MemoryState;
void *NextAddress;
}MemoryPageLevelOfNum_t;
typedef struct
{
memlevel_t LevelNum;
uint16_t InitFlag;
MemoryPageLevelOfNum_t *MemoryPageLevelOf[MEM_PAGE_LEVEL_MAX];
void *MemoryPageBaseAddressOf[MEM_PAGE_LEVEL_MAX];
uint32_t PageNumOfLevel[MEM_PAGE_LEVEL_MAX];
}MemoryDev_t;
这里包含了,内存管理器的主要定义。
uint8_t MemOneBase[MEM_MAX_SIZE];
其中,MEM_BLOCK_SIZE 定义了对齐大小,这里是32字节,也就是自定义的一块内存大小;MEM_MAX_SIZE 定义了需要的内存大小,即用来内存分配的大小。
/**
名 称 : mallocInit
功 能 : 内存管理器初始化
输 入 : _mgs - 分页策略
_coefficient - 分页系数
输 出 :
说 明 :
*/
void mallocInit(MemoryGroupingStrategy_t _mgs,uint8_t _coefficient)
{
uint32_t membufsize,memgroupsize, i_page,surplusblock;
memlevel_t memgrouplevel, i_mem;
MemoryPageLevelOfNum_t *mempagelevelbuf;
memgrouplevel = MEM_PAGE_LEVEL_MAX;
membufsize = MEM_MAX_SIZE - sizeof(MemoryDev_t);
membufsize /= MEM_BLOCK_SIZE;
_coefficient = _coefficient ? _coefficient : 1;
MemoryDev = (MemoryDev_t*)MemOneBase;
MemoryDev->MemoryPageLevelOf[0] = (MemoryPageLevelOfNum_t*)(MemOneBase + sizeof(MemoryDev_t));
switch (_mgs)
{
case Memory_Strategy_BlockBalanced: {
}break;
default: {
while (membufsize / memgrouplevel < ((1 << memgrouplevel-1)*_coefficient))
{
memgrouplevel--;
}
MemoryDev->LevelNum = memgrouplevel;
memgroupsize = membufsize / memgrouplevel;
for (i_mem = 0; i_mem < memgrouplevel; i_mem++)
{
MemoryDev->PageNumOfLevel[i_mem] = memgroupsize >> i_mem;
}
for (i_mem = memgrouplevel; i_mem < MEM_PAGE_LEVEL_MAX; i_mem++)
{
MemoryDev->PageNumOfLevel[i_mem] = 0;
}
surplusblock = membufsize - memgroupsize * memgrouplevel;
}
}
MemoryDev->PageNumOfLevel[0] += surplusblock;
for (i_mem = 1; i_mem < memgrouplevel; i_mem++)
{
MemoryDev->MemoryPageLevelOf[i_mem] = (MemoryPageLevelOfNum_t*)((uint32_t)MemoryDev->MemoryPageLevelOf[i_mem - 1] + MemoryDev->PageNumOfLevel[i_mem-1] * (1 << (i_mem-1)) * MEM_BLOCK_SIZE);
}
for (i_mem = memgrouplevel; i_mem < MEM_PAGE_LEVEL_MAX; i_mem++)
{
MemoryDev->MemoryPageLevelOf[i_mem] = 0;
}
for (i_mem = 0; i_mem < memgrouplevel; i_mem++)
{
mempagelevelbuf = MemoryDev->MemoryPageLevelOf[i_mem];
i_page = MemoryDev->PageNumOfLevel[i_mem];
while(i_page--)
{
mempagelevelbuf->MemoryState.MemLeve = i_mem+1;
mempagelevelbuf->MemoryState.MemFather = i_mem+1;
mempagelevelbuf->NextAddress = (MemoryPageLevelOfNum_t*)((uint32_t)mempagelevelbuf + (1 << i_mem)*MEM_BLOCK_SIZE);
mempagelevelbuf = mempagelevelbuf->NextAddress;
}
}
MemoryDev->InitFlag = MEM_INIT_SET;
}
初始化的过程,除了按照上面的规则外,还把内存页一页页划分出来,再通过链表的形式把每级的串起来。需要用的时候从链表上将内存页取下;释放的时候再将其放回到相应的链上。
uint8_t memoryCheckAndAdjust(const memlevel_t _level,uint8_t _flag)
{
uint8_t result = 0;
MemoryPageLevelOfNum_t *mpl_1,*mpl_2;
#if 0
if (0 == MemoryDev->PageNumOfLevel[_level])
{
if (0 != memoryCheckAndAdjust(_level + 1,1))
{
result = 1;
goto error;
}
}
if (0 != _flag)
{
mpl_1 = memoryPop(&MemoryDev->MemoryPageLevelOf[_level]);
MemoryDev->PageNumOfLevel[_level]--;
mpl_1->MemoryState.MemLeve = _level;
memoryPush(&MemoryDev->MemoryPageLevelOf[_level - 1], mpl_1);
mpl_2 = (MemoryPageLevelOfNum_t*)((uint32_t)mpl_1 + (1 << (_level - 1))*MEM_BLOCK_SIZE);
mpl_2->MemoryState.MemLeve = mpl_1->MemoryState.MemLeve;
mpl_2->MemoryState.MemFather = mpl_1->MemoryState.MemFather;
memoryPush(&MemoryDev->MemoryPageLevelOf[_level - 1], mpl_2);
MemoryDev->PageNumOfLevel[_level - 1] += 2;
}
#else
memlevel_t i_level;
for (i_level = _level; MemoryDev->LevelNum > i_level; i_level++)
{
if (0 != MemoryDev->PageNumOfLevel[i_level])
{
break;
}
}
if (_level != i_level)
{
if (MemoryDev->LevelNum > i_level)
{
mpl_1 = memoryPop(&MemoryDev->MemoryPageLevelOf[i_level]);
MemoryDev->PageNumOfLevel[i_level]--;
for (; _level < i_level; i_level--)
{
mpl_1->MemoryState.MemLeve = i_level;
memoryPush(&MemoryDev->MemoryPageLevelOf[i_level - 1], mpl_1);
MemoryDev->PageNumOfLevel[i_level - 1]++;
mpl_2 = (MemoryPageLevelOfNum_t*)((uint32_t)mpl_1 + (1 << (i_level - 1))*MEM_BLOCK_SIZE);
mpl_2->MemoryState.MemLeve = mpl_1->MemoryState.MemLeve;
mpl_2->MemoryState.MemFather = mpl_1->MemoryState.MemFather;
mpl_1 = mpl_2;
}
memoryPush(&MemoryDev->MemoryPageLevelOf[i_level], mpl_1);
MemoryDev->PageNumOfLevel[i_level]++;
}
else
{
result = 1;
}
}
#endif
error:
return result;
}
void* memoryGet(uint32_t _size)
{
uint8_t count, i;
memlevel_t memgrouplevel;
void *addr=0;
uint32_t block;
memgrouplevel = MEM_PAGE_LEVEL_MAX;
while (_size < ((1 << memgrouplevel)*MEM_BLOCK_SIZE - 1))
{
memgrouplevel--;
}
memgrouplevel += 1;
if (0 != memoryCheckAndAdjust(memgrouplevel,0))
{
goto error;
}
addr = (void*)((uint32_t)memoryPop(&MemoryDev->MemoryPageLevelOf[memgrouplevel])+1);
*(uint32_t*)addr = 0;
MemoryDev->PageNumOfLevel[memgrouplevel]--;
error:
return addr;
}
void* malloc(size_t _size)
{
if (MEM_INIT_SET != MemoryDev->InitFlag)
{
mallocInit(Memory_Strategy_CapacityBalanced,2);
}
return memoryGet(_size);
}
申请内存时,若当前内存页不够,则向上级申请,函数 memoryCheckAndAdjust(const memlevel_t _level,uint8_t _flag) 则是实现该功能的重要环节。
void memoryPut(void *_address)
{
MemoryPageLevelOfNum_t *mpl;
memlevel_t level;
mpl = (MemoryPageLevelOfNum_t*)((uint32_t)_address - 1);
level = mpl->MemoryState.MemLeve;
memoryPush(&MemoryDev->MemoryPageLevelOf[level -1], mpl);
MemoryDev->PageNumOfLevel[level - 1] ++;
error:
return;
}
void free(void *_address)
{
if (MEM_INIT_SET != MemoryDev->InitFlag)
{
mallocInit(Memory_Strategy_CapacityBalanced, 2);
}
else
{
memoryPut(_address);
}
}
完整程序
(本文仅供参考,转载请标明出处;若有大佬发现错误,请指出。)