前言
本文講RT-Thread的內(nèi)存管理,包括為何不使用C標(biāo)準(zhǔn)庫(kù)的內(nèi)存管理函數(shù)、內(nèi)存管理的特點(diǎn)、RT-Thread 程序內(nèi)存分布、內(nèi)存堆管理、內(nèi)存池管理以及使用STM32進(jìn)行實(shí)驗(yàn)。
一、不直接使用 C 標(biāo)準(zhǔn)庫(kù)中的內(nèi)存管理函數(shù)的原因
很多人會(huì)有疑問(wèn),為什么不直接使用 C 標(biāo)準(zhǔn)庫(kù)中的內(nèi)存管理函數(shù)呢?在電腦中我們可以用malloc()和 free()這兩個(gè)函數(shù)動(dòng)態(tài)的分配內(nèi)存和釋放內(nèi)存。但是,在嵌入式實(shí)時(shí)操作系統(tǒng)中,調(diào)用 malloc()和 free()卻是危險(xiǎn)的,原因有以下幾點(diǎn):
1、這些函數(shù)在小型嵌入式系統(tǒng)中并不總是可用的,小型嵌入式設(shè)備中的 RAM 不足。
2、它們的實(shí)現(xiàn)可能非常的大,占據(jù)了相當(dāng)大的一塊代碼空間。
3、他們幾乎都不是線程安全的。
4、它們并不是確定的,每次調(diào)用這些函數(shù)執(zhí)行的時(shí)間可能都不一樣。
5、它們有可能產(chǎn)生碎片。
6、這兩個(gè)函數(shù)會(huì)使得鏈接器配置得復(fù)雜。
7、如果允許堆空間的生長(zhǎng)方向覆蓋其他變量占據(jù)的內(nèi)存,它們會(huì)成為 debug 的災(zāi)難 。
二、內(nèi)存管理的功能特點(diǎn)
1、分配內(nèi)存的時(shí)間必須是確定的。一般內(nèi)存管理算法是根據(jù)需要存儲(chǔ)的數(shù)據(jù)的長(zhǎng)度在內(nèi)存中去尋找一個(gè)與這段數(shù)據(jù)相適應(yīng)的空閑內(nèi)存塊,然后將數(shù)據(jù)存儲(chǔ)在里面。而尋找這樣一個(gè)空閑內(nèi)存塊所耗費(fèi)的時(shí)間是不確定的,因此對(duì)于實(shí)時(shí)系統(tǒng)來(lái)說(shuō),這就是不可接受的,實(shí)時(shí)系統(tǒng)必須要保證內(nèi)存塊的分配過(guò)程在可預(yù)測(cè)的確定時(shí)間內(nèi)完成,否則實(shí)時(shí)任務(wù)對(duì)外部事件的響應(yīng)也將變得不可確定。
2、隨著內(nèi)存不斷被分配和釋放,整個(gè)內(nèi)存區(qū)域會(huì)產(chǎn)生越來(lái)越多的碎片(因?yàn)樵谑褂眠^(guò)程中,申請(qǐng)了一些內(nèi)存,其中一些釋放了,導(dǎo)致內(nèi)存空間中存在一些小的內(nèi)存塊,它們地址不連續(xù),不能夠作為一整塊的大內(nèi)存分配出去),系統(tǒng)中還有足夠的空閑內(nèi)存,但因?yàn)樗鼈兊刂凡⒎沁B續(xù),不能組成一塊連續(xù)的完整內(nèi)存塊,會(huì)使得程序不能申請(qǐng)到大的內(nèi)存。對(duì)于通用系統(tǒng)而言,這種不恰當(dāng)?shù)膬?nèi)存分配算法可以通過(guò)重新啟動(dòng)系統(tǒng)來(lái)解決 (每個(gè)月或者數(shù)個(gè)月進(jìn)行一次),但是對(duì)于那些需要常年不間斷地工作于野外的嵌入式系統(tǒng)來(lái)說(shuō),就變得讓人無(wú)法接受了。
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 包含的存儲(chǔ)空間有:片內(nèi) Flash 與片內(nèi) RAM,RAM 相當(dāng)于內(nèi)存,F(xiàn)lash 相當(dāng)于硬盤。
1、對(duì)于STM32,在keil編譯后,會(huì)出現(xiàn)如下信息:
上面提到的 Program Size 包含以下幾個(gè)部分:
(1)Code:代碼段,存放程序的代碼部分;
(2)RO-data:只讀數(shù)據(jù)段,存放程序中定義的常量;
(3)RW-data:讀寫數(shù)據(jù)段,存放初始化為非 0 值的全局變量;
(4)ZI-data:0 數(shù)據(jù)段,存放未初始化的全局變量及初始化為 0 的變量;
編譯完工程會(huì)生成一個(gè). map 的文件,該文件說(shuō)明了各個(gè)函數(shù)占用的尺寸和地址,在文件的最后幾行也說(shuō)明了上面幾個(gè)字段的關(guān)系:
1TotalROSize(Code+ROData)43688(42.66kB)2TotalRWSize(RWData+ZIData)3976(3.88kB)3TotalROMSize(Code+ROData+RWData)43812(42.79kB)4
2、程序運(yùn)行之前,需要有文件實(shí)體被燒錄到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,該被燒錄文件稱為可執(zhí)行映像文件。如圖下圖 中左圖所示,是可執(zhí)行映像文件燒錄到 STM32 后的內(nèi)存分布,它包含 RO 段和 RW 段兩個(gè)部分:其中 RO 段中保存了 Code、RO-data 的數(shù)據(jù),RW 段保存了 RW-data 的數(shù)據(jù),由于 ZI-data 都是 0,所以未包含在映像文件中。
RT-Thread 內(nèi)存分布(來(lái)源RT-Thread編程指南)
3、STM32 在上電啟動(dòng)之后默認(rèn)從 Flash 啟動(dòng),啟動(dòng)之后會(huì)將 RW 段中的 RW-data(初始化的全局變量)搬運(yùn)到 RAM 中,但不會(huì)搬運(yùn) RO 段,即 CPU 的執(zhí)行代碼從 Flash 中讀取,另外根據(jù)編譯器給出的 ZI 地址和大小分配出 ZI 段,并將這塊 RAM 區(qū)域清零。
四、內(nèi)存堆管理
內(nèi)存堆管理根據(jù)具體內(nèi)存設(shè)備劃分為三種情況:(1)針對(duì)小內(nèi)存塊的分配管理(小內(nèi)存管理算法);(2)針對(duì)大內(nèi)存塊的分配管理(slab 管理算法);(3)針對(duì)多內(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)存分布(來(lái)源RT-Thread編程指南)
(2)在前面的其他筆記,都是從內(nèi)部SRAM申請(qǐng)一塊靜態(tài)內(nèi)存來(lái)作為內(nèi)存使用。
1#ifdefined(RT_USING_USER_MAIN)&&defined(RT_USING_HEAP) 2#defineRT_HEAP_SIZE6*1024 3/*從內(nèi)部SRAM申請(qǐng)一塊靜態(tài)內(nèi)存來(lá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)那么接下來(lái),我們修改代碼,將 “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)存管理算法是一個(gè)簡(jiǎn)單的內(nèi)存分配算法。初始時(shí),它是一塊大的內(nèi)存,其大小為(MEM_SIZE)。
初始時(shí)的內(nèi)存(來(lái)源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》)
(2)當(dāng)需要分配內(nèi)存塊時(shí),將從這個(gè)大的內(nèi)存塊上分割出相匹配的內(nèi)存塊,然后把分割出來(lái)的空閑內(nèi)存塊還回給堆管理系統(tǒng)中。每個(gè)內(nèi)存塊都包含一個(gè)管理用的數(shù)據(jù)頭,通過(guò)這個(gè)頭把使用塊與空閑塊用雙向鏈表的方式鏈接起來(lái)(內(nèi)存塊鏈表)。
小內(nèi)存管理工作機(jī)制圖(來(lái)源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》)
(3)每個(gè)內(nèi)存塊(不管是已分配的內(nèi)存塊還是空閑的內(nèi)存塊)都包含一個(gè)數(shù)據(jù)頭,其中包括:
(A)magic:變數(shù)(或稱為幻數(shù)),它會(huì)被初始化成 0x1ea0(即英文單詞 heap),用于標(biāo)記這個(gè)內(nèi)存塊是一個(gè)內(nèi)存管理用的內(nèi)存數(shù)據(jù)塊;變數(shù)不僅僅用于標(biāo)識(shí)這個(gè)數(shù)據(jù)塊是一個(gè)內(nèi)存管理用的內(nèi)存數(shù)據(jù)塊,實(shí)質(zhì)也是一個(gè)內(nèi)存保護(hù)字:如果這個(gè)區(qū)域被改寫,那么也就意味著這塊內(nèi)存塊被非法改寫(正常情況下只有內(nèi)存管理器才會(huì)去碰這塊內(nèi)存)。
(B)used:指示出當(dāng)前內(nèi)存塊是否已經(jīng)分配。
(4)內(nèi)存管理的在表現(xiàn)主要體現(xiàn)在內(nèi)存的分配與釋放上,小型內(nèi)存管理算法可以用以下例子體現(xiàn)出來(lái)??臻e鏈表指針 lfree 初始指向 32 字節(jié)的內(nèi)存塊。當(dāng)用戶線程要再分配一個(gè) 64 字節(jié)的內(nèi)存塊時(shí),但此 lfree 指針指向的內(nèi)存塊只有 32 字節(jié)并不能滿足要求,內(nèi)存管理器會(huì)繼續(xù)尋找下一內(nèi)存塊,當(dāng)找到再下一塊內(nèi)存塊,128 字節(jié)時(shí),它滿足分配的要求。因?yàn)檫@個(gè)內(nèi)存塊比較大,分配器將把此內(nèi)存塊進(jìn)行拆分,余下的內(nèi)存塊(52字節(jié))繼續(xù)留在 lfree鏈表中,在每次分配內(nèi)存塊前,都會(huì)留出 12 字節(jié)數(shù)據(jù)頭用于 magic,used 信息及鏈表節(jié)點(diǎn)使用。返回給應(yīng)用的地址實(shí)際上是這塊內(nèi)存塊 12 字節(jié)以后的地址,而數(shù)據(jù)頭部分是用戶永遠(yuǎn)不應(yīng)該改變的部分。
小內(nèi)存管理算法鏈表結(jié)構(gòu)示意圖(來(lái)源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》)
分配 64 字節(jié)后的鏈表結(jié)構(gòu)(來(lái)源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》)
(5)釋放時(shí)則是相反的過(guò)程,分配器會(huì)查看前后相鄰的內(nèi)存塊是否空閑,如果空閑則合并成一個(gè)大的空閑內(nèi)存塊。
3、slab 管理算法
RT-Thread 的 slab 分配器是在 DragonFly BSD 創(chuàng)始人 Matthew Dillon 實(shí)現(xiàn)的 slab 分配器基礎(chǔ)上,針對(duì)嵌入式系統(tǒng)優(yōu)化的內(nèi)存分配算法。最原始的 slab 算法是 Jeff Bonwick 為 Solaris 操作系統(tǒng)而引入的一種高效內(nèi)核內(nèi)存分配算法。RT-Thread 的 slab 分配器實(shí)現(xiàn)主要是去掉了其中的對(duì)象構(gòu)造及析構(gòu)過(guò)程,只保留了純粹的緩沖型的內(nèi)存池算法。slab 分配器會(huì)根據(jù)對(duì)象的大小分成多個(gè)區(qū)(zone),也可以看成每類對(duì)象有一個(gè)內(nèi)存池,如下圖所示:
slab 內(nèi)存分配結(jié)構(gòu)圖(來(lái)源RT-Thread編程指南)
一個(gè) zone 的大小在 32K 到 128K 字節(jié)之間,分配器會(huì)在堆初始化時(shí)根據(jù)堆的大小自動(dòng)調(diào)整。系統(tǒng)中的 zone 最多包括 72 種對(duì)象,一次最大能夠分配 16K 的內(nèi)存空間,如果超出了 16K 那么直接從頁(yè)分配器中分配。每個(gè) zone 上分配的內(nèi)存塊大小是固定的,能夠分配相同大小內(nèi)存塊的 zone 會(huì)鏈接在一個(gè)鏈表中,而 72 種對(duì)象的 zone 鏈表則放在一個(gè)數(shù)組(zone_array[])中統(tǒng)一管理。
下面是內(nèi)存分配器主要的兩種操作:
(1)內(nèi)存分配:假設(shè)分配一個(gè) 32 字節(jié)的內(nèi)存,slab 內(nèi)存分配器會(huì)先按照 32 字節(jié)的值,從 zone array 鏈表表頭數(shù)組中找到相應(yīng)的 zone 鏈表。如果這個(gè)鏈表是空的,則向頁(yè)分配器分配一個(gè)新的 zone,然后從 zone 中返回第一個(gè)空閑內(nèi)存塊。如果鏈表非空,則這個(gè) zone 鏈表中的第一個(gè) zone 節(jié)點(diǎn)必然有空閑塊存在(否則它就不應(yīng)該放在這個(gè)鏈表中),那么就取相應(yīng)的空閑塊。如果分配完成后,zone 中所有空閑內(nèi)存塊都使用完畢,那么分配器需要把這個(gè) zone 節(jié)點(diǎn)從鏈表中刪除。
(2)內(nèi)存釋放:分配器需要找到內(nèi)存塊所在的 zone 節(jié)點(diǎn),然后把內(nèi)存塊鏈接到 zone 的空閑內(nèi)存塊鏈表中。如果此時(shí)zone 的空閑鏈表指示出 zone 的所有內(nèi)存塊都已經(jīng)釋放,即 zone 是完全空閑的,那么當(dāng) zone 鏈表中全空閑 zone 達(dá)到一定數(shù)目后,系統(tǒng)就會(huì)把這個(gè)全空閑的 zone 釋放到頁(yè)面分配器中去。
4、memheap 管理算法
(1)memheap 管理算法適用于系統(tǒng)含有多個(gè)地址可不連續(xù)的內(nèi)存堆。使用 memheap 內(nèi)存管理可以簡(jiǎn)化系統(tǒng)存在多個(gè)內(nèi)存堆時(shí)的使用:當(dāng)系統(tǒng)中存在多個(gè)內(nèi)存堆的時(shí)候,用戶只需要在系統(tǒng)初始化時(shí)將多個(gè)所需的memheap 初始化,并開(kāi)啟 memheap 功能就可以很方便地把多個(gè) memheap(地址可不連續(xù))粘合起來(lái)用于系統(tǒng)的 heap 分配。
注意:在開(kāi)啟 memheap 之后原來(lái)的 heap 功能將被關(guān)閉,兩者只可以通過(guò)打開(kāi)或關(guān)閉RT_USING_MEMHEAP_AS_HEAP來(lái)選擇其一。
(2)memheap 工作機(jī)制如下圖所示,首先將多塊內(nèi)存加入memheap_item鏈表進(jìn)行粘合。當(dāng)分配內(nèi)存塊時(shí),會(huì)先從默認(rèn)內(nèi)存堆去分配內(nèi)存,當(dāng)分配不到時(shí)會(huì)查找 memheap_item 鏈表,嘗試從其他的內(nèi)存堆上分配內(nèi)存塊。應(yīng)用程序不用關(guān)心當(dāng)前分配的內(nèi)存塊位于哪個(gè)內(nèi)存堆上,就像是在操作一個(gè)內(nèi)存堆。
memheap 處理多內(nèi)存堆(來(lái)源RT-Thread編程指南)
(3)對(duì)于有部分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不同,確認(rèn)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來(lái)決定是否使用memheap 管理算法,在rtconfig.h打開(kāi)關(guān)閉相關(guān)宏來(lái)實(shí)現(xiàn),如需要使用memheap 管理算法,打開(kāi)如下宏:
1#defineRT_USING_MEMHEAP//定義該宏可開(kāi)啟兩個(gè)或以上內(nèi)存堆拼接的使用,未定義則關(guān)閉2#defineRT_USING_MEMHEAP_AS_HEAP
(5)如果RT_USING_MEMHEAP和RT_USING_MEMHEAP_AS_HEAP這兩個(gè)宏打開(kāi)了,則使用memheap,那么系統(tǒng)內(nèi)存堆的時(shí)候首先會(huì)從SRAM1(96K的那塊)分配內(nèi)存,當(dāng)SRAM1(96K的那塊)用完了再到SRAM2(32K那塊)分配。
(6)打開(kāi)RT_USING_MEMHEAP_AS_HEAP之后,實(shí)現(xiàn)的算法不同,比如rt_malloc()函數(shù)的實(shí)現(xiàn)。
5、內(nèi)存堆配置和初始化
(1)在使用內(nèi)存堆時(shí),必須要在系統(tǒng)初始化的時(shí)候進(jìn)行堆的初始化,可以通過(guò)下面的函數(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)存時(shí),必須要在系統(tǒng)初始化的時(shí)候進(jìn)行堆內(nèi)存的初始化,可以通過(guò)下面的函數(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)申請(qǐng)內(nèi)存塊:會(huì)從系統(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)存分配器中申請(qǐng)的內(nèi)存后,必須及時(shí)釋放,否則會(huì)造成內(nèi)存泄漏,會(huì)把待釋放的內(nèi)存還回給堆管理器中,函數(shù)接口如下:
1voidrt_free(void*rmem);
(A)入口參數(shù):
rmem:待釋放的內(nèi)存塊指針。
(3)重分配內(nèi)存塊:在已分配內(nèi)存塊的基礎(chǔ)上重新分配內(nèi)存塊的大?。ㄔ黾踊蚩s?。?,在進(jìn)行重新分配內(nèi)存塊時(shí),原來(lái)的內(nèi)存塊數(shù)據(jù)保持不變(縮小的情況下,后面的數(shù)據(jù)被自動(dòng)截?cái)啵?,函?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)存地址的多個(gè)內(nèi)存塊,可以通過(guò)下面的函數(shù)接口完成:
1void*rt_calloc(rt_size_tcount,rt_size_tsize);
(A)入口參數(shù):
count:內(nèi)存塊數(shù)量。size:內(nèi)存塊容量。
(B)返回值:
指向第一個(gè)內(nèi)存塊地址的指針:成功,并且所有分配的內(nèi)存塊都被初始化成零。RT_NULL:分配失敗。
(5)設(shè)置分配內(nèi)存鉤子函數(shù):在分配內(nèi)存塊過(guò)程中,用戶可設(shè)置一個(gè)鉤子函數(shù),設(shè)置的鉤子函數(shù)會(huì)在內(nèi)存分配完成后進(jìn)行回調(diào)?;卣{(diào)時(shí),會(huì)把分配到的內(nèi)存塊地址和大小做為入口參數(shù)傳遞進(jìn)去,函數(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è)置一個(gè)鉤子函數(shù),設(shè)置的鉤子函數(shù)會(huì)在調(diào)用內(nèi)存釋放完成前進(jìn)行回調(diào)。回調(diào)時(shí),釋放的內(nèi)存塊地址會(huì)做為入口參數(shù)傳遞進(jìn)去(此時(shí)內(nèi)存塊并沒(mé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)存塊,非常靈活和方便。但其也存在明顯的缺點(diǎn):一是分配效率不高,在每次分配時(shí),都要空閑內(nèi)存塊查找;二是容易產(chǎn)生內(nèi)存碎片。為了提高內(nèi)存分配的效率,并且避免內(nèi)存碎片,RT-Thread 提供了另外一種內(nèi)存管理方法:內(nèi)存池(Memory Pool)。內(nèi)存池(Memory Pool)是一種用于分配大量大小相同的小內(nèi)存對(duì)象的技術(shù)。它可以極大加快內(nèi)存分配/釋放的速度。
1、內(nèi)存塊分配機(jī)制
(1)內(nèi)存池在創(chuàng)建時(shí)先向系統(tǒng)申請(qǐng)一大塊內(nèi)存,然后分成大小相等的多個(gè)小內(nèi)存塊,小內(nèi)存塊直接通過(guò)鏈表連接起來(lái)(此鏈表也稱為空閑內(nèi)存鏈表)。每次分配的時(shí)候,從空閑內(nèi)存鏈表中取出表頭上第一個(gè)內(nèi)存塊,提供給申請(qǐng)者。物理內(nèi)存中允許存在多個(gè)大小不同的內(nèi)存池,每一個(gè)內(nèi)存池又由多個(gè)大小相同的空閑內(nèi)存塊組成。當(dāng)一個(gè)內(nèi)存池對(duì)象被創(chuàng)建時(shí),內(nèi)存池對(duì)象就被分配給了一個(gè)內(nèi)存池控制塊,內(nèi)存控制塊的參數(shù)包括內(nèi)存池名,內(nèi)存緩沖區(qū),內(nèi)存塊大小,塊數(shù)以及一個(gè)等待線程列表。
內(nèi)存池示意圖(來(lái)源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》)
(2)內(nèi)核負(fù)責(zé)給內(nèi)存池分配內(nèi)存池控制塊,它同時(shí)也接收用戶線程的分配內(nèi)存塊申請(qǐng),當(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)存池操作將會(huì)創(chuàng)建一個(gè)內(nèi)存池對(duì)象并從堆上分配一個(gè)內(nèi)存池。創(chuàng)建內(nèi)存池是從對(duì)應(yīng)內(nèi)存池中分配和釋放內(nèi)存塊的先決條件,創(chuàng)建內(nèi)存池后,線程便可以從內(nèi)存池中執(zhí)行申請(qǐng)、釋放等操作。函數(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)存池對(duì)象成功。RT_NULL:創(chuàng)建失敗。
(2)刪除內(nèi)存池:將刪除內(nèi)存池對(duì)象并釋放申請(qǐng)的內(nèi)存,刪除內(nèi)存池時(shí),會(huì)首先喚醒等待在該內(nèi)存池對(duì)象上的所有線程(返回 RT_ERROR),然后再釋放已從內(nèi)存堆上分配的內(nèi)存池?cái)?shù)據(jù)存放區(qū)域,然后刪除內(nèi)存池對(duì)象。函數(shù)接口如下:
1rt_err_trt_mp_delete(rt_mp_tmp);
(A)入口參數(shù):mp:rt_mp_create返回的內(nèi)存池對(duì)象句柄。
(B)返回值:RT_EOK:刪除成功。
(3)初始化內(nèi)存池:初始化內(nèi)存池跟創(chuàng)建內(nèi)存池類似,只是初始化內(nèi)存池用于靜態(tài)內(nèi)存管理模式,內(nèi)存池控制塊來(lái)源于用戶在系統(tǒng)中申請(qǐng)的靜態(tài)對(duì)象。另外與創(chuàng)建內(nèi)存池不同的是,此處內(nèi)存池對(duì)象所使用的內(nèi)存空間是由用戶指定的一個(gè)緩沖區(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)存池對(duì)象。name:內(nèi)存池名。start:內(nèi)存池的起始位置。size:內(nèi)存池?cái)?shù)據(jù)區(qū)域大小。block_size:內(nèi)存塊容量。
(B)返回值:
RT_EOK:初始化成功。RT_ERROR:失敗。
注意:內(nèi)存池塊個(gè)數(shù) = size / (block_size + 4 鏈表指針大小),計(jì)算結(jié)果取整數(shù)。例如:內(nèi)存池?cái)?shù)據(jù)區(qū)總大小 size 設(shè)為 4096 字節(jié),內(nèi)存塊大小 block_size 設(shè)為 80 字節(jié);則申請(qǐng)的內(nèi)存塊個(gè)數(shù)為 4096/ (80+4)= 48 個(gè)。
(4)脫離內(nèi)存池:脫離內(nèi)存池將把內(nèi)存池對(duì)象從內(nèi)核對(duì)象管理器中脫離,內(nèi)核先喚醒所有等待在該內(nèi)存池對(duì)象上的線程,然后將內(nèi)存池對(duì)象從內(nèi)核對(duì)象管理器中脫離。函數(shù)接口如下:
1rt_err_trt_mp_detach(structrt_mempool*mp);
(A)入口參數(shù):
mp:內(nèi)存池對(duì)象。
(B)返回值:
RT_EOK:成功。
(5)分配內(nèi)存塊:從指定的內(nèi)存池中分配一個(gè)內(nèi)存塊,函數(shù)接口如下:
1void*rt_mp_alloc(rt_mp_tmp,rt_int32_ttime);
(A)入口參數(shù):
mp:內(nèi)存池對(duì)象。
time:超時(shí)時(shí)間。如果內(nèi)存池中有可用的內(nèi)存塊,則從內(nèi)存池的空閑塊鏈表上取下一個(gè)內(nèi)存塊,減少空閑塊數(shù)目并返回這個(gè)內(nèi)存塊;如果內(nèi)存池中已經(jīng)沒(méi)有空閑內(nèi)存塊,則判斷超時(shí)時(shí)間設(shè)置:若超時(shí)時(shí)間設(shè)置為零,則立刻返回空內(nèi)存塊;若等待時(shí)間大于零,則把當(dāng)前線程掛起在該內(nèi)存池對(duì)象上,直到內(nèi)存池中有可用的自由內(nèi)存塊,或等待時(shí)間到達(dá)。
(B)返回值:
分配的內(nèi)存塊地址:成功。RT_NULL:失敗。
(6)釋放內(nèi)存塊:任何內(nèi)存塊使用完后都必須被釋放,否則會(huì)造成內(nèi)存泄露。首先通過(guò)需要被釋放的內(nèi)存塊指針計(jì)算出該內(nèi)存塊所在的(或所屬于的)內(nèi)存池對(duì)象,然后增加內(nèi)存池對(duì)象的可用內(nèi)存塊數(shù)目,并把該被釋放的內(nèi)存塊加入空閑內(nèi)存塊鏈表上。接著判斷該內(nèi)存池對(duì)象上是否有掛起的線程,如果有,則喚醒掛起線程鏈表上的首線程。函數(shù)接口如下:
1voidrt_mp_free(void*block);
(A)入口參數(shù):block:內(nèi)存塊指針。
六、基于STM32的內(nèi)存管理實(shí)驗(yàn)
光說(shuō)不練都是假把式,那么接下來(lái)就行內(nèi)存管理的實(shí)際操作,基于STM32,使用RTT&正點(diǎn)原子聯(lián)合出品潘多拉開(kāi)發(fā)板,實(shí)現(xiàn)兩個(gè)實(shí)驗(yàn),分別是內(nèi)存堆管理實(shí)驗(yàn)和內(nèi)存池實(shí)驗(yàn)。
1、內(nèi)存堆管理實(shí)驗(yàn)
(1)實(shí)現(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返回值:無(wú)22備注:無(wú)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,開(kāi)機(jī)按下3次KEY0,如下現(xiàn)象,會(huì)打印出申請(qǐng)到內(nèi)存的地址,2秒后釋放內(nèi)存:
2、內(nèi)存池實(shí)驗(yàn)
(1)實(shí)現(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返回值:無(wú) 26備注:無(wú) 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,開(kāi)機(jī),打印創(chuàng)建mempool成功信息,連續(xù)按3次KEY1,打印如下信息,包括申請(qǐng)到內(nèi)存的地址,2秒后釋放內(nèi)存:
參考文獻(xiàn):
1、[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)—基于STM32》
2、《RT-THREAD 編程指南》
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4378瀏覽量
64609 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14534 -
RT-Thread
+關(guān)注
關(guān)注
32文章
1393瀏覽量
41725
原文標(biāo)題:社區(qū)新人的RT-Thread學(xué)習(xí)筆記8——內(nèi)存管理
文章出處:【微信號(hào):RTThread,微信公眾號(hào):RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
C語(yǔ)言內(nèi)存知識(shí)總結(jié):memset函數(shù)和calloc函數(shù)
如何使用LWIP標(biāo)準(zhǔn)C庫(kù)對(duì)內(nèi)存堆進(jìn)行操作?
請(qǐng)問(wèn)固件庫(kù)的函數(shù)可以直接調(diào)用嗎?
在標(biāo)準(zhǔn)的c庫(kù)函數(shù)中printf進(jìn)行重定向
怎樣去實(shí)現(xiàn)嵌入式裸機(jī)內(nèi)存動(dòng)態(tài)管理呢
AVR單片機(jī)C語(yǔ)言庫(kù)

CodeVisionAVR C語(yǔ)言庫(kù)函數(shù)介紹
c++標(biāo)準(zhǔn)庫(kù)手冊(cè)
MicroBlaze:malloc 函數(shù)動(dòng)態(tài)分配內(nèi)存溢出

標(biāo)準(zhǔn)函數(shù)是什么_標(biāo)準(zhǔn)函數(shù)有哪些

標(biāo)準(zhǔn)C函數(shù)庫(kù)的用法

評(píng)論