一,内存堆简介
lwip使用内存堆进行动态内存分配,弥补了内存池的缺点。
内存堆的灵活性表现在,每个内存块的大小是可以随意变化的,每次用户申请内存时,都会从内存堆中寻找第一个长度不小于所需大小的内存块,然后将该内存块裁剪合适后,将剩余的内存组成新的内存块返回内存堆中。
回收内存块时,若其前后的内存块也处于空闲,则可将多个内存块合并成一个大内存块。防止内存的碎片化。
一开始的内存堆就是一个大的内存块,经过不断的申请和释放后,可能呈现出如下的局面,所以在使用时要遵循“申请,释放,申请,释放”。
二,内存堆数据结构
内存块结构体就是一个双向的链表,这个链表在内存上也是连续的。其中next和prev表示的是相对首地址的偏移量。
struct mem {
mem_size_t next; //下一个内存块
mem_size_t prev; //上一个内存块
u8_t used; //是否被使用
};
此外还有几个全局变量:
ram_heap是内存堆的真实内存,MEM_SIZE_ALIGNED由用户定义内存堆总大小。SIZEOF_STRUCT_MEM指mem结构体的大小,MEM_ALIGNMENT是为了字节对齐。为什么SIZEOF_STRUCT_MEM要乘以2?因为在初始的内存堆中有两个内存块,分别在首尾,但尾部的内存块标志为used,这样做是方便了分配,回收代码的编写
u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];
static u8_t *ram;//指向内存堆对齐后的首地址
static struct mem *ram_end;//指向最后一个内存块
static struct mem *lfree;//指向最低地址的可用内存块
三,内存堆实现函数
1,内存堆初始化
void
mem_init(void)
{
struct mem *mem;
ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER); //内存堆对齐
//初始化内存堆的头部,初始内存堆只有一个空闲的内存块
mem = (struct mem *)(void *)ram;
mem->next = MEM_SIZE_ALIGNED; //MEM_SIZE_ALIGNED是内存堆对齐后的总大小,这里表示该内存的next是内存堆末尾
mem->prev = 0;//prev指向自己
mem->used = 0;//未使用
//初始化内存堆末尾
ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED]; //末尾地址
ram_end->used = 1; //末尾不可使用
ram_end->next = MEM_SIZE_ALIGNED;//next和prev指向自己
ram_end->prev = MEM_SIZE_ALIGNED;
lfree = (struct mem *)(void *)ram;//低地址可用内存块就是第一个内存
MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);//设置内存堆分配统计量
//创建信号量
if(sys_mutex_new(&mem_mutex) != ERR_OK) {
LWIP_ASSERT("failed to create mem_mutex", 0);
}
}
初始化的过程就是给内存堆创建了两个内存块,并初始化全局变量ram,ram_end,lfree,以及信号量(使用信号量是因为内存堆是全局数组)
2,内存申请
void *
mem_malloc(mem_size_t size)
{
mem_size_t ptr, ptr2;//相对ram的偏移量
struct mem *mem, *mem2;//新的内存块
LWIP_MEM_ALLOC_DECL_PROTECT();
if (size == 0) {
return NULL;
}
//申请的内存大小也要字节对齐
size = LWIP_MEM_ALIGN_SIZE(size);
//size必须满足最小条件
if(size < MIN_SIZE_ALIGNED) {
size = MIN_SIZE_ALIGNED;
}
//size不能太大
if (size > MEM_SIZE_ALIGNED) {
return NULL;
}
sys_mutex_lock(&mem_mutex); //请求信号量
LWIP_MEM_ALLOC_PROTECT();
//从lfree开始遍历men链表,直到找到第一个不小于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->next - (ptr + SIZEOF_STRUCT_MEM))表示内存块可用空间。若可用空间>=size,说明这个内存块适合;
if ((!mem->used) &&
(mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {
//MIN_SIZE_ALIGNED一个内存块的最小空间;
//若mem的可用空间减去size后,其空间还能作为一个内存块存在,则裁剪出新的内存块,并插入链表
if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) {
//计算新的内存块偏移
ptr2 = ptr + SIZEOF_STRUCT_MEM + size;
//初始化新内存块,插入mem后
mem2 = (struct mem *)(void *)&ram[ptr2];
mem2->used = 0;
mem2->next = mem->next;
mem2->prev = ptr;
//更新mem
mem->next = ptr2;
mem->used = 1;//标记已使用
//若mem2的next不是最后一个内存块
if (mem2->next != MEM_SIZE_ALIGNED) {
//则将mem2的next的prev指向mem2,因为最后一个内存块的prev必须指向自己
((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
}
MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
} else {
//直接把mem返回
mem->used = 1;
MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));
}
//若mem是lfree,则需要更新lfree
if (mem == lfree) {
//遍历mem之后的内存块更新lfree
while (lfree->used && lfree != ram_end) {
LWIP_MEM_ALLOC_UNPROTECT();
/* prevent high interrupt latency... */
LWIP_MEM_ALLOC_PROTECT();
lfree = (struct mem *)(void *)&ram[lfree->next];
}
LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
}
LWIP_MEM_ALLOC_UNPROTECT();
sys_mutex_unlock(&mem_mutex);//释放信号量
return (u8_t *)mem + SIZEOF_STRUCT_MEM; //返回内存块可用地址
}
}
MEM_STATS_INC(err); //分配内存失败
LWIP_MEM_ALLOC_UNPROTECT();
sys_mutex_unlock(&mem_mutex);
return NULL;
}
这个函数的主要逻辑就是从lfree开始寻找合适的内存块,若内存块足够大,则从内存块中分出一个新的mem2,并将该mem插入链表,最后返回所需要的内存地址。这样能提高内存利用率。
3,内存释放
void
mem_free(void *rmem)
{
struct mem *mem;
LWIP_MEM_FREE_DECL_PROTECT();
//检查指针
if (rmem == NULL) {
return;
}
//检查指针超出内存堆范围
if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
SYS_ARCH_DECL_PROTECT(lev);
SYS_ARCH_PROTECT(lev);
MEM_STATS_INC(illegal);
SYS_ARCH_UNPROTECT(lev);
return;
}
LWIP_MEM_FREE_PROTECT();
mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM); //获取内存块对应起始地址
mem->used = 0;//消除标记
//是否需要更新lfree
if (mem < lfree) {
/* the newly freed struct is now the lowest */
lfree = mem;
}
MEM_STATS_DEC_USED(used, mem->next - (mem_size_t)(((u8_t *)mem - ram)));
plug_holes(mem); //合并前后内存块
LWIP_MEM_FREE_UNPROTECT();
}
这个函数非常简单,主要是plug_holes(mem);实现了空闲内存的合并。
static void
plug_holes(struct mem *mem)
{
struct mem *nmem; //next内存块
struct mem *pmem; //prev内存块
nmem = (struct mem *)(void *)&ram[mem->next]; //获取下一个内存块
//若 mem和nmem都不是最后一个内存块且nmen未使用
if (mem != nmem && nmem->used == 0 && (u8_t *)nmem != (u8_t *)ram_end) {
if (lfree == nmem) {
lfree = mem;
}
//将mem和nmem合并
mem->next = nmem->next;
((struct mem *)(void *)&ram[nmem->next])->prev = (mem_size_t)((u8_t *)mem - ram);
}
pmem = (struct mem *)(void *)&ram[mem->prev];//获取上一个内存块
//pmem未使用,则合并mem和pmem
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);
}
}