在內核初始化時,會事先在內存中初始化相應的內存池,內核會將所有可用的區域根據宏定義的配置以固定的大小為單位進行劃分,然后用一個簡單的鏈表將所有空閑塊連接起來,這樣子就組成一個個的內存池。由于鏈表中所有節點的大小相同,所以分配時不需要查找,直接取出第一個節點中的空間分配給用戶即可。
注意了,內核在初始化內存池的時候,是根據用戶配置的宏定義進行初始化的,比如,用戶定義了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類型中,是通過“##”連接符產生的。

圖 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地址