内存堆mem.c/h
用户可以向系统申请分配任意大小内存块(有大小限制,最小不能小于MIN_SIZE)
下面是与内存堆管理相关数据结构,内存堆的管理类似于双向链表
名称 | 类型 | 所在文件 | 描述 |
---|---|---|---|
ram_heap[] | 全局型数组 | mem.c | 系统堆内存空间 |
ram | 全局型指针 | mem.c | 指向内存堆对齐后起始地址 |
mem | 结构体 | mem.c | 附加在每个内存块前面的结构体 |
ram_end | mem型指针 | mem.c | 指向系统最后一个内存块 |
lfree | mem型指针 | mem.c | 指向当前系统具有最低地址的空闲内存块 |
mem_mutex | 互斥量 | mem.c | 用于内存堆的互斥信号量 |
mem.h
是声明了一些函数、定义了一些宏定义,这些宏定义是做些字节对齐计算在内存池实现中也有使用到,如下
mem.c
主要定义了mem结构体,内存堆函数实现,内存堆数据定义
mem结构体(内存块管理结构体)
这里解释下
next
下一个内存块相对于内存堆首地址偏移量
prev
上一个内存块相对于内存堆首地址偏移量
used
1表示mem结构体管理的内存块已分配,0表示未被分配
内存堆定义
用户定义的可用内存堆大小是MEM_SIZE
,在lwipopt.h
和opt.h
中宏定义,但是实际定义的堆空间还要加上2个struct mem
结构体大小,上述空间均要字节对齐,最后定义出ram_heap
1. mem_init
初始化完成后如下图
2. mem_malloc
void * mem_malloc(mem_size_t size)
{
mem_size_t ptr, ptr2;
struct mem *mem, *mem2;
if (size == 0) {
return NULL;
}
//对要分配size做字节对齐
size = LWIP_MEM_ALIGN_SIZE(size);
//申请空间最少MIN_SIZE_ALIGNED
if(size < MIN_SIZE_ALIGNED) {
size = MIN_SIZE_ALIGNED;
}
if (size > MEM_SIZE_ALIGNED) {
return NULL;
}
/* protect the heap from concurrent access */
sys_mutex_lock(&mem_mutex);
//从lfree开始遍历,找到第一个长度大于size的空闲内存块
for( ptr = (mem_size_t)((u8_t *)lfree - ram);
ptr < MEM_SIZE_ALIGNED - size;
ptr = ((struct mem *)(void *)&ram[ptr])->next
)
{
mem = (struct mem *)(void *)&ram[ptr];//取得内存块管理结构体mem
if ( (!mem->used) &&
(mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size
)
{//未使用且空间大于等于用户要申请的size,否则for循环到下一个内存块管理结构体mem
if( mem->next - (ptr + SIZEOF_STRUCT_MEM) >=
(size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)
)//判断分配给用户size后剩下的内存是否还能组成一个最小的块,
{//能
ptr2 = ptr + SIZEOF_STRUCT_MEM + size;//分配后剩余空间偏移地址
//在ptr2处构造mem结构体管理/分配后剩余空间
mem2 = (struct mem *)(void *)&ram[ptr2];
mem2->used = 0;//未用
mem2->next = mem->next;//将新空闲快插入链表中
mem2->prev = ptr;
mem->next = ptr2;//更新前面的mem,mem的next指向mem2
mem->used = 1;//已用
if (mem2->next != MEM_SIZE_ALIGNED) //如果mem2的下一个不是最后一个
{ //mem2下面的管理结构体的prev要指向mem2即ptr2
((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
}//增加全局量
MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
}
else //不能组成最小的块
{ //则全部分配给用户,标记已用
mem->used = 1;
MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));//增加全局量
}
if (mem == lfree) //如果分配的是lfree指向的内存块则还要调整lfree
{
struct mem *cur = lfree;
while (cur->used && cur != ram_end)
{
cur = (struct mem *)(void *)&ram[cur->next];
}//遍历找到下一个位置最低的空闲块更新lfree
lfree = cur;
}
sys_mutex_unlock(&mem_mutex);//释放互斥量
return (u8_t *)mem + SIZEOF_STRUCT_MEM;//偏移掉管理结构体返回给用户
}
}
MEM_STATS_INC(err);//从for循环遍历也找不到长度大于size的空闲内存块,标记出错
sys_mutex_unlock(&mem_mutex);//释放互斥量
return NULL;
}
第一次mem_malloc
假设全部被malloc
3. mem_free
释放是根据传入的地址做SIZEOF_STRUCT_MEM
偏移找到mem结构体,由于malloc的时候已经建立了next和prev的值所以只用改写used参数为就表示释放,最后还要判断能否和mem结构体前后的mem结构体做合并,如果能合并则要再次改写next和prev值
void mem_free(void *rmem)
{
struct mem *mem;
//合法性判断
if (rmem == NULL) {
return;
}
if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
SYS_ARCH_DECL_PROTECT(lev);
/* protect mem stats from concurrent access */
SYS_ARCH_PROTECT(lev);
MEM_STATS_INC(illegal);
SYS_ARCH_UNPROTECT(lev);
return;
}
//做SIZEOF_STRUCT_MEM偏移找到mem结构体地址
mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);
mem->used = 0;
//若释放的地址比lfree低则要更新lfree
if (mem < lfree) {
/* the newly freed struct is now the lowest */
lfree = mem;
}
//合并检查
plug_holes(mem);
}
static void plug_holes(struct mem *mem)
{
struct mem *nmem;
struct mem *pmem;
//找到mem的下一个mem管理结构体地址,并判断是否空闲
nmem = (struct mem *)(void *)&ram[mem->next];
if (mem != nmem && nmem->used == 0 && (u8_t *)nmem != (u8_t *)ram_end)
{ //空闲则做合并
if (lfree == nmem)
{
lfree = mem;
}
mem->next = nmem->next;
((struct mem *)(void *)&ram[nmem->next])->prev = (mem_size_t)((u8_t *)mem - ram);
}
//找到mem的上一个mem管理结构体地址,并判断是否空闲
pmem = (struct mem *)(void *)&ram[mem->prev];
if (pmem != mem && pmem->used == 0)
{//空闲则做合并
if (lfree == mem)
{
lfree = pmem;
}
pmem->next = mem->next;
((struct mem *)(void *)&ram[mem->next])->prev = (mem_size_t)((u8_t *)pmem - ram);
}
}
假如在上图的基础上释放size2则只需把size2的used改成0,因为size2的上下都不能合并,如下图
假如在全部都被malloc的情况下先free size1 再free size3,最后free size2则需要先合并下面相邻的,再合并上面相邻的。并更新包在合并内存快外面的mem结构体里面的next prev