基于DWC_ether_qos的以太网驱动开发-LWIP的内存池介绍

描述

本文转自公众号,欢迎关注

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内部实现了堆和内存池管理可以直接使用,也可以配置使用系统的堆管理,非常灵活,移植性也非常好。

以太网

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分