动态内存管理本质是对一大块内存(可以理解为数组)分配和释放的管理,lwip提供了2种动态内存管理策略内存池pool、内存堆heap
内存池memp.c/h
系统只能为用户分配几个固定大小的内存块,优点是比较快,不会产生内存碎片,缺点是产生内存浪费,适合对那些固定数据结构进行空间分配,比如TCP首部,IP首部等
下面是与内存池管理相关数据结构
名称 | 类型 | 所在文件 | 描述 |
---|---|---|---|
memp_t | 枚举类型 | memp.h | 为每种pool定义编号index |
memp_tab[] | 全局型指针数组 | memp.c | 分别指向每类pool中的第一个pool |
memp_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);//退出临界区
}