Lwip的动态内存管理机制有三种:
◆glibc的内存分配策略
◆内存堆(HEAP)分配策略
◆内存池(POLL)分配策略
Lwip的内存堆分配策略和glibc的内存分配策略只能从其中选择一种。
/** * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library * instead of the lwip internal allocator. Can save code size if you * already use it. */ #ifndef MEM_LIBC_MALLOC #defineMEM_LIBC_MALLOC 0 #endif |
由上述代码注释可知当MEM_LIBC_MALLOC为1时,使用的是c运行库中的内存分配释放函数malloc\free\realloc。当MEM_LIBC_MALLOC为0时使用lwip的内存堆分配策略。
内存堆分配策略
初始化
内存堆的初始化函数为mem_init,此函数主要功能是初始化堆内存的起始地址ram,剩余内存起始地址lfree,以及空闲内存链表。
/* 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[MEM_SIZE_ALIGNED]; 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; |
起始地址ram指向的是数组ram_heap的首地址,数组ram_heap的定义如下:
#ifndef LWIP_RAM_HEAP_POINTER /** the heap. we need one struct mem at the end and some room for alignment */ u8_tram_heap[MEM_SIZE_ALIGNED+(2U*SIZEOF_STRUCT_MEM)+MEM_ALIGNMENT]; #defineLWIP_RAM_HEAP_POINTER ram_heap #endif /* LWIP_RAM_HEAP_POINTER */ |
经过初始化后ram_heap的内存布局如下图所示:
ram_heap内存布局
堆内存分配
Lwip中堆内存分配函数为mem_malloc,此函数会从ram_heap数组中分配参数指定大小的字节数,如果分配成功,返回的是新分配内存指针。
下面分析mem_malloc函数看具体的实现:
/* Expand the size of the allocated memory region so that we can adjust for alignment. */ size =LWIP_MEM_ALIGN_SIZE(size); if (size <MIN_SIZE_ALIGNED) { /* every data block must be at least MIN_SIZE_ALIGNED long */ size =MIN_SIZE_ALIGNED; } |
对于参数size首先需要需要扩展成MEM_ALIGNMENT字节对齐。size值不能够小于MIN_SIZE个字节,代码中定义为12个字节,正好等于SIZEOF_STRUCT_MEM,用于存放struct mem结构体的内容。
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]; if ((!mem->used) && (mem->next -(ptr+SIZEOF_STRUCT_MEM)) >=size) { if (mem->next -(ptr+SIZEOF_STRUCT_MEM)>=(size +SIZEOF_STRUCT_MEM+MIN_SIZE_ALIGNED)) { ptr2 =ptr+SIZEOF_STRUCT_MEM+size; mem2 =(struct mem*)(void *)&ram[ptr2]; mem2->used = 0; mem2->next = mem->next; mem2->prev = ptr; mem->next = ptr2; mem->used = 1; if (mem2->next !=MEM_SIZE_ALIGNED) { ((struct mem*)(void *)&ram[mem2->next])->prev = ptr2; } } else { mem->used = 1; } |
最外层的for循环遍历从lfree开始的堆内存空间,直到找到一个比size个字节大的空闲内存空间。mem结构体的起始地址就是lfree开始的堆内存空间地址,如果mem->used为0,表示这段内存空间空闲。如果mem指向的下段内存的起始地址与mem起始地址之间大于size+SIZEOF_STRUCT_MEM,则表示找到一个满足条件的可用空闲内存空间。
在找到一个满足size字节大小的可用空闲内存空间后,还存在两种情况:
情况一:
这片空闲内存空间够大,满足如下条件
mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)
将这片内存空间分成两部分,用链表连接起来,并将前面mem->used设置为1。
情况二:
这片空闲内存空间内存在分配size+SIZEOF_STRUCT_MEM大小之后,剩余空间小于SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED,这样直接将mem->used设置为1即可。
从上面代码可知lwip的动态堆内存分配就是从ram_heap数组中找到一个比所申请size大的空间内存空间,就从中切割出合适的内存空间,并将剩余的的返回给堆内存,以备后面之用。
内存池分配策略
内存池有很多种,从memp_std.h文件就可看到RAW_PCB、UDP_PCB、TCP_PCB等等,每种类型的内存池单个大小是固定的。
const struct memp_desc* constmemp_pools[MEMP_MAX]={ #defineLWIP_MEMPOOL(name,num,size,desc)&memp_ ## name, #include"lwip/priv/memp_std.h" }; |
内存池由memp_pools全局数组进行管理,数组大小为MEMP_MAX,此值就是所有类型的内存池的个数,它是枚举memp_std.h文件中所有类型的内存池得到的。
typedef enum { #defineLWIP_MEMPOOL(name,num,size,desc) MEMP_##name, #include"lwip/priv/memp_std.h" MEMP_MAX }memp_t; |
memp_pools数组的成员值是取对应类型的内存池地址,结合memp_std.h就会看到:
memp_pools[0] = &memp_RAW_PCB
那memp_ ## name值是如何定义的呢?
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc)u8_tmemp_memory_ ## name ## _base\ [((num) *(MEMP_SIZE+MEMP_ALIGN_SIZE(size)))];\ \ staticstruct memp *memp_tab_ ##name;\ \ conststruct memp_descmemp_ ##name ={\ LWIP_MEM_ALIGN_SIZE(size),\ (num),\ DECLARE_LWIP_MEMPOOL_DESC(desc)\ memp_memory_ ## name ## _base,\ &memp_tab_ ##name \ }; |
这个宏看起来比较难懂,它首先定义了一个unsigned char型的数组memp_memory_ ## name ## _base,数组大小为此种类型内存池数num *(此种类型内存池固定大小size + memp管理区大小MEMP_SIZE)的乘积。
接着定义了一个类型为struct memp的static指针变量memp_tab_ ## name。
最后定义了类型为struct memp_desc的常量memp_ ## name,并对此常量进行了初始化。memp_TCP_PCB变量的各成员示例如下:
size成员大小为232,等于struct tcp_pcb结构体大小。
num成员为5,表示此内存池内存分为5个部分。
base成员为memp_TCP_PCB类型内存池首地址。
tab成员指针用来链接此内存池的5个部分。
这里memp_memory_TCP_PCB_base数组首地址为0x6813c0,分成5个部分,每个部分的首地址如下所示:
此种类型内存池结构图如下所示:
初始化
内存池的初始化函数为memp_init,它首先会初始化MEMP_MAX个不同类型内存池的统计信息,也就是初始化lwip_stats.memp数组。接下来就是调用memp_init_pool函数初始化MEMP_MAX个不同类型内存池的struct memp_desc结构体信息。