这里写目录标题
内存管理
LWIP的本质就是对数据的处理。网络数据量非常多,LWIP对这些数据进行处理会消耗系统的资源,而好的内存管理就有必要了。
内存分配的实质就是,事先准备一大块内存堆(可以说是一个巨大的数组),然后把启始地址返回给申请者。这就需要内核必须采用自己独有的一套数据结构来描述、记录哪些内存空间已经分配,哪些内存空间是未使用的,根据使用的机制不同,延伸出多种类型的内存分配策略。
内存分配方式
常见的内存分配策略有两种,一种是分配固定大小的内存块poll。另外的是利用内存堆heap进行动态内存分布。
固定大小内存块
用户只能申请大小固定的内存块,在内存初始化的时候,系统会将所有可用的内存区域划分为N块固定大小的内存 ,然后将这些内存块通过单链表的方式连接起来,用户在申请内存块的时候就直接从链表的头部取出一个内存块进行分配,同理释放内存块的时候也是很简单,直接将内存块释放到链表的头部即可。
优点:内存分配的时间固定,效率高
缺点:只能申请固定内存块,如果内存大小不能够满足用户的需要,无法申请成功,内存太大的时候也会照成内存的浪费。
LWIP中有很多固定是的数据结构空间,如 TCP 首部、UDP 首部,IP 首部,以太网首部等都是固定的数据
结构,其大小就是一个固定的值。这种分配策略称为动态内存池分配策略。
可变长度分配
LWIP 采用的是(首次拟合)内存内存管理算法,申请的内存只会找一块比所请求的内存大的空闲块,从中割除合适的块,将剩余的返回到动态堆中。这种内存堆的分配方式,在申请和释放的时候都会消耗时间,可以看做是以时间换空间的策略。
优点:内存了浪费小,比较简单,适用于小内存管理
缺点:频繁的进行动态内存分配和释放,可能造成严重的内存碎片。
动态内存池(POLL)
- 申请的大小必须是指定的固定大小字节的值(4、8、12)
- 系统将可用的内存区,按照固定的大小字节进行划分,然后用单链表将所有的空闲内存链接起来。
- 链表中所有节点大小相同,分配和释放都简单。
LWIP中的memp.c 和memp.h就是动态内存分配策略
内存池的预处理
在内核初始的时候,根据宏定义配置以固定大小进行划分,然后用链表将所有的空闲块链接起来,这样就组成了一个个的内存池。
/* LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
* creates a pool name MEMP_pool_name. description is used in stats.c
*/
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
#endif /* LWIP_RAW */
在初始化的时候,RAM协议控制块需要的POOL 资源就会被初始化,其数量由MEMP_NUM_RAM_PCB 宏定义决定。在这里有一个很有意思的文件,那就是memp_std.h 文件,该文件位于include/lwip/priv 目录下,它里面全是宏定义。
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
“##”称为连接符(concatenator),用于将两个Token连接成一个Token(符号),这个Token可以是C语言的关键字,“int 、for、while”,也可以是用户自定义的变量,“a、num、name”等
#define LWIP_MEMPOOL(mjn,12,22,“sda”) MEMP_##name,
编译后的结果:
MEMP_mjn,
经过预处理后的代码
typedef enum
{
MEMP_RAW_PCB,
MEMP_UDP_PCB,
MEMP_TCP_PCB,
MEMP_TCP_PCB_LISTEN,
MEMP_TCP_SEG,
MEMP_ALTCP_PCB,
MEMP_REASSDATA,
MEMP_NETBUF,
MEMP_NETCONN,
MEMP_MAX
} memp_t;
在memp_std.h 文件的结尾,会撤销#undef LWIP_MEMPOOL ,因为这个文件被很多文件调用,且每次调用都会重新定义这个宏定义的功能。
在memp.h中,从新定义了这个宏定义
#define LWIP_MEMPOOL(name,num,size,desc)
#include "lwip/priv/memp_std.h"
在看lwip源码的时候,如果发现一个宏定义,但是找到不到这个定义的时候,但是编译没有问题,那这个宏定义可能就是通过“##”连接符产生的。
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
\
LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
\
static struct memp *memp_tab_ ## name; \
\
const struct memp_desc memp_ ## name = { \
DECLARE_LWIP_MEMPOOL_DESC(desc) \
LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
LWIP_MEM_ALIGN_SIZE(size), \
(num), \
memp_memory_ ## name ## _base, \
&memp_tab_ ## name \
};
LWIP_MEMPOOL(RAW_PCB,MEMP_NUM_RAW_PCB,sizeof(struct raw_pcb),"RAW_PCB")
/* 通过转换后得到的结果,例子是 RAW_PCB */
LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_RAW_PCB_base, ((MEMP_NUM_RAW_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb)))));
static struct memp *memp_tab_RAW_PCB;
/** Memory pool descriptor */ poll的描述
const struct memp_desc memp_RAW_PCB = {
DECLARE_LWIP_MEMPOOL_DESC("RAW_PCB")
LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_RAW_PCB)
LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb),
(MEMP_NUM_RAW_PCB),
memp_memory_RAW_PCB_base,
&memp_tab_RAW_PCB
};
//再次转换
u8_t memp_memory_RAW_PCB_base[(((((MEMP_NUM_RAW_PCB) *
(MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb)))))
+ MEM_ALIGNMENT - 1U))];
static struct memp *memp_tab_RAW_PCB;
const struct memp_desc memp_RAW_PCB ={
(((sizeof(struct raw_pcb)) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U)),
LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)),
(MEMP_NUM_RAW_PCB),
memp_memory_RAW_PCB_base,
&memp_tab_RAW_PCB
};
就使用上面的 RAW_PCB 的例子,每种 POOL 在经过编译器都会得到一个结构体,memp_desc memp_XXXX,XXXX 表示对应的 POOL 类型,如RAW_PCB 的结构体就是 memp_desc memp_RAW_PCB,这里面就记录了该内存块对其后的大
小LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb))。也就是说,在经过编译器的处理,该结构体就保存了每种POLL的内存对齐后的大小。
内存池初始化
在LWIP协议栈初始化的时候,memp_init(),会对内存池进行初始化,真正初始化内存的函数的memp_init_pool()。
就是根据每种 POOL 的memp_desc 描述进行初始化,在每种类型的POOL 中将空闲内存块连接成单链表,并且使用memset() 函数将其内容清零,这样子就初始化完成了。
内存申请
void *memp_malloc(memp_t type)
{
void *memp;
LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;
;(→֒
memp = do_memp_malloc_pool(memp_pools[type]);
return memp;
}
内存释放
void memp_free(memp_t type, void *mem)
{
LWIP_ERROR("memp_free: type < MEMP_MAX",
(type < MEMP_MAX), return;);
if (mem == NULL)
{
return;
}
do_memp_free_pool(memp_pools[type], mem);
}
动态内存堆 mem
LwIP 为了能够灵活的使用内存,为使用者提供两种简单却又高效的动态内存管理策略:动态内存堆管理heap、动态内存池管理poll,而内存池管理策略在前面的章节已经讲解,那么现在就来看看内存堆的管理。
管理策略分两种:
- C标准库自带的内存管理策略
- lwip自身实现的内存堆管理策略
通过MEM_LIBC_MALLOC来进行选择
内存堆初始化
在内核初始化的时候,会调用mem_init() 函数进行内存堆的初始化
内存堆的申请
mem_malloc() 函数是LwIP 中内存分配函数。
内存堆的释放
mem_free() 函数是LwIP 中内存分配函数。
使用C库的malloc和free来管理内存
LWIP的配置
- MEM_LIBC_MALLOC:定义是否使用C标准库自带的内存分配策略,默认是0
- 使用LWIP自身的动态内存管理分两种:1、是内存堆(heap,在mem.c中)管理实现大数组管理。2、通过内存池(poll,在memp.c中)管理,事先开辟的固定内存的池。
- MEMP_MEM_MALLOC 该宏定义表示是否使用 LwIP 内存堆分配策略实现内存池分配(即:要从内存池中获取内存时,实际是从内存堆中分配)。默认情况下为0,表示不从内存堆中分配,内存池为独立一块内存实现。与MEM_USE_POOLS 只能选择其一
- MEM_USE_POOLS:该宏定义表示是否使用LwIP 内存池分配策略实现内存堆的分配(即:要从内存堆中获取内存时,实际是从内存池中分配)。默认情况下为0,表示不使用从内存池中分配,内存堆为独立一块内存实现。MEMP_MEM_MALLOC 只能选择其一。