聊一聊Lwip內(nèi)存管理策略
01內(nèi)存池
LWIP中的內(nèi)存池(POOL)分配策略簡單,但是內(nèi)存的分配、釋放效率高,可以有效的防止內(nèi)存碎片的產(chǎn)生。在內(nèi)存的策略下用戶只能申請固定大小的空間,內(nèi)存池方法主要用于LWIP內(nèi)核中固定數(shù)據(jù)結(jié)構(gòu)的分配,比如UDP控制塊,TCP控制塊等。LWIP內(nèi)核在初始化的時候已經(jīng)為每個數(shù)據(jù)結(jié)構(gòu)類型都初始化了一定數(shù)量的POOL,文件memp.c和memp.h就是內(nèi)存池相關內(nèi)容。
至于LWIP內(nèi)核建立多少種POOL依賴于用戶和系統(tǒng)配置,比如如果定義了宏LWIP_UDP為1,那么在編譯時與UDP控制塊數(shù)據(jù)結(jié)構(gòu)相關的內(nèi)存池POOL就會被建立(MEMP_UDP_PCB),如果定義了宏LWIP_TCP為1,編譯時與TCP數(shù)據(jù)結(jié)構(gòu)相關的內(nèi)存池就會被建立(MEMP_TCP_PCB、MEMP_TCP_SEG)等等!每種類型的POOL大小都是固定的。用戶可以在lwipopts.h文件中定義,LWIP在opt.h中已經(jīng)配置了默認值。
有6個與LWIP內(nèi)存池有關的全局變量和數(shù)據(jù)結(jié)構(gòu):memp_t、memp_tab[]、memp_sizes[]、memp_num[]、memp_desc[]和memp_memory[]。
1.1、memp_t數(shù)據(jù)類型
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代表枚舉類型中元素總個數(shù)(C語言基礎知識),并不代表任何類型的POOL
1.2、memp_tab全局指針數(shù)組
memp_tab為一個全局指針數(shù)組,指向每類POOL的第一個POOL,memp_tab在文件memp.c文件中定義,定義如下:
1.3、memp_sizes全局數(shù)組
memp_sizes為一個全局數(shù)組,用來記錄每個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的大小,這里的大小都是進行了內(nèi)存對齊的。
這個宏定義,MEM_ALIGNMENT為4,也就是4字節(jié)對齊。
分析這個宏定義,也就說,當MEM_ALIGNMENT為4時,傳入的size為3時,變?yōu)?
申請3個字節(jié),實際申請4個字節(jié)
申請6個字節(jié),實際申請8個字節(jié)
申請18個字節(jié),實際申請20個字節(jié)
1.4、 memp_num[]全局數(shù)組
memp_num為一個全局數(shù)組,用來記錄每類POOL中POOL的個數(shù),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的數(shù)量,用戶可以在lwipopts.h文件中定義,LWIP在opt.h中已經(jīng)配置了默認值。
1.5、memp_desc[]全局型指針數(shù)組
memp_desc為一個全局型指針數(shù)組,指向每類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中的每個元素指向了一個字符串,這些字符串在統(tǒng)計信息輸出中可能用到。
1.6、memp_memory[]數(shù)組
memp_memory為一個數(shù)組,這個數(shù)組才是真正的內(nèi)存池!!!這個數(shù)組在文件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的話也需要對這個大小進行內(nèi)存對齊!
1.7、與內(nèi)存池管理相關的函數(shù):
使用內(nèi)存池分配內(nèi)存的優(yōu)點在于速度快,效率高,不會產(chǎn)生內(nèi)存碎片,但是缺點在于只能分配各種固定大小的內(nèi)存空間,LWIP必須實現(xiàn)知道用戶要使用哪些類型的POOL,每種類型的POOL數(shù)量,然后根據(jù)這個需求建立內(nèi)存池。
02內(nèi)存堆
LWIP還提供了另外一種內(nèi)存策略—內(nèi)存堆,使用內(nèi)存堆策略就可以隨便申請任意大小的內(nèi)存了。但是這種方法效率和速度會有所下降。
使用內(nèi)存堆策略的話用戶申請的內(nèi)存大小有最小限制,所申請的內(nèi)存大小不能小于MIN_SIZE,LWIP默認的MIN_SIZE為12個字節(jié),在mem.c文件中。該值用戶可以自行定義。
使用內(nèi)存堆策略,其有點事內(nèi)存浪費小,比較簡單,適合于小內(nèi)存的管理,但是缺點就是如果頻繁的進行動態(tài)內(nèi)存申請和釋放的話,可能會造成嚴重的內(nèi)存碎片,如果碎片嚴重的話可能會導致內(nèi)存分配失敗!
內(nèi)存堆策略下的內(nèi)存空間是數(shù)組:ram_heap[]。在mem.c文件中。
與內(nèi)存堆有關的函數(shù)有3個:
- mem_init()
- mem_malloc()
- mem_free()
03其他內(nèi)存策略
前面講的內(nèi)存池(POOL)和內(nèi)存堆(HEAP)這兩個內(nèi)存策略都是LWIP默認的內(nèi)存策略,LWIP內(nèi)核中大量的使用了這兩個策略,不過LWIP也給我們提供了其他可選的內(nèi)存策略。
(1)、當定義宏MEM_LIBC_MALLOC為1,那么與內(nèi)存堆相關的代碼就不會被編譯的,內(nèi)存堆中的mem_malloc()和mem_free()就會被ANSIC編譯器自帶的malloc()和free()替代。
(2)、當定義宏MEMP_MEM_MALLOC為1,那么內(nèi)存池文件memp.c就不會被編譯。
(3)、當定義宏MEM_USE_POOLS定義為1,那么內(nèi)存堆分配相關的函數(shù)及全局變量不會被編譯,這個時候就用內(nèi)存池分配方式來實現(xiàn)內(nèi)存堆的的分配方式,因為內(nèi)存池的分配策略效率很高。
但是(3)中的方法使用起來比較麻煩,需要在lwipopts.h中定義宏MEM_USE_POOLS和MEM_USE_CUSTOM_POOLS都為1,還需要在另外一個頭文件lwippools.h中開辟一些用于內(nèi)存堆分配函數(shù)的內(nèi)存池。