前言 前一章我们讨论了内存池的分配和回收的一些内幕这一节我们将来讨论一下lwip的mem内存堆机制那有的人就很好奇既然有了内存池的管理机制了为什么还要多此一举搞个内存堆管理呢?二者有什么区别又或者各有什么优缺点呢? 这些疑惑将在这一节揭晓。
1、memp相关宏以及变量的解释 【1】宏定义解释
1、MEM_USE_POOLS //使用内存池分配内存堆 2、MEM_LIBC_MALLOC //使用标准c函数库分配 3、MIN_SIZE //最小内存池大小 4、LWIP_RAM_HEAP_POINTER //定义的内存池的头部 6、MEM_USE_POOLS_TRY_BIGGER_POOL //如果当前内存池枯竭将尝试其他大的内存池
【2】数据结构
struct memp_malloc_helper { memp_t poolnr; }; //当使用MEM_USE_POOLS时mem内存用于区分哪一类型内存池 struct mem { /** index (-> ram[next]) of the next struct */ mem_size_t next; /** index (-> ram[prev]) of the previous struct */ mem_size_t prev; /** 1: this area is used; 0: this area is unused */ u8_t used; }; //当不使用MEM_USE_POOLS是内存管理结构
以上就是内存堆管理最重要的两个基本结构单元
【3】变量
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; //始终指向内存堆中最低空闲内存块的地址
有人就要问了就上面几个变量吗?你可不要耍我啊对你没有看错在你眼中如此冗杂的内存管理仅仅使用上图这么几个简答的变量由此可以看出其编码的巧妙。这也告诉我们看起来冗杂的东西不要害怕maybe只是虚胖哈哈哈。。。
2、Mem的内存机制原理
这里为了让大家更加直观的认识我觉得从上而下开始讲解。其实MEM提供几种分配机制。
1、使用使用LWIP自己的内存分配机制
2、使用系统的库(malloc和free)来分配
而决定上面的分配方式是通过MEM_LIBC_MALLOC来实现的当为1时采用c中的malloc和free来实现的。这里我们着重分析LWIP自己的内存分配机制。好了现在LWIP是通过自己的内存分配机制来实现内存堆了那么就结束了吗??? 不不不怎么可能LWIP在此基础上又分了两种构造机制。
由此才将所谓的内存申请动作真正映射到物理内存故一般我将第二个宏的作用定义为决定内存的来源。
本着有简到难的观点出发我们首先来看一下USE POOLS。
上图为使用内存池来分配内存堆的图示由图我们可以看出内存申请后lwip在申请的内存前加了一个poolnr的结构这个正是我们前面提到的struct memp_malloc_helper 结构它专门用来管理使用内存池分配内存堆的。
有的人可能就要问了为什么要引入这样的结构呢?按照我们以往的经验来说不应该是分配好内存到释放的时候直接使用这个地址来释放就好了吗? 是的你说的没错但是你忽略了一点那就是不同的内存池的大小不一样也就是在释放的时候必须还要指定具体是哪一个尺寸的内存池给你分配的内存因此这个结构主要就是记录这个信息。
有的人又要问那我使用一个外部变量来管理不就可以了确实如此但是假象一下当你申请了100个内存块此时如果你自己来管理将是多磨的冗杂并且容易出错因此lwip为我们很好的解决了这个问题。
讲完了mem使用内存池的申请我们接下来看看内存堆自己对申请和释放是如何管理的。首先来看一下内存堆初始的模型
由图可以看出在初始化完成其实内存堆被分为了两个块一块是真正的内存卡及ram_heap和ram_end之间的内存另一个是ram_end之后的内存。至于为什么其后面存在多余的空闲的内存主要是考虑的内存对齐的开销因此增加了适量的内存。
上图所示为mem在初始化完成申请的第一个内存可以看出Ifree会随着分配的进行指向最低空闲内存块同是由struct mem结构来连接新形成的内存块使其形成一个链式的结构使用used域来标识该内存块是否使用。
上图表示申请了n次后的一个内存状况可以看出经过n次的内存申请和释放后内存趋于分散化此时将形成内存碎片而不是像memp一样0碎片化。而对于如何优化和处理碎片也是众多内存分配和释放算法的一个重要的区别。对于这个小的碎片我们需要在适时对其进行合并以达到一些大的内存申请需要。Lwip也提供了这样的机制我们将在源码分析中对其进行讲解。我们可以通过下图加以理解。
由于图片太大貌似截图都不太清晰文章后边将附上这些图的下载链接供读者自行下载。
3、Mem的源码分析 【1】内存池的初始化
这里需要注意一点对于内存池分配方式而言是不需要mem_init()的初始化的原因在于mem_init主要是为内存堆准备起始环境而如果使用内存池分配不需要该环境而是需要初始化memp_init()内存池环境。
void mem_init(void) { struct mem *mem; LWIP_ASSERT("Sanity check alignment", (SIZEOF_STRUCT_MEM 0); /* align the heap */ ram (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER); /* initialize the start of the heap */ mem (struct mem *)(void *)ram; mem->next MEM_SIZE_ALIGNED; mem->prev 0; mem->used 0; /* initialize the end of the heap */ ram_end (struct mem *)(void *) ram_end->used 1; ram_end->next MEM_SIZE_ALIGNED; ram_end->prev MEM_SIZE_ALIGNED; /* initialize the lowest-free pointer to the start of the heap */ lfree (struct mem *)(void *)ram; MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED); if(sys_mutex_new( ERR_OK) { LWIP_ASSERT("failed to create mem_mutex", 0); } } 由上面可以看出内存堆初始化主要做了这几件事。
1、初始化内存管理的基本结构即mem结构。
2、初始化尾部ram_end及Ifree。
【2】内存池的分配
1、使用内存池分配
void * mem_malloc(mem_size_t size) { void *ret; struct memp_malloc_helper *element; memp_t poolnr; mem_size_t required_size size LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper)); //计算实际需要的内存 for (poolnr MEMP_POOL_FIRST; poolnr < MEMP_POOL_LAST; poolnr (memp_t)(poolnr 1)) { #if MEM_USE_POOLS_TRY_BIGGER_POOL again: #endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */ /* is this pool big enough to hold an element of the required size plus a struct memp_malloc_helper that saves the pool this element came from? */ if (required_size < memp_sizes[poolnr]) { break; //适配到需要的大小相当的内存池 } } if (poolnr > MEMP_POOL_LAST) { LWIP_ASSERT("mem_malloc(): no pool is that big!", 0); return NULL; } element (struct memp_malloc_helper*)memp_malloc(poolnr); //从该内存池的链表上进行内存申请但是此时不一样链表上存在内存 if (element NULL) { /* No need to DEBUGF or ASSERT: This error is already taken care of in memp.c */ #if MEM_USE_POOLS_TRY_BIGGER_POOL //如果定义了 可以查找其他的内存池链 /** Try a bigger pool if this one is empty! */ if (poolnr
内存池的分配方式高效并且不会产生碎片这在memp内存池这一章已经讲过了mem的主要优点在于可以对不同内存大小的分配更加的灵活。以上的分配的策略主要是
当然如果 使能了MEM_USE_POOLS_TRY_BIGGER_POOL宏的话将再次搜索下一个大于需求内存的label直到找到或者全遍历。
2、使用内存堆分配
mem_malloc(mem_size_t size) { mem_size_t ptr, ptr2; struct mem *mem, *mem2; #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT u8_t local_mem_free_count 0; #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */ LWIP_MEM_ALLOC_DECL_PROTECT(); if (size 0) { return NULL; } /* Expand the size of the allocated memory region so that we can adjust for alignment. */ size LWIP_MEM_ALIGN_SIZE(size); if(size
【3】内存的释放
内存的申请存在两种方式那么必然对应着两种释放方式。
1、内存池分配方式的释放
void mem_free(void *rmem) { struct memp_malloc_helper *hmem; LWIP_ASSERT("rmem ! NULL", (rmem ! NULL)); LWIP_ASSERT("rmem MEM_ALIGN(rmem)", (rmem LWIP_MEM_ALIGN(rmem))); /* get the original struct memp_malloc_helper */ hmem (struct memp_malloc_helper*)(void*)((u8_t*)rmem - LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper))); //找到分配的真正首地址 LWIP_ASSERT("hmem ! NULL", (hmem ! NULL)); LWIP_ASSERT("hmem MEM_ALIGN(hmem)", (hmem LWIP_MEM_ALIGN(hmem))); LWIP_ASSERT("hmem->poolnr poolnr
2、内存堆分配方式的释放
void mem_free(void *rmem) { struct mem *mem; LWIP_MEM_FREE_DECL_PROTECT(); if (rmem NULL) { //合法性检查 LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("mem_free(p NULL) was called.\n")); return; } LWIP_ASSERT("mem_free: sanity check alignment", (((mem_ptr_t)rmem) 0); LWIP_ASSERT("mem_free: legal memory", (u8_t *)rmem > (u8_t *)ram //合法性检查 if ((u8_t *)rmem (u8_t *)ram_end) { SYS_ARCH_DECL_PROTECT(lev); LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory\n")); /* protect mem stats from concurrent access */ SYS_ARCH_PROTECT(lev); MEM_STATS_INC(illegal); SYS_ARCH_UNPROTECT(lev); return; } /* protect the heap from concurrent access */ LWIP_MEM_FREE_PROTECT(); /* Get the corresponding struct mem ... */ mem (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM); //获取真实的内存首地址 /* ... which has to be in a used state ... */ LWIP_ASSERT("mem_free: mem->used", mem->used); /* ... and is now unused. */ mem->used 0; //释放内存 if (mem
当然mem中还存在其他的一些功能函数如void *mem_trim(void *rmem, mem_size_t newsize)进行内存的压缩等等。读者可根据兴趣自行阅读和分析。
最后在指出一点就是当系统使用了默认的c分配函数如malloc和free是将存在如下的内存分配函数。
void *mem_calloc(mem_size_t count, mem_size_t size) { void *p; /* allocate count objects of size size */ p mem_malloc(count * size); if (p) { /* zero the memory */ memset(p, 0, count * size); } return p; } 其同样完成两个工作。
#ifndef mem_free #define mem_free free #endif #ifndef mem_malloc #define mem_malloc malloc #endif #ifndef mem_calloc #define mem_calloc calloc #endif 这是使用c系统函数分配的声明和定义这里我就不一一讲解了读者可以自行在mem.h函数中查找这也就是模块化编程的最大优点模块间相互区里耦合性较低。
关于mem内存堆的相关问题的分析就到这里