LwIP有两种内存管理策略,它们分别是内存堆和内存池。
1、内存堆
1.1 相关特性
内存堆是什么:
内存堆就是动态分配内存,从低地址开始查找找到合适的内存后返回指针,并将多余的内存与临近的内存进行合并(first fit算法)。
内存堆的由来:
1、申请一个名为ram_heap的大数组,模拟C库的malloc和free的实现过程,对大数组进行操作。LwIP默认选择该方式。
2、动态申请内存池,以此为基础作为内存堆的空间。一般不使用,配置宏MEM_USE_POOLS、MEMP_USE_CUSTOM_POOLS可使能。
3、使用C库的malloc来实现。一般不使用该情况,配置宏MEM_LIBC_MALLOC可使能。
相关解释在mem.c中有如下解释:
内存堆的对齐:
LWIP_MEM_ALIGN_SIZE宏用于对齐,它的对齐字节数由宏MEM_ALIGNMENT所确定,在默认情况下MEM_ALIGNMENT = 4,因此是4字节对齐。
以下内存对齐描述都认为MEM_ALIGNMENT = 4,即:4字节对齐。
对于最小分配内存大小,它所占的空间由宏MIN_SIZE_ALIGNED表示,在默认情况下MIN_SIZE = 12,因此对齐后最小分配内存大小 = 12字节。
对于内存控制块,它所占的空间用宏SIZEOF_STRUCT_MEM表示,sizeof(struct mem)的范围是5~7,因此经过对齐后应为8字节。
对于内存堆的可用最大空间,它所占的空间由宏MEM_SIZE_ALIGNED表示,在默认情况下MEM_SIZE = 30*1024,因此经过对齐后内存堆的空间应为30K字节。
对于内存堆的数组名与空间大小,它由宏LWIP_DECLARE_MEMORY_ALIGNED来确定,最终的结果为注释部分
内存堆相关指针:
u8_t* ram:指向内存堆对齐后起始地址,即:ram_heap[0]的地址。
struct mem* ram_end:指向系统最后一个内存块,这代表内存堆的结尾,是不可用的空间。
struct mem* lfree:指向当前系统具有最低地址的空闲内存块,这是动态变化的值。
内存堆的结构:
内存堆由内存控制块和可用内存两部分构成,当申请内存堆后,返回的地址是可用内存的首地址。
具体框图如下:
1.2 相关函数
内存堆的管理函数存放在mem.c中,关键函数有init、malloc、free三个,分别对应初始化、申请、释放。
1.2.1 mem_init
控制块结构体:
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; // 描述内存块是否可用 0 :未使用;1 :已使用
#if MEM_OVERFLOW_CHECK
/** this keeps track of the user allocation size for guard checks */
mem_size_t user_size;
#endif
};
函数功能:
初始化LwIP的内存堆,由lwip_init调用。
函数结构:
1、初始化raw指针指向数组首地址
2、在数组首地址处生成一个控制块,并代表整个内存堆为空。
mem->next = MEM_SIZE_ALIGNED;
mem->used = 0;
3、在数组尾部生成一个控制块,并代表内存堆的结尾部分。
4、将lfree指向raw,因为在初始化时内存堆全为空。
函数实现:
/**
* Zero the heap and initialize start, end and lowest-free
*/
void
mem_init(void)
{
struct mem *mem;
LWIP_ASSERT("Sanity check alignment",
(SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT - 1)) == 0);
//1.初始化raw指向数组头
ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);//ram指向内存堆的首地址
//2.初始化头控制块,空间的开头
mem = (struct mem *)(void *)ram; //mem是控制块指针,指向第一个控制块
mem->next = MEM_SIZE_ALIGNED; //MEM_SIZE_ALIGNED为内存堆的最大空间,next指向它代表整个空间空闲
mem->prev = 0;
mem->used = 0; //used = 0代表本空间可用
//3.初始化尾控制块,空间的结尾
ram_end = ptr_to_mem(MEM_SIZE_ALIGNED); //ram_end是控制块指针,指向最末尾的位置,代表内存堆空间已用完
ram_end->used = 1; //ram_end之后虽然还有一些空间,但这些空间用作对齐,因此used = 1代表不可用
ram_end->next = MEM_SIZE_ALIGNED;
ram_end->prev = MEM_SIZE_ALIGNED;
MEM_SANITY();
//4.初始化lfree指向最低地址的空闲内存块
lfree = (struct mem *)(void *)ram; //初始化时全空闲,因此lfree指向数组头ram
MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);
if (sys_mutex_new(&mem_mutex) != ERR_OK) {
LWIP_ASSERT("failed to create mem_mutex", 0);
}
}
初始化后指针与空间分布如下:
1.2.2 mem_malloc
函数功能:
在内存堆中开辟指定大小的空间。
函数结构:
1、对申请大小进行相关处理,如:对齐、大小应至少为最小内存堆大小、大小应小于最大的大小
2、使用first fit算法查找空闲内存堆,这是一个循环遍历的查找
first fit算法:
1、整体框架是令ptr指向lfree,之后通过next遍历整个内存堆。代码如下:
for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size;
ptr = ptr_to_mem(ptr)->next) {
//相关处理代码
}
在遍历过程中,如果ptr<剩余空间,那么该循环则会退出。内存框图如下:
2、在循环中,首先要将ptr转为struct mem格式,从而对控制块的used位进行判断。如果该块未被使用并且内存足够的话,则进行相应操作。
for(){
mem = ptr_to_mem(ptr); //转为控制块进行操作
if ((!mem->used) &&
(mem-&g