在内核初始化时,会事先在内存中初始化相应的内存池,内核会将所有可用的区域根据宏定义的配置以固定的大小为单位进行划分,然后用一个简单的链表将所有空闲块连接起来,这样子就组成一个个的内存池。由于链表中所有节点的大小相同,所以分配时不需要查找,直接取出第一个节点中的空间分配给用户即可。
注意了,内核在初始化内存池的时候,是根据用户配置的宏定义进行初始化的,比如,用户定义了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类型,每个类型都有自己的内存池区域,是编译器开辟出来的内存空间,简单来说就是一个数组,我们知道这个区域的的起始地址,就能对它进行操作。
- 说明
- 第1章:网络协议简介
- 1.1:常用网络协议
- 1.2:网络协议的分层模型
- 1.3:协议层报文间的封装与拆封
- 第2章:LwIP简介
- 2.1:LwIP的优缺点
- 2.2:LwIP的文件说明
- 2.2.1:如何获取LwIP源码文件
- 2.2.2:LwIP文件说明
- 2.3:查看LwIP的说明文档
- 2.4:使用vscode查看源码
- 2.4.1:查看文件中的符号列表(函数列表)
- 2.4.2:函数定义跳转
- 2.5:LwIP源码里的example
- 2.6:LwIP的三种编程接口
- 2.6.1:RAW/Callback API
- 2.6.2:NETCONN API
- 2.6.3:SOCKET API
- 第3章:开发平台介绍
- 3.1:以太网简介
- 3.1.1:PHY层
- 3.1.2:MAC子层
- 3.2:STM32的ETH外设
- 3.3:MII 和 RMII 接口
- 3.4:PHY:LAN8720A
- 3.5:硬件设计
- 3.6:软件设计
- 3.6.1:获取STM32的裸机工程模板
- 3.6.2:添加bsp_eth.c与bsp_eth.h
- 3.6.3:修改stm32f4xx_hal_conf.h文件
- 第4章:LwIP的网络接口管理
- 4.1:netif结构体
- 4.2:netif使用
- 4.3:与netif相关的底层函数
- 4.4:ethernetif.c文件内容
- 4.4.1:ethernetif数据结构
- 4.4.2:ethernetif_init()
- 4.4.3:low_level_init()
- 第5章:LwIP的内存管理
- 5.1:几种内存分配策略
- 5.1.1:固定大小的内存块
- 5.1.2:可变长度分配
- 5.2:动态内存池(POOL)
- 5.2.1:内存池的预处理
- 5.2.2:内存池的初始化
- 5.2.3:内存分配
- 5.2.4:内存释放
- 5.3:动态内存堆
- 5.3.1:内存堆的组织结构
- 5.3.2:内存堆初始化
- 5.3.3:内存分配
- 5.3.4:内存释放
- 5.4:使用C库的malloc和free来管理内存
- 5.5:LwIP中的配置
- 第6章:网络数据包
- 6.1:TCP/IP协议的分层思想
- 6.2:LwIP的线程模型
- 6.3:pbuf结构体说明
- 6.4:pbuf的类型
- 6.4.1:PBUF_RAM类型的pbuf
- 6.4.2:PBUF_POOL类型的pbuf
- 6.4.3:PBUF_ROM和PBUF_REF类型pbuf
- 6.5:pbuf_alloc()
- 6.6:pbuf_free()
- 6.7:其它pbuf操作函数
- 6.7.1:pbuf_realloc()
- 6.7.2:pbuf_header()
- 6.7.3:pbuf_take()
- 6.8:网卡中使用的pbuf
- 6.8.1:low_level_output()
- 6.8.2:low_level_input()
- 6.8.3:ethernetif_input()
- 第7章:无操作系统移植LwIP
- 7.1:将LwIP添加到裸机工程
- 7.2:移植头文件
- 7.3:移植网卡驱动
- 7.4:LwIP时基
- 7.5:协议栈初始化
- 7.6:获取数据包
- 7.6.1:查询方式
- 7.6.2:ping命令详解
- 7.6.3:中断方式
- 第8章:有操作系统移植LwIP
- 8.1:LwIP中添加操作系统
- 8.1.1:拷贝FreeRTOS源码到工程文件夹
- 8.1.2:添加FreeRTOS源码到工程组文件夹
- 8.1.3:指定FreeRTOS头文件的路径
- 8.1.4:修改stm32f10x_it.c
- 8.2:lwipopts.h文件需要加入的配置
- 8.3:sys_arch.c/h文件的编写
- 8.4:网卡底层的编写
- 8.5:协议栈初始化
- 8.6:移植后使用ping测试基本响应
- 第9章:LwIP一探究竟
- 9.1:网卡接收数据的流程
- 9.2:内核超时处理
- 9.2.1:sys_timeo结构体与超时链表
- 9.2.2:注册超时事件
- 9.2.3:超时检查
- 9.3:tcpip_thread线程
- 9.4:LwIP中的消息
- 9.4.1:消息结构
- 9.4.2:数据包消息
- 9.4.3:API消息
- 9.5:揭开LwIP神秘的面纱
- 第10章:ARP协议
- 10.1:链路层概述
- 10.2:MAC地址的基本概念
- 10.3:初识ARP
- 10.4:以太网帧结构
- 10.5:IP地址映射为物理地址
- 10.6:ARP缓存表
- 10.7:ARP缓存表的超时处理
- 10.8:ARP报文
- 10.9:发送ARP请求包
- 10.10:数据包接收流程
- 10.10.1:以太网之数据包接收
- 10.10.2:ARP数据包处理
- 10.10.3:更新ARP缓存表
- 10.11:数据包发送流程
- 10.11.1:etharp_output()函数
- 10.11.2:etharp_output_to_arp_index()函数
- 10.11.3:etharp_query()函数
- 第11章:IP协议
- 11.1:IP地址.md
- 11.1.1:概述
- 11.1.2:IP地址编址
- 11.1.3:特殊IP地址