本文转自公众号,欢迎关注
https://mp.weixin.qq.com/s/mBoGSf_u9edFF01U_OZT9g
一.前言
lwIP为基础结构提供了专用的内存池管理,比如netconn,protocol控制块,包缓存等。在memp.c下实现。
LWIP的内存池有两种方式实现,通过宏MEMP_MEM_MALLOC配置,默认opt.h中配置为0.
配置为1使用mem_malloc/mem_free mem.c
配置为0使用单独实现memp.c。
我们这里重点讲后者。
二. 相关源码
src/core/memp.c
src/include/lwip/memp.h
src/include/lwip/priv/memp_std.h
src/include/lwip/priv/memp_priv.h
三. 源码分析
3.1数据结构
内存池的关键数据结构是struct memp_desc对应内存池节点,一个类型的内存池是一个节点,
多个类型的内存池可以作为链表一起管理。
内存池最基本的数据结构是由宏LWIP_MEMPOOL_DECLARE定义的,
该宏在memp.h根据MEMP_MEM_MALLOC的配置实现为
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc)
和
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc)
前者是使用mem_malloc/mem_free实现内存池时使用,后者是单独实现时使用,我们重点关注后者。
其中LWIP_DECLARE_MEMORY_ALIGNED可以用户实现,如果用户未定义,arch.h默认是
#ifndef LWIP_DECLARE_MEMORY_ALIGNED
即定义了一个数组variable_name,其大小是LWIP_MEM_ALIGN_BUFFER(size)
LWIP_MEM_ALIGN_BUFFER可以用户实现,如果用户未定义,arch.h中默认为
#ifndef LWIP_MEM_ALIGN_BUFFER
即人为的放大了,保证对齐之后始终能保证有足够size的空间。
比如size是8,但是数组的基地址则可能是任意地址,比如是0x0001,要保证地址4字节对齐,
那么只能往后移动实际用的地址是0x0004,那么前面就浪费了3字节,此时8+(4-1)多分配3字节则浪费了这3字节也能保证剩余8字节可用8+(4-1)-3=8。
如果地址是0x0002则浪费2字节,可用8+(4-1)-2>8大于2字节,其他情况类似。
其中MEM_ALIGNMENT在opt中默认为1,用户在lwipopts.h中可以配置。
继续来看宏LWIP_MEMPOOL_DECLARE_STATS_INSTANCE和LWIP_MEMPOOL_DECLARE_STATS_REFERENCE,memp_priv.h中
如果定义了宏MEMP_STATS即使能统计信息,则LWIP_MEMPOOL_DECLARE_STATS_INSTANCE定义了结构体变量static struct stats_mem name;
LWIP_MEMPOOL_DECLARE_STATS_REFERENCE即&name,该变量地址
否则都是空
#if MEMP_STATS
其中stats.h中truct stats_mem如下
/** Memory stats */
继续看DECLARE_LWIP_MEMPOOL_DESC,memp_priv.h中
如果定义了宏LWIP_DEBUG,MEMP_OVERFLOW_CHECK,LWIP_STATS_DISPLAY三者之一,实际就是成员赋值为desc,否则为空。
#if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
继续看LWIP_MEM_ALIGN_SIZE,在arch.h中默认实现,即向上对齐,MEM_ALIGNMENT对齐必须是2的指数。
#ifndef LWIP_MEM_ALIGN_SIZE
最终LWIP_MEMPOOL_DECLARE(name,num,size,desc)展开后为
u8_t memp_memory_name_base[LWIP_MEM_ALIGN_BUFFER(((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size))))]
static struct stats_mem memp_stats_name;
static struct memp*memp_tab_name;
const struct memp_desc memp_name = {
desc,
&memp_stats_name,
LWIP_MEM_ALIGN_SIZE(size),
num,
memp_memory_ name_base,
&memp_tab_name
};
即定义一个数组作为存储,一个统计变量,一个static struct memp指针变量,一个结构变量static struct memp。
其中struct memp
#if !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK
其中struct memp_desc
/** Memory pool descriptor */
3.2内存池接口
LWIP_MEMPOOL_DECLARE(my_private_pool, 10, sizeof(foo), "Some description")
即上面数据结构说明的,使用该宏定义节点相关存储数组,变量等。
LWIP_MEMPOOL_INIT(my_private_pool) ->memp_init_pool
将节点的存储链表形式串起来。
*desc->tab指向最后一个item。
void* my_new_mem = LWIP_MEMPOOL_ALLOC(my_private_pool); ->memp_malloc_pool
从链表中取出一个item。
LWIP_MEMPOOL_FREE(my_private_pool, my_new_mem); ->memp_free_pool
Item插入到链表中
这里管理内存池实际可以用上述的链表形式,也可以用bitmap形式。
上述用链表形式每次malloc(出链表),free(入链表)都是堆链表尾,*desc->tab操作,执行时间固定。
而bitmap需要使用for循环去查询空闲bit执行时间不固定,当然也可以使用类似ucOS优先级调度的查表法来优化。
四.内部使用的内存池
数据结构中分析了LWIP_MEMPOOL_DECLARE宏,实际就是定义内存节点相关的变量(存储数组,统计变量,描述结构等)
用户可以直接使用内存池接口。
而lwip内部也使用了内存池进行管理,见memp.c/memp.h
4.1内部使用内存池
memp.h中
/* run once with empty definition to handle all custom includes in lwippools.h */
第一个#include "lwip/priv/memp_std.h"前因为LWIP_MEMPOOL为空,所以include memp_std.h进来所有的LWIP_MEMPOOL为空,此时相当于只是include进来memp_std.h中包含的头文件。
后一个#include "lwip/priv/memp_std.h"前LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
所以include memp_std.h后相当于,定义了MEMP_xxx的枚举
展开就是(当然要对应的宏使能才会有)
typedef enum {
RAW_PCB_RAW_PCB,
UDP_PCB_UDP_PCB,
......
MEMP_MAX
} memp_t;
注意每次memp_std.h中都undef了相关宏,所以不影响后续使用。
这里是一个小的编程技巧,一个头文件不同的宏配置下展开为不同的内容。
而memp.c中
#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
第一个包含memp_std.h 根据LWIP_MEMPOOL_DECLARE,定义了各个内存池节点
后一个memp_std.h则根据LWIP_MEMPOOL(name,num,size,desc) & name展开
即定义了一个结构体数组,数组的成员即上面定义的内存池节点。
const struct memp_desc *const memp_pools[MEMP_MAX] = {
&RAW_PCB,
......
};
所有使用到内建内存池总结如下
4.2内部使用内存池接口
以下都是调用内存池接口,不再赘述。
memp_free
memp_free_pool
memp_malloc
memp_malloc_pool
memp_init
五.堆使用内存池实现
前面我们看到定义宏MEMP_MEM_MALLOC,memp.c内存池可以使用mem.c堆实现.
反过来定义宏MEM_USE_POOLS,mem.c也可以用memp.c内存池实现.所以可以看出LWIP的堆管理实现方式比较灵活的,可以根据实际应用配置。
在opt.h中MEM_USE_POOLS是默认配置为0的,可以在lwipopts.h中修改配置。
#if !defined MEM_USE_POOLS || defined __DOXYGEN__
如果MEMP_USE_CUSTOM_POOLS配置为1,则MEMP_USE_CUSTOM_POOLS也要配置为1
此时
memp_std.h中
/*
可以看出,用户必须提供lwippools.h文件,申明对应的内存池节点。
即在原来memp_t和memp_pools的基础上后面继续添加节点。
内容如下
LWIP_MALLOC_MEMPOOL_START
此时mem_malloc则从
memp_pools中
MEMP_POOL_FIRST~MEMP_POOL_LAST处
搜搜,找到有节点有空闲空间,大小满足所需大小的即止。
即如下获取MEMP_POOL_HELPER_FIRST和MEMP_POOL_HELPER_LAST
#if MEM_USE_POOLS && MEMP_USE_CUSTOM_POOLS
六.总结
内存池适合分配大小固定的动态内存分配,比如用于协议包等的缓存等;
其算法简单,执行时间固定,比较可靠。但是其存储是单独分配的静态数组,相当于需要固定分配一部分空间,不管对应的程序运行还是不运行,比较浪费空间。
当然也可以定义宏MEMP_MEM_MALLOC使用mem_malloc堆的方式实现,而mem_malloc进一步可以配置使用LWIP的实现还是使用系统的malloc(配置宏MEM_LIBC_MALLOC)。
此时和系统堆共用,这样存储利用率更高。
LWIP内部实现了堆和内存池管理可以直接使用,也可以配置使用系统的堆管理,非常灵活,移植性也非常好。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !