聊一聊Lwip內存管理策略
01內存池
LWIP中的內存池(POOL)分配策略簡單,但是內存的分配、釋放效率高,可以有效的防止內存碎片的產生。在內存的策略下用戶只能申請固定大小的空間,內存池方法主要用于LWIP內核中固定數據結構的分配,比如UDP控制塊,TCP控制塊等。LWIP內核在初始化的時候已經為每個數據結構類型都初始化了一定數量的POOL,文件memp.c和memp.h就是內存池相關內容。
至于LWIP內核建立多少種POOL依賴于用戶和系統配置,比如如果定義了宏LWIP_UDP為1,那么在編譯時與UDP控制塊數據結構相關的內存池POOL就會被建立(MEMP_UDP_PCB),如果定義了宏LWIP_TCP為1,編譯時與TCP數據結構相關的內存池就會被建立(MEMP_TCP_PCB、MEMP_TCP_SEG)等等!每種類型的POOL大小都是固定的。用戶可以在lwipopts.h文件中定義,LWIP在opt.h中已經配置了默認值。
有6個與LWIP內存池有關的全局變量和數據結構:memp_t、memp_tab[]、memp_sizes[]、memp_num[]、memp_desc[]和memp_memory[]。
1.1、memp_t數據類型
memp_t為一個枚舉類型變量,用來給每個POOL取個名字,或者說是編號。memp_t在文件memp.h文件中定義,定義如下:
- #define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
這句代碼意思是:
遇到LWIP_MEMPOOL(name,num,size,desc) 換成MEMP_##name
例如:在memp_std.h的34行
- LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(structraw_pcb), "RAW_PCB")
變成了
- MEMP_RAW_PCB
所以memp_t編譯之后如下:
- typedef enum
- {
- MEMP_ RAW_PCB,
- MEMP_ UDP_PCB,
- MEMP_ TCP_PCB,
- MEMP_ TCP_PCB_LISTEN,
- MEMP_ TCP_SEG,
- MEMP_ REASSDATA,
- …….
- MEMP_MAX
- } memp_t;
其中MEMP_MAX代表memp_t代表枚舉類型中元素總個數(C語言基礎知識),并不代表任何類型的POOL
1.2、memp_tab全局指針數組
memp_tab為一個全局指針數組,指向每類POOL的第一個POOL,memp_tab在文件memp.c文件中定義,定義如下:
1.3、memp_sizes全局數組
memp_sizes為一個全局數組,用來記錄每個POOL的大小,memp_sizes在文件memp.c文件中定義,定義如下:
編譯之后
- const u16_t memp_sizes[MEMP_MAX] =
- {
- LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)),
- LWIP_MEM_ALIGN_SIZE(sizeof(struct udp_pcb)),
- LWIP_MEM_ALIGN_SIZE(sizeof(struct tcp_pcb)),
- LWIP_MEM_ALIGN_SIZE(sizeof(structtcp_pcb_listen)),
- LWIP_MEM_ALIGN_SIZE(sizeof(struct tcp_seg)),
- …….
- }
memp_sizes中保存了每種類型POOL的大小,這里的大小都是進行了內存對齊的。
這個宏定義,MEM_ALIGNMENT為4,也就是4字節對齊。
分析這個宏定義,也就說,當MEM_ALIGNMENT為4時,傳入的size為3時,變為4
申請3個字節,實際申請4個字節
申請6個字節,實際申請8個字節
申請18個字節,實際申請20個字節
1.4、 memp_num[]全局數組
memp_num為一個全局數組,用來記錄每類POOL中POOL的個數,memp_num在文件memp.c文件中定義,定義如下:
編譯之后
- const u16_t memp_num[MEMP_MAX] =
- {
- (MEMP_NUM_RAW_PCB),
- (MEMP_NUM_UDP_PCB),
- (MEMP_NUM_TCP_PCB),
- (MEMP_NUM_TCP_PCB_LISTEN),
- (MEMP_NUM_TCP_SEG),
- ……
- };
上面的MEMP_NUM_RAW_PCB、MEMP_NUM_UDP_PCB等等都是由用戶定義的,用來記錄對應的POOL的數量,用戶可以在lwipopts.h文件中定義,LWIP在opt.h中已經配置了默認值。
1.5、memp_desc[]全局型指針數組
memp_desc為一個全局型指針數組,指向每類POOL的描述符,memp_desc在文件memp.c文件中定義,定義如下:
編譯之后
- static const char *memp_desc[MEMP_MAX] =
- {
- ("RAW_PCB"),
- ("UDP_PCB"),
- ("TCP_PCB"),
- ("TCP_PCB_LISTEN"),
- ("TCP_PCB_LISTEN"),
- …….
- };
memp_desc中的每個元素指向了一個字符串,這些字符串在統計信息輸出中可能用到。
1.6、memp_memory[]數組
memp_memory為一個數組,這個數組才是真正的內存池!!!這個數組在文件memp.c文件中定義,定義如下:
編譯之后
- static u8_t memp_memory
- [
- MEM_ALIGNMENT – 1
- +((MEMP_NUM_RAW_PCB) * (MEMP_SIZE +
- MEMP_ALIGN_SIZE(sizeof(struct raw_pcb)) ))
- +((MEMP_NUM_UDP_PCB) * (MEMP_SIZE +
- MEMP_ALIGN_SIZE(sizeof(struct udp_pcb)) ))
- +((MEMP_NUM_TCP_PCB) * (MEMP_SIZE +
- MEMP_ALIGN_SIZE(sizeof(struct tcp_pcb)) ))
- ……..
- ];
其中MEMP_SIZE表示需要在每個POOL頭部預留的空間,LWIP中在某些特殊場合使用該空間中的值來對POOL進行特殊處理,這里不使用該項功能,所以MEMP_SIZE為0,。如果使用到MEMP_SIZE的話也需要對這個大小進行內存對齊!
1.7、與內存池管理相關的函數:
使用內存池分配內存的優點在于速度快,效率高,不會產生內存碎片,但是缺點在于只能分配各種固定大小的內存空間,LWIP必須實現知道用戶要使用哪些類型的POOL,每種類型的POOL數量,然后根據這個需求建立內存池。
02內存堆
LWIP還提供了另外一種內存策略—內存堆,使用內存堆策略就可以隨便申請任意大小的內存了。但是這種方法效率和速度會有所下降。
使用內存堆策略的話用戶申請的內存大小有最小限制,所申請的內存大小不能小于MIN_SIZE,LWIP默認的MIN_SIZE為12個字節,在mem.c文件中。該值用戶可以自行定義。
使用內存堆策略,其有點事內存浪費小,比較簡單,適合于小內存的管理,但是缺點就是如果頻繁的進行動態內存申請和釋放的話,可能會造成嚴重的內存碎片,如果碎片嚴重的話可能會導致內存分配失敗!
內存堆策略下的內存空間是數組:ram_heap[]。在mem.c文件中。
與內存堆有關的函數有3個:
- mem_init()
- mem_malloc()
- mem_free()
03其他內存策略
前面講的內存池(POOL)和內存堆(HEAP)這兩個內存策略都是LWIP默認的內存策略,LWIP內核中大量的使用了這兩個策略,不過LWIP也給我們提供了其他可選的內存策略。
(1)、當定義宏MEM_LIBC_MALLOC為1,那么與內存堆相關的代碼就不會被編譯的,內存堆中的mem_malloc()和mem_free()就會被ANSIC編譯器自帶的malloc()和free()替代。
(2)、當定義宏MEMP_MEM_MALLOC為1,那么內存池文件memp.c就不會被編譯。
(3)、當定義宏MEM_USE_POOLS定義為1,那么內存堆分配相關的函數及全局變量不會被編譯,這個時候就用內存池分配方式來實現內存堆的的分配方式,因為內存池的分配策略效率很高。
但是(3)中的方法使用起來比較麻煩,需要在lwipopts.h中定義宏MEM_USE_POOLS和MEM_USE_CUSTOM_POOLS都為1,還需要在另外一個頭文件lwippools.h中開辟一些用于內存堆分配函數的內存池。