动态内存管理本质是对一大块内存(可以理解为数组)分配和释放的管理,lwip提供了2种动态内存管理策略内存池pool、内存堆heap
系统只能为用户分配几个固定大小的内存块,优点是比较快,不会产生内存碎片,缺点是产生内存浪费,适合对那些固定数据结构进行空间分配,比如TCP首部,IP首部等 下面是与内存池管理相关数据结构
名称类型所在文件描述memp_t枚举类型memp.h为每种pool定义编号indexmemp_tab[]全局型指针数组memp.c分别指向每类pool中的第一个poolmemp_sizes[]全局型数组memp.c每类pool中单个pool所占字节数memp_num[]全局型数组memp.c每类pool中pool的个数memp_desc[]全局型指针数组memp.c指向没pool的描述字符串memp_memory[]全局型数组memp.c所有pool所占内存总和memp.h 只定义了一个枚举类型,其他宏默认为0不会被编译
memp_t
typedef enum { #define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name, #include "lwip/memp_std.h" MEMP_MAX } memp_t;首先是个宏定义,##是连接符,表示吧MEMP_和宏定义中的name连接起来,然后include一个头文件,表示把这个头文件内容复制到下面,看下memp_std.h头文件
... #if LWIP_RAW LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB") #endif /* LWIP_RAW */ #if LWIP_UDP LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB") #endif /* LWIP_UDP */ ...这个头文件都是一些宏定义表示不同类型的pool,比如 LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb),"RAW_PCB") 这种pool类型是RAW_PCB,有MEMP_NUM_RAW_PCB个,每一个大小sizeof(struct raw_pcb),描述符是"RAW_PCB",注意这个头文件没有#ifndef、#define、#endif防止重复include的条件编译,所以它可以被多次include,而且最后#undef LWIP_MEMPOOL表示取消LWIP_MEMPOOL这个宏定义,以便后面重新宏定义。 结合memp_t的宏定义则memp_t最终会变成如下形式
typedef enum { MEMP_RAW_PCB, MEMP_UDP_PCB, MEMP_TCP_PCB, MEMP_TCP_PCB_LISTEN, MEMP_TCP_SEG, ... MEMP_MAX } memp_t;这句是为每种类型pool编个号index,其中MEMP_MAX,表示有多少种类型pool
memp_sizes[]
static 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_sizes[]最终形式如下
static const u16_t memp_sizes[MEMP_MAX] = { LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)), LWIP_MEM_ALIGN_SIZE(sizeof(struct udp_pcb)), LWIP_MEM_ALIGN_SIZE(sizeof(struct tcp_pcb)), LWIP_MEM_ALIGN_SIZE(sizeof(struct tcp_pcb_listen)), LWIP_MEM_ALIGN_SIZE(sizeof(struct tcp_seg)), ... };LWIP_MEM_ALIGN_SIZE是如下宏定义,表示MEM_ALIGNMENT字节对齐后向上取整
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT-1))memp_num[]同理
static const u16_t memp_num[MEMP_MAX] = { #define LWIP_MEMPOOL(name,num,size,desc) (num), #include "lwip/memp_std.h" };如下
static const u16_t memp_num[MEMP_MAX] = { MEMP_NUM_RAW_PCB, MEMP_NUM_UDP_PCB, MEMP_NUM_TCP_PCB, MEMP_NUM_TCP_PCB_LISTEN, MEMP_NUM_TCP_SEG, ... };memp_desc[]同理
static const char *memp_desc[MEMP_MAX] = { #define LWIP_MEMPOOL(name,num,size,desc) (desc), #include "lwip/memp_std.h" };如下
static const char *memp_desc[MEMP_MAX] = { ("RAW_PCB"), ("UDP_PCB"), ("TCP_PCB"), ("TCP_PCB_LISTEN"), ... };最后memp_memory[]是按全部内存池所占容量的总和开辟的一块连续内存(就是定义了一个数组)
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" ];最终如下
static u8_t memp_memory[MEM_ALIGNMENT - 1 + ( (MEMP_NUM_RAW_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb)) ) ) + ( (MEMP_NUM_UDP_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct udp_pcb)) ) ) + ... ];MEM_ALIGNMENT - 1字节对齐目的 MEMP_SIZE表示在每个pool头不预留的空间,根据宏定义有2种取值,下面会分别画出2种取值的图示,这里是为0
MEMP_ALIGN_SIZE宏将size字节对齐后向上取整
memp_tab[]是指针数组,里面每个元素指向各自类型pool的第一个空闲pool
struct memp { struct memp *next; }; static struct memp *memp_tab[MEMP_MAX];1. memp_init 初始化要实现的功能是按照不同的类型把每个类型下的pool依次用链表串起来,memp_tab用来存放头指向每种pool下的第一个空闲pool,每种类型最后一个空闲pool指向NULL,本质是构造MEMP_MAX条单向链表,只不过这些单向链表一开始地址是连续的
void memp_init(void) { struct memp *memp; u16_t i, j; //memp指向memp_memory对齐后地址 memp = (struct memp *)LWIP_MEM_ALIGN(memp_memory); /* for every pool: */ for (i = 0; i < MEMP_MAX; ++i) //循环对每种pool初始化 { memp_tab[i] = NULL;//开始没有pool,所以指向NULL /* create a linked list of memp elements */ for (j = 0; j < memp_num[i]; ++j) { memp->next = memp_tab[i];//新入pool的next指向的之前的第一个pool地址 memp_tab[i] = memp; //插入新pool到首部 memp = (struct memp *)(void *) //偏移到下一个新pool ((u8_t *)memp + MEMP_SIZE + memp_sizes[i]); } } }用图表示如下(下面每种类型pool只画了3个) 把每个pool详细内容表示出来,用另一种形式画出来如下,分别画出MEMP_SIZE为0和不为0情况,memp_tab指向链表中的MEMP_SIZE当然要么全为0要么全不为0,这里为方便个画一种。(下面每种类型pool只画了2个) 2. memp_malloc 是把memp_tab指向的pool分配给用户并指向下一个链表的pool,本质是取出链表出头节点
void * memp_malloc(memp_t type) //输入参数是要分配的pool类型index { struct memp *memp; SYS_ARCH_DECL_PROTECT(old_level);//声明一个临界区保护变量 SYS_ARCH_PROTECT(old_level);//进入临界区 memp = memp_tab[type];//根据类型index取出链表头 if (memp != NULL) {//不为空说明还此类型还有pool memp_tab[type] = memp->next;//链表头指向下一个pool MEMP_STATS_INC_USED(used, type);//增加内存池分配相关统计量 memp = (struct memp*)(void *)((u8_t*)memp + MEMP_SIZE);//偏移出预留空间作为返回给用户 } else { MEMP_STATS_INC(err, type);//增加内存池分配出错统计量 } SYS_ARCH_UNPROTECT(old_level);//退出临界区 return memp;//返回分配的地址给用户 }3. memp_free 释放本质是把用户交给你的地址插入链表头节点
void memp_free(memp_t type, void *mem)//要知道释放的类型和地址 { struct memp *memp; SYS_ARCH_DECL_PROTECT(old_level);//声明一个临界区保护变量 if (mem == NULL) {//null直接返回 return; } //得到pool的起始地址,并用memp指向pool的起始地址,再构造struct memp结构体 memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE); SYS_ARCH_PROTECT(old_level);//进入临界区 MEMP_STATS_DEC(used, type); //减少内存池分配相关统计量 //将pool插入memp_tab[type]头部 memp->next = memp_tab[type]; //memp->next指向原来的头部 memp_tab[type] = memp;//memp_tab指向新的头部 SYS_ARCH_UNPROTECT(old_level);//退出临界区 }