一,lwip内存池简介
c语言的动态内存分配,会造成内存碎片,lwip使用内存池这样的结构来解决该问题。因为网络数据传输需要经常动态分配内存。
lwip的内存池按照用户给出的结构体,把一段连续的内存切分成一个个大小相同的内存区域(大小为结构体大小)。当使用动态内存分配时,会为用户分配一个内存块,释放时也会将整个内存块的回收。
这种动态内存分配方法的优点是速度快,缺点是pool中的内存可能没被利用,会造成一定的浪费。故适合用来为固定大小的数据结构分配内存,提供空间利用。
二,内存池的数据结构
lwip的内存池结构体非常简单,是个单向的链表,每种类型的pool维持一条单向链表。
struct memp {
struct memp *next;
};
lwip内存池所使用的内存其实就是一个全局数组,其定义如下:
//内存池的真正内存区域
static u8_t memp_memory[MEM_ALIGNMENT - 1
#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
#include "lwip/memp_std.h"
];
需要关注的是这个数组的大小是在编译的时候才会被确定。这个数组大小的计算公式如下
pool_a的大小 × pool_a的数量 + pool_b的大小 × pool_b的数量 + pool_b的大小 × pool_b的数量 +…+MEM_ALIGNMENT -1
在lwip/memp_std.h
中有各种类型的pool的定义如下:
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
#endif
当编译器执行时,这些宏被展开成类似如下:
+MEMP_NUM_RAW_PCB * MEMP_ALIGN_SIZE(sizeof(struct raw_pcb))
这就对应了“pool_a的大小 × pool_a的数量”,所有宏展开后就成为上面的公式,也就是所需的内存池的总大小。
另外还有一些变量以类似的形式被编译器编译:
memp_sizes[MEMP_MAX]数组存放了各种类型pool的大小。MEMP_MAX=pool种类。
const u16_t memp_sizes[MEMP_MAX] = {
#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEM_ALIGN_SIZE(size),
#include "lwip/memp_std.h"
};
memp_num[MEMP_MAX]数组存放了各种类型pool的数量。MEMP_MAX=pool种类。
static const u16_t memp_num[MEMP_MAX] = {
#define LWIP_MEMPOOL(name,num,size,desc) (num),
#include "lwip/memp_std.h"
};
static struct memp *memp_tab[MEMP_MAX]; //用数组保存了各种类型pool链的第一个空闲块
三,内存管理函数
1,初始化函数
void memp_init(void)
{
struct memp *memp;
u16_t i, j;
//将所有内存池初始化为0
for (i = 0; i < MEMP_MAX; ++i) {
MEMP_STATS_AVAIL(used, i, 0);
MEMP_STATS_AVAIL(max, i, 0);
MEMP_STATS_AVAIL(err, i, 0);
MEMP_STATS_AVAIL(avail, i, memp_num[i]);
}
memp = (struct memp *)LWIP_MEM_ALIGN(memp_memory); //获取内存池首地址
//将内存池分配成不同类型的pool
for (i = 0; i < MEMP_MAX; ++i) {
memp_tab[i] = NULL;
for (j = 0; j < memp_num[i]; ++j) {
memp->next = memp_tab[i];
memp_tab[i] = memp;
memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]);
}
}
}
初始完成后的内存池如图,不同类型的pool其实是在一片连续的内存memp_memory 上。
2,内存分配函数
void *memp_malloc(memp_t type)
{
struct memp *memp;
SYS_ARCH_DECL_PROTECT(old_level);//临界保护
SYS_ARCH_PROTECT(old_level);
memp = memp_tab[type]; //获取指定类型的pool链、
if (memp != NULL) {
memp_tab[type] = memp->next; //更新空闲内存块
MEMP_STATS_INC_USED(used, type); //pool使用记录
memp = (struct memp*)(void *)((u8_t*)memp + MEMP_SIZE); //调节memp地址,这里的MEMP_SIZE为0,所以不变
} else {
MEMP_STATS_INC(err, type);
}
SYS_ARCH_UNPROTECT(old_level);//退出保护
return memp;
}
使用这个函数申请pool时,从指定类型pool链中取出一个空闲的内存块,并返回地址。
3,释放内存
void memp_free(memp_t type, void *mem)
{
struct memp *memp;
SYS_ARCH_DECL_PROTECT(old_level);
//判断释放的内存是否空指针
if (mem == NULL) {
return;
}
memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE); //MEMP_SIZE为0
SYS_ARCH_PROTECT(old_level);//临界保护
MEMP_STATS_DEC(used, type); //used置位0
memp->next = memp_tab[type];//将内存块插入链表
memp_tab[type] = memp;
SYS_ARCH_UNPROTECT(old_level);//退出临界保护
}
释放内存时只需要将pool重归链表。
内存池的分配方式总体上就比较简单,在lwip中使用内存池的常常是固定大小的结构体,如udp控制块,tcp控制块等。