企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
在内核初始化时,会事先在内存中初始化相应的内存池,内核会将所有可用的区域根据宏定义的配置以固定的大小为单位进行划分,然后用一个简单的链表将所有空闲块连接起来,这样子就组成一个个的内存池。由于链表中所有节点的大小相同,所以分配时不需要查找,直接取出第一个节点中的空间分配给用户即可。 注意了,内核在初始化内存池的时候,是根据用户配置的宏定义进行初始化的,比如,用户定义了LWIP\_UDP这个宏定义,在编译的时候,编译器就会将与UDP协议控制块相关的数据构编译编译进去,这样子就将LWIP\_MEMPOOL(UDP\_PCB, MEMP\_NUM\_UDP\_PCB, sizeof(struct udp\_pcb),"UDP\_PCB")包含进去,在初始化的时候,UDP协议控制块需要的POOL资源就会被初始化,其数量由MEMP\_NUM\_UDP\_PCB宏定义决定,注意了,不同协议的POOL内存块的大小是不一样的,这由协议的性质决定,如UDP协议控制块的内存块大小是sizeof(struct udp\_pcb),而TCP协议控制块的POOL大小则为sizeof(struct tcp\_pcb)。通过这种方式,就可以将一个个用户配置的宏定义功能需要的POOL包含进去,就使得编程变得更加简便。 在这里有一个很有意思的文件,那就是memp\_std.h文件,该文件位于include/lwip/priv目录下,它里面全是宏定义,LwIP为什么要这样子写呢,其实很简单,当然是为了方便,在不同的地方调用#include "lwip/priv/memp\_std.h"就能产生不同的效果。 该文件中的宏值定义全部依赖于宏LWIP\_MEMPOOL(name,num,size,desc),这样,只要外部提供的该宏值不同,则包含该文件的源文件在编译器的预处理后,就会产生不一样的结果。这样,就可以通过在不同的地方多次包含该文件,前面必定提供宏值MEMPOOL以产生不同结果。可能有些人看得一脸懵逼,其实我一开始也是这样子,不得不说LwIP源码的作者还是很厉害的。 简单来说,就是在外边提供LWIP\_MEMPOOL宏定义,然后在包含memp\_std.h文件,编译器就会帮我们处理,我们先来看看memp\_std.h文件到底有什么内容,具体见代码清单 5‑1,再举个简单的例子说明一下,具体见代码清单 5‑2。 ``` 1 #if LWIP_RAW 2 LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, 3 sizeof(struct raw_pcb), "RAW_PCB") 4 #endif /* LWIP_RAW */ 5 6 #if LWIP_UDP 7 LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, 8 sizeof(struct udp_pcb), "UDP_PCB") 9 #endif /* LWIP_UDP */ 10 11 #if LWIP_TCP 12 LWIP_MEMPOOL(TCP_PCB, MEMP_NUM_TCP_PCB, 13 sizeof(struct tcp_pcb), "TCP_PCB") 14 15 LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN, 16 sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN") 17 18 LWIP_MEMPOOL(TCP_SEG, MEMP_NUM_TCP_SEG, 19 sizeof(struct tcp_seg), "TCP_SEG") 20 #endif /* LWIP_TCP */ 21 22 #if LWIP_ALTCP && LWIP_TCP 23 LWIP_MEMPOOL(ALTCP_PCB, MEMP_NUM_ALTCP_PCB, 24 sizeof(struct altcp_pcb), "ALTCP_PCB") 25 #endif /* LWIP_ALTCP && LWIP_TCP */ 26 27 #if LWIP_IPV4 && IP_REASSEMBLY 28 LWIP_MEMPOOL(REASSDATA, MEMP_NUM_REASSDATA, 29 sizeof(struct ip_reassdata), "REASSDATA") 30 #endif /* LWIP_IPV4 && IP_REASSEMBLY */ 31 32 #if LWIP_NETCONN || LWIP_SOCKET 33 LWIP_MEMPOOL(NETBUF, MEMP_NUM_NETBUF, 34 sizeof(struct netbuf), "NETBUF") 35 36 LWIP_MEMPOOL(NETCONN, MEMP_NUM_NETCONN, 37 sizeof(struct netconn), "NETCONN") 38 #endif /* LWIP_NETCONN || LWIP_SOCKET */ 39 #undef LWIP_MEMPOOL ``` memp_std.h使用方式的例子 ``` 1 typedef enum 2 { 3 #define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name, 4 #include "lwip/priv/memp_std.h" 5 MEMP_MAX 6 } memp_t; ``` 可能很多人一看到代码清单 5‑2的例子,就懵逼了,这写的是什么鬼东西,完全不知道LwIP作者想要干什么,但是当你读懂这段代码的时候,你就不得不佩服LwIP作者的水平了,那是真的厉害。 先说说“#define LWIP\_MEMPOOL(name,num,size,desc) MEMP\_##name,”这个宏定义,此处先补充一下C语言的连接符“##”相关的知识,##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。在编译器编译的时候,它会扫描源码,将代码分解为一个个的Token,Token可以是C语言的关键字,如int、for、while等,也可以是用户自定义的变量,如,a、num、name等,当我们经过“#define LWIP\_MEMPOOL(name,num,size,desc) MEMP\_##name,”这个宏定义后,在编译过程中遇到了“LWIP\_MEMPOOL(EmbedFire,num,size,desc)”这句代码,编译器就会将它替换为“MEMP\_EmbedFire,”注意,这里有一个英文的逗号“,”,因为现在是定义枚举类型的数据,那么经过编译器处理的代码清单 5‑2代码后,这个枚举变量就会变成以下的内容(假设所有的宏定义都是使能状态),具体见代码清单 5‑3。 ``` 1 typedef enum 2 { 3 MEMP_RAW_PCB, 4 MEMP_UDP_PCB, 5 MEMP_TCP_PCB, 6 MEMP_TCP_PCB_LISTEN, 7 MEMP_TCP_SEG, 8 MEMP_ALTCP_PCB, 9 MEMP_REASSDATA, 10 MEMP_NETBUF, 11 MEMP_NETCONN, 12 MEMP_MAX 13 } memp_t; ``` memp\_t类型在整个内存池的管理中是最重要的存在,通过内存池申请函数申请内存的时候,唯一的参数就是memp\_t类型的,它将告诉分配的函数在哪种类型的POOL中去分配对应的内存块,这样子就直接管理了系统中所有类型的POOL。 这个枚举变量的MEMP\_MAX不代表任何类型的POOL,它只是记录这系统中所有的POOL的数量,比如例子中的MEMP\_RAW\_PCB 的值为0,而MEMP\_MAX的值为9,就表示当前系统中有9种POOL。 理解了这样子的编程,是不是大开眼界了?不过还有一点需要注意的是,在memp\_std.h文件的最后需要对LWIP\_MEMPOOL宏定义进行撤销,因为该文件很会被多个地方调用,在每个调用的地方会重新定义这个宏定义的功能,所以在文件的末尾添加这句#undef LWIP\_MEMPOOL代码是非常有必要的。 在这里还要给大家提个醒,如果以后看LwIP源码的时候,发现某个找不到定义,但是编译是没有问题的,那么很可能就是通过“##”连接符产生的宏定义了,例如图 5‑2出现的情况,MEMP\_RAW\_PCB定义就在memp\_t类型中,是通过“##”连接符产生的。 ![](https://box.kancloud.cn/eeb5f3a3c05707aaf283925eac8955ab_581x61.png) 图 5‑2未找到MEMP\_RAW\_PCB的定义 按照这种包含头文件的原理,只需要定义LWIP\_MEMPOOL宏的作用,就能产生很大与内存池相关的操作,如在memp.c文件的开头就定义了如下代码: ``` 1 #define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc) 2 #include "lwip/priv/memp_std.h" ``` 经过包含memp\_std.h文件后,再经过编译器的处理,就能得到下面的结果,具体见代码清单 5‑5加粗部分。其实这些编译器处理的代码我们并不需要怎么理会,简单了解一下即可. ``` 1 #define LWIP_MEMPOOL(name,num,size,desc) \ 2 LWIP_MEMPOOL_DECLARE(name,num,size,desc) 3 4 #define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) \ 5 u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)] 6 7 LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1U)) 8 9 #define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \ 10 LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, \ 11 ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \ 12 \ 13 LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \ 14 \ 15 static struct memp *memp_tab_ ## name; \ 16 \ 17 const struct memp_desc memp_ ## name = { \ 18 DECLARE_LWIP_MEMPOOL_DESC(desc) \ 19 LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \ 20 LWIP_MEM_ALIGN_SIZE(size), \ 21 (num), \ 22 memp_memory_ ## name ## _base, \ 23 &memp_tab_ ## name \ 24 }; 25 26 /* 编译时候的宏定义 */ 27 LWIP_MEMPOOL(RAW_PCB,MEMP_NUM_RAW_PCB,sizeof(struct raw_pcb),"RAW_PCB") 28 29 /* 通过转换后得到的结果,例子是 RAW_PCB */ 30 LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_RAW_PCB_base, 31 ((MEMP_NUM_RAW_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb))))); 32 33 LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_RAW_PCB) 34 35 static struct memp *memp_tab_RAW_PCB; 36 37 const struct memp_desc memp_RAW_PCB = 38 { 39 DECLARE_LWIP_MEMPOOL_DESC("RAW_PCB") 40 LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_RAW_PCB) 41 LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)), 42 (MEMP_NUM_RAW_PCB), 43 memp_memory_RAW_PCB_base, 44 &memp_tab_RAW_PCB 45 }; 46 47 /* 再次转换 */ 48 u8_t memp_memory_RAW_PCB_base[(((((MEMP_NUM_RAW_PCB) * 49 (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb))))) 50 + MEM_ALIGNMENT - 1U))]; 51 52 static struct memp *memp_tab_RAW_PCB; 53 54 const struct memp_desc memp_RAW_PCB ={ 55 (((sizeof(struct raw_pcb)) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U)), 56 LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)), 57 (MEMP_NUM_RAW_PCB), 58 memp_memory_RAW_PCB_base, 59 &memp_tab_RAW_PCB 60 }; 61 62 /* 代入数据得到,注意,数据是根据自己配置的宏定义得到的 */ 63 u8_t memp_memory_RAW_PCB_base[((4 * 24) + 4 - 1U)]; 64 65 static struct memp *memp_tab_RAW_PCB; 66 67 const struct memp_desc memp_RAW_PCB ={ 68 ((24) + 4 - 1U) & ~(4-1U)), 69 (24), 70 4 71 memp_memory_RAW_PCB_base, 72 &memp_tab_RAW_PCB 73 }; ``` 关于包含memp_std.h文件的处理就不再过多说明了,用户也不需要了解太多,我们就来看看关于内存池的主要参数,就使用上面的RAW_PCB的例子,每种POOL在经过编译器都会得到一个结构体,memp_desc memp_XXXX,XXXX表示对应的POOL类型,如RAW_PCB的结构体就是memp_desc memp_RAW_PCB,这里面就记录了该内存块对其后的大小LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb))。也就是说,在经过编译器的处理,该结构体就保存了每种POOL的内存对齐后的大小。 同理该结构体也记录了每种POOL的其他参数,如内存块的个数num,比如MEMP_NUM_RAW_PCB,这些就是用户配置的宏定义,都会被记录在里面,还有每种POOL的描述 “DECLARE_LWIP_MEMPOOL_DESC("RAW_PCB")”,当然这个参数可用可不用,这只是一个字符串,在输出信息的时候用到。 除了这些信息,还有一个最重要的信息,那就是真正的内存池区域,使用u8_t memp_memory_XXXX_base进行定义,XXXX表示对应的POOL类型,每个类型都有自己的内存池区域,是编译器开辟出来的内存空间,简单来说就是一个数组,我们知道这个区域的的起始地址,就能对它进行操作。