前言
本文講RT-Thread的內(nèi)存管理,包括為何不使用C標(biāo)準(zhǔn)庫的內(nèi)存管理函數(shù)、內(nèi)存管理的特點、RT-Thread 程序內(nèi)存分布、內(nèi)存堆管理、內(nèi)存池管理以及使用STM32進行實驗。
一、不直接使用 C 標(biāo)準(zhǔn)庫中的內(nèi)存管理函數(shù)的原因
很多人會有疑問,為什么不直接使用 C 標(biāo)準(zhǔn)庫中的內(nèi)存管理函數(shù)呢?在電腦中我們可以用malloc()和 free()這兩個函數(shù)動態(tài)的分配內(nèi)存和釋放內(nèi)存。但是,在嵌入式實時操作系統(tǒng)中,調(diào)用 malloc()和 free()卻是危險的,原因有以下幾點:
1、這些函數(shù)在小型嵌入式系統(tǒng)中并不總是可用的,小型嵌入式設(shè)備中的 RAM 不足。
2、它們的實現(xiàn)可能非常的大,占據(jù)了相當(dāng)大的一塊代碼空間。
3、他們幾乎都不是線程安全的。
4、它們并不是確定的,每次調(diào)用這些函數(shù)執(zhí)行的時間可能都不一樣。
5、它們有可能產(chǎn)生碎片。
6、這兩個函數(shù)會使得鏈接器配置得復(fù)雜。
7、如果允許堆空間的生長方向覆蓋其他變量占據(jù)的內(nèi)存,它們會成為 debug 的災(zāi)難 。
二、內(nèi)存管理的功能特點
1、分配內(nèi)存的時間必須是確定的。一般內(nèi)存管理算法是根據(jù)需要存儲的數(shù)據(jù)的長度在內(nèi)存中去尋找一個與這段數(shù)據(jù)相適應(yīng)的空閑內(nèi)存塊,然后將數(shù)據(jù)存儲在里面。而尋找這樣一個空閑內(nèi)存塊所耗費的時間是不確定的,因此對于實時系統(tǒng)來說,這就是不可接受的,實時系統(tǒng)必須要保證內(nèi)存塊的分配過程在可預(yù)測的確定時間內(nèi)完成,否則實時任務(wù)對外部事件的響應(yīng)也將變得不可確定。
2、隨著內(nèi)存不斷被分配和釋放,整個內(nèi)存區(qū)域會產(chǎn)生越來越多的碎片(因為在使用過程中,申請了一些內(nèi)存,其中一些釋放了,導(dǎo)致內(nèi)存空間中存在一些小的內(nèi)存塊,它們地址不連續(xù),不能夠作為一整塊的大內(nèi)存分配出去),系統(tǒng)中還有足夠的空閑內(nèi)存,但因為它們地址并非連續(xù),不能組成一塊連續(xù)的完整內(nèi)存塊,會使得程序不能申請到大的內(nèi)存。對于通用系統(tǒng)而言,這種不恰當(dāng)?shù)膬?nèi)存分配算法可以通過重新啟動系統(tǒng)來解決 (每個月或者數(shù)個月進行一次),但是對于那些需要常年不間斷地工作于野外的嵌入式系統(tǒng)來說,就變得讓人無法接受了。
3、嵌入式系統(tǒng)的資源環(huán)境也是不盡相同,有些系統(tǒng)的資源比較緊張,只有數(shù)十 KB 的內(nèi)存可供分配,而有些系統(tǒng)則存在數(shù) MB 的內(nèi)存,如何為這些不同的系統(tǒng),選擇適合它們的高效率的內(nèi)存分配算法,就將變得復(fù)雜化。
三、RT-Thread 程序內(nèi)存分布
一般 MCU 包含的存儲空間有:片內(nèi) Flash 與片內(nèi) RAM,RAM 相當(dāng)于內(nèi)存,F(xiàn)lash 相當(dāng)于硬盤。
1、對于STM32,在keil編譯后,會出現(xiàn)如下信息:
上面提到的 Program Size 包含以下幾個部分:
(1)Code:代碼段,存放程序的代碼部分;
(2)RO-data:只讀數(shù)據(jù)段,存放程序中定義的常量;
(3)RW-data:讀寫數(shù)據(jù)段,存放初始化為非 0 值的全局變量;
(4)ZI-data:0 數(shù)據(jù)段,存放未初始化的全局變量及初始化為 0 的變量;
編譯完工程會生成一個. map 的文件,該文件說明了各個函數(shù)占用的尺寸和地址,在文件的最后幾行也說明了上面幾個字段的關(guān)系:
1TotalROSize(Code+ROData)43688(42.66kB)2TotalRWSize(RWData+ZIData)3976(3.88kB)3TotalROMSize(Code+ROData+RWData)43812(42.79kB)4
2、程序運行之前,需要有文件實體被燒錄到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,該被燒錄文件稱為可執(zhí)行映像文件。如圖下圖 中左圖所示,是可執(zhí)行映像文件燒錄到 STM32 后的內(nèi)存分布,它包含 RO 段和 RW 段兩個部分:其中 RO 段中保存了 Code、RO-data 的數(shù)據(jù),RW 段保存了 RW-data 的數(shù)據(jù),由于 ZI-data 都是 0,所以未包含在映像文件中。
RT-Thread 內(nèi)存分布(來源RT-Thread編程指南)
3、STM32 在上電啟動之后默認從 Flash 啟動,啟動之后會將 RW 段中的 RW-data(初始化的全局變量)搬運到 RAM 中,但不會搬運 RO 段,即 CPU 的執(zhí)行代碼從 Flash 中讀取,另外根據(jù)編譯器給出的 ZI 地址和大小分配出 ZI 段,并將這塊 RAM 區(qū)域清零。
四、內(nèi)存堆管理
內(nèi)存堆管理根據(jù)具體內(nèi)存設(shè)備劃分為三種情況:(1)針對小內(nèi)存塊的分配管理(小內(nèi)存管理算法);(2)針對大內(nèi)存塊的分配管理(slab 管理算法);(3)針對多內(nèi)存堆的分配情況(memheap 管理算法)
1、將 *“ZI 段結(jié)尾處”* 到內(nèi)存尾部的空間用作內(nèi)存堆
(1)內(nèi)存堆管理用于管理一段連續(xù)的內(nèi)存空間如下圖所示,RT-Thread 將 “ZI 段結(jié)尾處” 到內(nèi)存尾部的空間用作內(nèi)存堆。
RT-Thread 內(nèi)存分布(來源RT-Thread編程指南)
(2)在前面的其他筆記,都是從內(nèi)部SRAM申請一塊靜態(tài)內(nèi)存來作為內(nèi)存使用。
1#ifdefined(RT_USING_USER_MAIN)&&defined(RT_USING_HEAP) 2#defineRT_HEAP_SIZE6*1024 3/*從內(nèi)部SRAM申請一塊靜態(tài)內(nèi)存來作為內(nèi)存堆使用*/ 4staticuint32_trt_heap[RT_HEAP_SIZE];//heapdefaultsize:24K(1024*4*6) 5 6RT_WEAKvoid*rt_heap_begin_get(void) 7{ 8returnrt_heap; 9}1011RT_WEAKvoid*rt_heap_end_get(void)12{13returnrt_heap+RT_HEAP_SIZE;14}15#endif161718/*在rt_hw_board_init中*/1920rt_system_heap_init(rt_heap_begin_get(),rt_heap_end_get());
(3)那么接下來,我們修改代碼,將 “ZI 段結(jié)尾處” 到內(nèi)存尾部的空間用作內(nèi)存堆。
(A)在board.h添加如下代碼:
1#ifdef__ICCARM__ 2//Use*.icframsymbal,toavoidhardcode. 3externchar__ICFEDIT_region_IRAM1_end__; 4#defineSTM32_SRAM_END&__ICFEDIT_region_IRAM1_end__ 5#else 6#defineSTM32_SRAM_SIZE96/*根據(jù)自己的MCU不同修改*/ 7#defineSTM32_SRAM_END(0x20000000+STM32_SRAM_SIZE*1024)/*根據(jù)自己的MCU不同修改*/ 8#endif 910#ifdef__CC_ARM11externintImage$$RW_IRAM1$$ZI$$Limit;12#defineHEAP_BEGIN(&Image$$RW_IRAM1$$ZI$$Limit)13#elif__ICCARM__14#pragmasection="HEAP"15#defineHEAP_BEGIN(__segment_end("HEAP"))16#else17externint__bss_end;18#defineHEAP_BEGIN(&__bss_end)19#endif2021#defineHEAP_ENDSTM32_SRAM_END
(B)在board.c中將前面第(2)的那部分代碼全部去掉,然后修改rt_hw_board_init函數(shù),在后面加入如下代碼:
1#ifdefined(RT_USING_USER_MAIN)&&defined(RT_USING_HEAP)2rt_system_heap_init((void*)HEAP_BEGIN,(void*)HEAP_END);3#endif
2、小內(nèi)存管理算法
(1)小內(nèi)存管理算法是一個簡單的內(nèi)存分配算法。初始時,它是一塊大的內(nèi)存,其大小為(MEM_SIZE)。
初始時的內(nèi)存(來源[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》)
(2)當(dāng)需要分配內(nèi)存塊時,將從這個大的內(nèi)存塊上分割出相匹配的內(nèi)存塊,然后把分割出來的空閑內(nèi)存塊還回給堆管理系統(tǒng)中。每個內(nèi)存塊都包含一個管理用的數(shù)據(jù)頭,通過這個頭把使用塊與空閑塊用雙向鏈表的方式鏈接起來(內(nèi)存塊鏈表)。
小內(nèi)存管理工作機制圖(來源[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》)
(3)每個內(nèi)存塊(不管是已分配的內(nèi)存塊還是空閑的內(nèi)存塊)都包含一個數(shù)據(jù)頭,其中包括:
(A)magic:變數(shù)(或稱為幻數(shù)),它會被初始化成 0x1ea0(即英文單詞 heap),用于標(biāo)記這個內(nèi)存塊是一個內(nèi)存管理用的內(nèi)存數(shù)據(jù)塊;變數(shù)不僅僅用于標(biāo)識這個數(shù)據(jù)塊是一個內(nèi)存管理用的內(nèi)存數(shù)據(jù)塊,實質(zhì)也是一個內(nèi)存保護字:如果這個區(qū)域被改寫,那么也就意味著這塊內(nèi)存塊被非法改寫(正常情況下只有內(nèi)存管理器才會去碰這塊內(nèi)存)。
(B)used:指示出當(dāng)前內(nèi)存塊是否已經(jīng)分配。
(4)內(nèi)存管理的在表現(xiàn)主要體現(xiàn)在內(nèi)存的分配與釋放上,小型內(nèi)存管理算法可以用以下例子體現(xiàn)出來??臻e鏈表指針 lfree 初始指向 32 字節(jié)的內(nèi)存塊。當(dāng)用戶線程要再分配一個 64 字節(jié)的內(nèi)存塊時,但此 lfree 指針指向的內(nèi)存塊只有 32 字節(jié)并不能滿足要求,內(nèi)存管理器會繼續(xù)尋找下一內(nèi)存塊,當(dāng)找到再下一塊內(nèi)存塊,128 字節(jié)時,它滿足分配的要求。因為這個內(nèi)存塊比較大,分配器將把此內(nèi)存塊進行拆分,余下的內(nèi)存塊(52字節(jié))繼續(xù)留在 lfree鏈表中,在每次分配內(nèi)存塊前,都會留出 12 字節(jié)數(shù)據(jù)頭用于 magic,used 信息及鏈表節(jié)點使用。返回給應(yīng)用的地址實際上是這塊內(nèi)存塊 12 字節(jié)以后的地址,而數(shù)據(jù)頭部分是用戶永遠不應(yīng)該改變的部分。
小內(nèi)存管理算法鏈表結(jié)構(gòu)示意圖(來源[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》)
分配 64 字節(jié)后的鏈表結(jié)構(gòu)(來源[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》)
(5)釋放時則是相反的過程,分配器會查看前后相鄰的內(nèi)存塊是否空閑,如果空閑則合并成一個大的空閑內(nèi)存塊。
3、slab 管理算法
RT-Thread 的 slab 分配器是在 DragonFly BSD 創(chuàng)始人 Matthew Dillon 實現(xiàn)的 slab 分配器基礎(chǔ)上,針對嵌入式系統(tǒng)優(yōu)化的內(nèi)存分配算法。最原始的 slab 算法是 Jeff Bonwick 為 Solaris 操作系統(tǒng)而引入的一種高效內(nèi)核內(nèi)存分配算法。RT-Thread 的 slab 分配器實現(xiàn)主要是去掉了其中的對象構(gòu)造及析構(gòu)過程,只保留了純粹的緩沖型的內(nèi)存池算法。slab 分配器會根據(jù)對象的大小分成多個區(qū)(zone),也可以看成每類對象有一個內(nèi)存池,如下圖所示:
slab 內(nèi)存分配結(jié)構(gòu)圖(來源RT-Thread編程指南)
一個 zone 的大小在 32K 到 128K 字節(jié)之間,分配器會在堆初始化時根據(jù)堆的大小自動調(diào)整。系統(tǒng)中的 zone 最多包括 72 種對象,一次最大能夠分配 16K 的內(nèi)存空間,如果超出了 16K 那么直接從頁分配器中分配。每個 zone 上分配的內(nèi)存塊大小是固定的,能夠分配相同大小內(nèi)存塊的 zone 會鏈接在一個鏈表中,而 72 種對象的 zone 鏈表則放在一個數(shù)組(zone_array[])中統(tǒng)一管理。
下面是內(nèi)存分配器主要的兩種操作:
(1)內(nèi)存分配:假設(shè)分配一個 32 字節(jié)的內(nèi)存,slab 內(nèi)存分配器會先按照 32 字節(jié)的值,從 zone array 鏈表表頭數(shù)組中找到相應(yīng)的 zone 鏈表。如果這個鏈表是空的,則向頁分配器分配一個新的 zone,然后從 zone 中返回第一個空閑內(nèi)存塊。如果鏈表非空,則這個 zone 鏈表中的第一個 zone 節(jié)點必然有空閑塊存在(否則它就不應(yīng)該放在這個鏈表中),那么就取相應(yīng)的空閑塊。如果分配完成后,zone 中所有空閑內(nèi)存塊都使用完畢,那么分配器需要把這個 zone 節(jié)點從鏈表中刪除。
(2)內(nèi)存釋放:分配器需要找到內(nèi)存塊所在的 zone 節(jié)點,然后把內(nèi)存塊鏈接到 zone 的空閑內(nèi)存塊鏈表中。如果此時zone 的空閑鏈表指示出 zone 的所有內(nèi)存塊都已經(jīng)釋放,即 zone 是完全空閑的,那么當(dāng) zone 鏈表中全空閑 zone 達到一定數(shù)目后,系統(tǒng)就會把這個全空閑的 zone 釋放到頁面分配器中去。
4、memheap 管理算法
(1)memheap 管理算法適用于系統(tǒng)含有多個地址可不連續(xù)的內(nèi)存堆。使用 memheap 內(nèi)存管理可以簡化系統(tǒng)存在多個內(nèi)存堆時的使用:當(dāng)系統(tǒng)中存在多個內(nèi)存堆的時候,用戶只需要在系統(tǒng)初始化時將多個所需的memheap 初始化,并開啟 memheap 功能就可以很方便地把多個 memheap(地址可不連續(xù))粘合起來用于系統(tǒng)的 heap 分配。
注意:在開啟 memheap 之后原來的 heap 功能將被關(guān)閉,兩者只可以通過打開或關(guān)閉RT_USING_MEMHEAP_AS_HEAP來選擇其一。
(2)memheap 工作機制如下圖所示,首先將多塊內(nèi)存加入memheap_item鏈表進行粘合。當(dāng)分配內(nèi)存塊時,會先從默認內(nèi)存堆去分配內(nèi)存,當(dāng)分配不到時會查找 memheap_item 鏈表,嘗試從其他的內(nèi)存堆上分配內(nèi)存塊。應(yīng)用程序不用關(guān)心當(dāng)前分配的內(nèi)存塊位于哪個內(nèi)存堆上,就像是在操作一個內(nèi)存堆。
memheap 處理多內(nèi)存堆(來源RT-Thread編程指南)
(3)對于有部分ST MCU是將內(nèi)部SRAM分為地址不連續(xù)的兩部分SRAM1和SRAM2,那么就可以用memheap管理算法,例如IoT board的MCU STM32L475VET6。在前面講將到的 “ZI 段結(jié)尾處” 到內(nèi)存尾部的空間用作內(nèi)存堆,只是修改了SRAM1(96K)部分,那么如果想用SRAM2(32K)部分,需要修改代碼。
(A)在board.h中加入如下代碼:
1/*根據(jù)自己的MCU不同,確認MCU內(nèi)部SRAM是否有分為兩塊SRAM1和SRAM2,STM32L475VET6內(nèi)部SRAM分為SRAM1和SRAM2兩塊地址不連續(xù)*/2#defineSTM32_SRAM2_SIZE323#defineSTM32_SRAM2_BEGIN(0x10000000u)4#defineSTM32_SRAM2_END(0x10000000+STM32_SRAM2_SIZE*1024)5#defineSTM32_SRAM2_HEAP_SIZE((uint32_t)STM32_SRAM2_END-(uint32_t)STM32_SRAM2_BEGIN)
(B)在board.c中加入如下代碼:
1#ifdefined(RT_USING_MEMHEAP)&&defined(RT_USING_MEMHEAP_AS_HEAP)2staticstructrt_memheapsystem_heap;3#endif
(C)修改board.c中的rt_hw_board_init函數(shù),內(nèi)存堆配置和初始化代碼改為:
1#ifdefined(RT_USING_MEMHEAP)&&defined(RT_USING_MEMHEAP_AS_HEAP)2rt_system_heap_init((void*)HEAP_BEGIN,(void*)HEAP_END);3rt_memheap_init(&system_heap,"sram2",(void*)STM32_SRAM2_BEGIN,STM32_SRAM2_HEAP_SIZE);4#else5rt_system_heap_init((void*)HEAP_BEGIN,(void*)HEAP_END);6#endif
(4)根據(jù)自己是否想用使用SRAM2來決定是否使用memheap 管理算法,在rtconfig.h打開關(guān)閉相關(guān)宏來實現(xiàn),如需要使用memheap 管理算法,打開如下宏:
1#defineRT_USING_MEMHEAP//定義該宏可開啟兩個或以上內(nèi)存堆拼接的使用,未定義則關(guān)閉2#defineRT_USING_MEMHEAP_AS_HEAP
(5)如果RT_USING_MEMHEAP和RT_USING_MEMHEAP_AS_HEAP這兩個宏打開了,則使用memheap,那么系統(tǒng)內(nèi)存堆的時候首先會從SRAM1(96K的那塊)分配內(nèi)存,當(dāng)SRAM1(96K的那塊)用完了再到SRAM2(32K那塊)分配。
(6)打開RT_USING_MEMHEAP_AS_HEAP之后,實現(xiàn)的算法不同,比如rt_malloc()函數(shù)的實現(xiàn)。
5、內(nèi)存堆配置和初始化
(1)在使用內(nèi)存堆時,必須要在系統(tǒng)初始化的時候進行堆的初始化,可以通過下面的函數(shù)接口完成:
1voidrt_system_heap_init(void*begin_addr,void*end_addr);
(A)入口參數(shù):
begin_addr:堆內(nèi)存區(qū)域起始地址。end_addr:堆內(nèi)存區(qū)域結(jié)束地址。
(2)在使用 memheap 堆內(nèi)存時,必須要在系統(tǒng)初始化的時候進行堆內(nèi)存的初始化,可以通過下面的函數(shù)接口完成:
1rt_err_trt_memheap_init(structrt_memheap*memheap,2constchar*name,3void*start_addr,4rt_uint32_tsize);
(A)入口參數(shù):
memheap:memheap 控制塊。name:內(nèi)存堆的名稱。start_addr:堆內(nèi)存區(qū)域起始地址。size:堆內(nèi)存大小。
(B)返回值:
RT_EOK:成功。
6、內(nèi)存堆的管理方式
(1)申請內(nèi)存塊:會從系統(tǒng)堆空間中找到合適大小的內(nèi)存塊,然后把內(nèi)存塊可用地址返回給用戶,函數(shù)接口如下:
1void*rt_malloc(rt_size_tsize);
(A)入口參數(shù):
size:需要分配的內(nèi)存塊的大小,單位為字節(jié)。
(B)返回值:
分配的內(nèi)存塊地址:成功。RT_NULL:失敗。
(2)釋放內(nèi)存塊:應(yīng)用程序使用完從內(nèi)存分配器中申請的內(nèi)存后,必須及時釋放,否則會造成內(nèi)存泄漏,會把待釋放的內(nèi)存還回給堆管理器中,函數(shù)接口如下:
1voidrt_free(void*rmem);
(A)入口參數(shù):
rmem:待釋放的內(nèi)存塊指針。
(3)重分配內(nèi)存塊:在已分配內(nèi)存塊的基礎(chǔ)上重新分配內(nèi)存塊的大?。ㄔ黾踊蚩s?。?,在進行重新分配內(nèi)存塊時,原來的內(nèi)存塊數(shù)據(jù)保持不變(縮小的情況下,后面的數(shù)據(jù)被自動截斷),函數(shù)接口如下:
1void*rt_realloc(void*rmem,rt_size_tnewsize);
(A)入口參數(shù):
rmem:指向已分配的內(nèi)存塊。newsize:重新分配的內(nèi)存大小。
(B)返回值:
重新分配的內(nèi)存塊地址:成功。RT_NULL:失敗。
(4)分配多內(nèi)存塊:從內(nèi)存堆中分配連續(xù)內(nèi)存地址的多個內(nèi)存塊,可以通過下面的函數(shù)接口完成:
1void*rt_calloc(rt_size_tcount,rt_size_tsize);
(A)入口參數(shù):
count:內(nèi)存塊數(shù)量。size:內(nèi)存塊容量。
(B)返回值:
指向第一個內(nèi)存塊地址的指針:成功,并且所有分配的內(nèi)存塊都被初始化成零。RT_NULL:分配失敗。
(5)設(shè)置分配內(nèi)存鉤子函數(shù):在分配內(nèi)存塊過程中,用戶可設(shè)置一個鉤子函數(shù),設(shè)置的鉤子函數(shù)會在內(nèi)存分配完成后進行回調(diào)?;卣{(diào)時,會把分配到的內(nèi)存塊地址和大小做為入口參數(shù)傳遞進去,函數(shù)接口如下:
1voidrt_malloc_sethook(void(*hook)(void*ptr,rt_size_tsize));
(A)hook:鉤子函數(shù)指針。
(B)void hook(void *ptr, rt_size_t size); 函數(shù)接口參數(shù):
ptr:分配到的內(nèi)存塊指針。 size:分配到的內(nèi)存塊的大小。
(6)設(shè)置是否內(nèi)存鉤子函數(shù):在釋放內(nèi)存時,用戶可設(shè)置一個鉤子函數(shù),設(shè)置的鉤子函數(shù)會在調(diào)用內(nèi)存釋放完成前進行回調(diào)。回調(diào)時,釋放的內(nèi)存塊地址會做為入口參數(shù)傳遞進去(此時內(nèi)存塊并沒有被釋放),函數(shù)接口如下:
1voidrt_free_sethook(void(*hook)(void*ptr));
(A)hook:鉤子函數(shù)指針。
(B)void hook(void *ptr); 函數(shù)接口參數(shù):
ptr:待釋放的內(nèi)存塊指針。
五、內(nèi)存池
內(nèi)存堆管理器可以分配任意大小的內(nèi)存塊,非常靈活和方便。但其也存在明顯的缺點:一是分配效率不高,在每次分配時,都要空閑內(nèi)存塊查找;二是容易產(chǎn)生內(nèi)存碎片。為了提高內(nèi)存分配的效率,并且避免內(nèi)存碎片,RT-Thread 提供了另外一種內(nèi)存管理方法:內(nèi)存池(Memory Pool)。內(nèi)存池(Memory Pool)是一種用于分配大量大小相同的小內(nèi)存對象的技術(shù)。它可以極大加快內(nèi)存分配/釋放的速度。
1、內(nèi)存塊分配機制
(1)內(nèi)存池在創(chuàng)建時先向系統(tǒng)申請一大塊內(nèi)存,然后分成大小相等的多個小內(nèi)存塊,小內(nèi)存塊直接通過鏈表連接起來(此鏈表也稱為空閑內(nèi)存鏈表)。每次分配的時候,從空閑內(nèi)存鏈表中取出表頭上第一個內(nèi)存塊,提供給申請者。物理內(nèi)存中允許存在多個大小不同的內(nèi)存池,每一個內(nèi)存池又由多個大小相同的空閑內(nèi)存塊組成。當(dāng)一個內(nèi)存池對象被創(chuàng)建時,內(nèi)存池對象就被分配給了一個內(nèi)存池控制塊,內(nèi)存控制塊的參數(shù)包括內(nèi)存池名,內(nèi)存緩沖區(qū),內(nèi)存塊大小,塊數(shù)以及一個等待線程列表。
內(nèi)存池示意圖(來源[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》)
(2)內(nèi)核負責(zé)給內(nèi)存池分配內(nèi)存池控制塊,它同時也接收用戶線程的分配內(nèi)存塊申請,當(dāng)獲得這些信息后,內(nèi)核就可以從內(nèi)存池中為內(nèi)存池分配內(nèi)存。內(nèi)存池一旦初始化完成,內(nèi)部的內(nèi)存塊大小將不能再做調(diào)整。
2、內(nèi)存池的管理方式
(1)創(chuàng)建內(nèi)存池:創(chuàng)建內(nèi)存池操作將會創(chuàng)建一個內(nèi)存池對象并從堆上分配一個內(nèi)存池。創(chuàng)建內(nèi)存池是從對應(yīng)內(nèi)存池中分配和釋放內(nèi)存塊的先決條件,創(chuàng)建內(nèi)存池后,線程便可以從內(nèi)存池中執(zhí)行申請、釋放等操作。函數(shù)接口如下:
1rt_mp_trt_mp_create(constchar*name,2rt_size_tblock_count,3rt_size_tblock_size);
(A)入口參數(shù):name:內(nèi)存池名。block_count:內(nèi)存塊數(shù)量。block_size:內(nèi)存塊容量。
(B)返回值:
內(nèi)存池的句柄:創(chuàng)建內(nèi)存池對象成功。RT_NULL:創(chuàng)建失敗。
(2)刪除內(nèi)存池:將刪除內(nèi)存池對象并釋放申請的內(nèi)存,刪除內(nèi)存池時,會首先喚醒等待在該內(nèi)存池對象上的所有線程(返回 RT_ERROR),然后再釋放已從內(nèi)存堆上分配的內(nèi)存池數(shù)據(jù)存放區(qū)域,然后刪除內(nèi)存池對象。函數(shù)接口如下:
1rt_err_trt_mp_delete(rt_mp_tmp);
(A)入口參數(shù):mp:rt_mp_create返回的內(nèi)存池對象句柄。
(B)返回值:RT_EOK:刪除成功。
(3)初始化內(nèi)存池:初始化內(nèi)存池跟創(chuàng)建內(nèi)存池類似,只是初始化內(nèi)存池用于靜態(tài)內(nèi)存管理模式,內(nèi)存池控制塊來源于用戶在系統(tǒng)中申請的靜態(tài)對象。另外與創(chuàng)建內(nèi)存池不同的是,此處內(nèi)存池對象所使用的內(nèi)存空間是由用戶指定的一個緩沖區(qū)空間,用戶把緩沖區(qū)的指針傳遞給內(nèi)存池控制塊,其余的初始化工作與創(chuàng)建內(nèi)存池相同。函數(shù)接口如下:
1rt_err_trt_mp_init(structrt_mempool*mp,2constchar*name,3void*start,4rt_size_tsize,5rt_size_tblock_size);
(A)入口參數(shù):
mp:內(nèi)存池對象。name:內(nèi)存池名。start:內(nèi)存池的起始位置。size:內(nèi)存池數(shù)據(jù)區(qū)域大小。block_size:內(nèi)存塊容量。
(B)返回值:
RT_EOK:初始化成功。RT_ERROR:失敗。
注意:內(nèi)存池塊個數(shù) = size / (block_size + 4 鏈表指針大小),計算結(jié)果取整數(shù)。例如:內(nèi)存池數(shù)據(jù)區(qū)總大小 size 設(shè)為 4096 字節(jié),內(nèi)存塊大小 block_size 設(shè)為 80 字節(jié);則申請的內(nèi)存塊個數(shù)為 4096/ (80+4)= 48 個。
(4)脫離內(nèi)存池:脫離內(nèi)存池將把內(nèi)存池對象從內(nèi)核對象管理器中脫離,內(nèi)核先喚醒所有等待在該內(nèi)存池對象上的線程,然后將內(nèi)存池對象從內(nèi)核對象管理器中脫離。函數(shù)接口如下:
1rt_err_trt_mp_detach(structrt_mempool*mp);
(A)入口參數(shù):
mp:內(nèi)存池對象。
(B)返回值:
RT_EOK:成功。
(5)分配內(nèi)存塊:從指定的內(nèi)存池中分配一個內(nèi)存塊,函數(shù)接口如下:
1void*rt_mp_alloc(rt_mp_tmp,rt_int32_ttime);
(A)入口參數(shù):
mp:內(nèi)存池對象。
time:超時時間。如果內(nèi)存池中有可用的內(nèi)存塊,則從內(nèi)存池的空閑塊鏈表上取下一個內(nèi)存塊,減少空閑塊數(shù)目并返回這個內(nèi)存塊;如果內(nèi)存池中已經(jīng)沒有空閑內(nèi)存塊,則判斷超時時間設(shè)置:若超時時間設(shè)置為零,則立刻返回空內(nèi)存塊;若等待時間大于零,則把當(dāng)前線程掛起在該內(nèi)存池對象上,直到內(nèi)存池中有可用的自由內(nèi)存塊,或等待時間到達。
(B)返回值:
分配的內(nèi)存塊地址:成功。RT_NULL:失敗。
(6)釋放內(nèi)存塊:任何內(nèi)存塊使用完后都必須被釋放,否則會造成內(nèi)存泄露。首先通過需要被釋放的內(nèi)存塊指針計算出該內(nèi)存塊所在的(或所屬于的)內(nèi)存池對象,然后增加內(nèi)存池對象的可用內(nèi)存塊數(shù)目,并把該被釋放的內(nèi)存塊加入空閑內(nèi)存塊鏈表上。接著判斷該內(nèi)存池對象上是否有掛起的線程,如果有,則喚醒掛起線程鏈表上的首線程。函數(shù)接口如下:
1voidrt_mp_free(void*block);
(A)入口參數(shù):block:內(nèi)存塊指針。
六、基于STM32的內(nèi)存管理實驗
光說不練都是假把式,那么接下來就行內(nèi)存管理的實際操作,基于STM32,使用RTT&正點原子聯(lián)合出品潘多拉開發(fā)板,實現(xiàn)兩個實驗,分別是內(nèi)存堆管理實驗和內(nèi)存池實驗。
1、內(nèi)存堆管理實驗
(1)實現(xiàn)代碼:
1#include"main.h" 2#include"board.h" 3#include"rtthread.h" 4#include"data_typedef.h" 5#include"key.h" 6 7/*線程句柄*/ 8staticrt_thread_tthread1=RT_NULL; 9voiddynmem_sample(void);1011intmain(void)12{13dynmem_sample();14return0;15}1617/**************************************************************18函數(shù)名稱:thread1_entry19函數(shù)功能:線程1入口函數(shù)20輸入?yún)?shù):parameter:入口參數(shù)21返回值:無22備注:無23**************************************************************/24voidthread1_entry(void*parameter)25{26u8key;27char*ptr=RT_NULL;2829while(1)30{31key=key_scan(0);3233if(key==KEY0_PRES)34{35ptr=rt_malloc(10);36if(ptr!=RT_NULL)37{38rt_kprintf("rt_mallocsuccessful\r\n");39sprintf(ptr,"%s","helloRTT");40rt_kprintf("0x%p\r\n",ptr);/*打印分配到的地址*/41rt_kprintf("%s\r\n",ptr);42}43else44{45rt_kprintf("rt_mallocfailed\r\n");46}4748rt_thread_mdelay(2000);4950if(ptr!=RT_NULL)51{52rt_free(ptr);53ptr=RT_NULL;54rt_kprintf("rt_freesuccessful\r\n");55}56else57{58rt_kprintf("rt_freefailed,ptr!=NULL\r\n");59}60}6162rt_thread_mdelay(1);63}64}656667voiddynmem_sample(void)68{69thread1=rt_thread_create("thread1",70thread1_entry,71NULL,72512,733,7420);75if(thread1!=RT_NULL)76{77rt_thread_startup(thread1);;78}79else80{81rt_kprintf("createthread1failed\r\n");82return;83}84}
(2)觀察FinSH,開機按下3次KEY0,如下現(xiàn)象,會打印出申請到內(nèi)存的地址,2秒后釋放內(nèi)存:
2、內(nèi)存池實驗
(1)實現(xiàn)代碼:
1#include"main.h" 2#include"board.h" 3#include"rtthread.h" 4#include"data_typedef.h" 5#include"key.h" 6 7/*線程句柄*/ 8staticrt_thread_tthread2=RT_NULL; 9 10staticrt_mp_tmp; 11 12voidmempool_sample(void); 13 14intmain(void) 15{ 16mempool_sample(); 17 18return0; 19} 20 21/************************************************************** 22函數(shù)名稱:thread2_entry 23函數(shù)功能:線程2入口函數(shù) 24輸入?yún)?shù):parameter:入口參數(shù) 25返回值:無 26備注:無 27**************************************************************/ 28voidthread2_entry(void*parameter) 29{ 30u8key; 31char*ptr=RT_NULL; 32 33while(1) 34{ 35key=key_scan(0); 36 37if(key==KEY1_PRES) 38{ 39ptr=rt_mp_alloc(mp,0); 40if(ptr!=RT_NULL) 41{ 42rt_kprintf("rt_mp_allocsuccessful\r\n"); 43sprintf(ptr,"%s","helloRTT"); 44rt_kprintf("0x%p\r\n",ptr);/*打印分配到的地址*/ 45rt_kprintf("%s\r\n",ptr); 46} 47else 48{ 49rt_kprintf("rt_mp_allocfailed\r\n"); 50} 51 52rt_thread_mdelay(2000); 53 54if(ptr!=RT_NULL) 55{ 56rt_mp_free(ptr); 57ptr=RT_NULL; 58rt_kprintf("rt_mp_freesuccessful\r\n"); 59} 60else 61{ 62rt_kprintf("rt_mp_freefailed,ptr!=NULL\r\n"); 63} 64} 65 66rt_thread_mdelay(1); 67} 68} 69 70 71voidmempool_sample(void) 72{ 73mp=rt_mp_create("mp1",20,20); 74 75if(mp!=RT_NULL) 76{ 77rt_kprintf("mempoolcreatesuccessful\r\n"); 78} 79else 80{ 81rt_kprintf("mempoolcreatefailed\r\n"); 82return; 83} 84 85thread2=rt_thread_create("thread2", 86thread2_entry, 87NULL, 88512, 893, 9020); 91if(thread2!=RT_NULL) 92{ 93rt_thread_startup(thread2);; 94} 95else 96{ 97rt_kprintf("createthread2failed\r\n"); 98return; 99}100101}
(2)觀察FinSH,開機,打印創(chuàng)建mempool成功信息,連續(xù)按3次KEY1,打印如下信息,包括申請到內(nèi)存的地址,2秒后釋放內(nèi)存:
參考文獻:
1、[野火?]《RT-Thread 內(nèi)核實現(xiàn)與應(yīng)用開發(fā)實戰(zhàn)—基于STM32》
2、《RT-THREAD 編程指南》
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4344瀏覽量
62813 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14162 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1302瀏覽量
40268
原文標(biāo)題:社區(qū)新人的RT-Thread學(xué)習(xí)筆記8——內(nèi)存管理
文章出處:【微信號:RTThread,微信公眾號:RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論