关于动态内存池分配
动态内存池分配策略实现起来非常简单。内存的分配、释放效率高,不像内存堆那样产生大量的内存碎片,有效防止碎片的产生,这种方式下,用户只能申请到大小固定的空间。在LWIP中,这种方式主要用于内核中固定数据结构的分配,例如UDP控制块、TCP控制块等,内核在初始化时已经为每个数据结构类型都初始化好了一定数量的POOL。源文件memp.h和memp.c包含了实现动态内存池分配策略的所有数据结构和函数。
——引自《嵌入式网络那些事:LwIP协议深度剖析与实战演练》(朱升林)
动态内存池管理相关数据结构
名称 | 类型 | 所在文件 | 描述 |
---|---|---|---|
memp_t | 枚举数据类型 | memp.h | 为每一个POOL定义一个名称/编号 |
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分配的内存空间 |
/* Create the list of all memory pools managed by memp. MEMP_MAX represents a NULL pool at the end */
typedef enum {
#define LWIP_MEMPOOL(name, num, size, desc) MEMP_##name,
#include "lwip/memp_std.h"
MEMP_MAX
} memp_t;
通过各种内存池的唯一的名称定义一个enum,并且在最后插入MEMP_MAX来得到内存池的总数。
这段代码中的中的宏定义,它告诉编译器,如果遇到LWIP_MEMPOOL(name, num, size, desc)这个宏的时候,就把它用“MEMP_##name“来代替,双井号”##“在C语言中被称为连接符,真真的我也是第一次遇到,以前看书的时候从没注意过,也没见过其他地方用了。我又回去翻看了下丹尼斯·里奇老爷爷写的《C程序设计语言》。
以下是《C程序设计语言》中的原文:
预处理运算符 ##为宏扩展提供了一种连接实际变元的手段。如果替换文本中的参数用##相连,那么参数就被实际变元替换, ##与前后的空白符被删除,并对替换后的结果重新扫描,例如,下面的宏paste用于连接两个变元:
’ #define paste(front, back) front ## back’
从而宏调用paste(name, 1)的结果是建立单词name1。
也就是如果以后编译过程中编译器遇到下面这样一句C语句,会将它替换为“MEMP_example1”
LWIP_MEMPOOL(example1, num1, size1, desc1)
接下来,#include “memp_std.h”,则编译器会马上找到该文件并编译它,编译完该头文件后,枚举类型memp_t就被定义出来了。这个头文件是对各种内存池的描述。
memp_std.h文件主要是由三种内存池组成:
+ Lwip_MEMPOOL 标准的内存池
+ LWIP_MALLOC_MEMPOOL 被mem.c中mem_malloc使用的内存池,需要在lwippool.h自定义不同大小的内存池
+ LWIP_PBUF_MEMPOOL PBUF的内存池
文件memp_std.h本质是由多个LWIP_MEMPOOOL宏组成,所以编译器依次对他们进行替换,这样当memp_std.h编译完之后,memp_t就建立起来,本质是:
typedef enum {
MEMP_RAW_PCB,
MEMP_UDP_PCB,
MEMP_TCP_PCB,
MEMP_TCP_PCB_LISTEN,
MEMP_TCP_SEG,
........
MEMP_MAX
} memp_t;
其中的MEMP_MAX并不代表任何类型的POOL,但是这里代表了该枚举类型中的元素的总个数。
其他
memp_size[MEMP_MAX]数组存放每个内存池的节点大小