0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

不直接使用C標(biāo)準(zhǔn)庫中的內(nèi)存管理函數(shù)的原因

RTThread物聯(lián)網(wǎng)操作系統(tǒng) ? 來源:lp ? 2019-04-05 15:37 ? 次閱讀

前言

本文講RT-Thread的內(nèi)存管理,包括為何不使用C標(biāo)準(zhǔn)庫的內(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)庫中的內(nèi)存管理函數(shù)的原因

很多人會有疑問,為什么不直接使用 C 標(biāo)準(zhǔn)庫中的內(nèi)存管理函數(shù)呢?在電腦中我們可以用malloc()和 free()這兩個函數(shù)動態(tài)的分配內(nèi)存和釋放內(nèi)存。但是,在嵌入式實(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í)行的時間可能都不一樣。

5、它們有可能產(chǎn)生碎片。

6、這兩個函數(shù)會使得鏈接器配置得復(fù)雜。

7、如果允許堆空間的生長方向覆蓋其他變量占據(jù)的內(nèi)存,它們會成為 debug 的災(zāi)難 。

二、內(nèi)存管理的功能特點(diǎn)

1、分配內(nèi)存的時間必須是確定的。一般內(nèi)存管理算法是根據(jù)需要存儲的數(shù)據(jù)的長度在內(nèi)存中去尋找一個與這段數(shù)據(jù)相適應(yīng)的空閑內(nèi)存塊,然后將數(shù)據(jù)存儲在里面。而尋找這樣一個空閑內(nèi)存塊所耗費(fèi)的時間是不確定的,因此對于實(shí)時系統(tǒng)來說,這就是不可接受的,實(shí)時系統(tǒng)必須要保證內(nèi)存塊的分配過程在可預(yù)測的確定時間內(nèi)完成,否則實(shí)時任務(wù)對外部事件的響應(yīng)也將變得不可確定。

2、隨著內(nèi)存不斷被分配和釋放,整個內(nèi)存區(qū)域會產(chǎn)生越來越多的碎片(因?yàn)樵谑褂眠^程中,申請了一些內(nèi)存,其中一些釋放了,導(dǎo)致內(nèi)存空間中存在一些小的內(nèi)存塊,它們地址不連續(xù),不能夠作為一整塊的大內(nèi)存分配出去),系統(tǒng)中還有足夠的空閑內(nèi)存,但因?yàn)樗鼈兊刂凡⒎沁B續(xù),不能組成一塊連續(xù)的完整內(nèi)存塊,會使得程序不能申請到大的內(nèi)存。對于通用系統(tǒng)而言,這種不恰當(dāng)?shù)膬?nèi)存分配算法可以通過重新啟動系統(tǒng)來解決 (每個月或者數(shù)個月進(jìn)行一次),但是對于那些需要常年不間斷地工作于野外的嵌入式系統(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、程序運(yùn)行之前,需要有文件實(shí)體被燒錄到 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 在上電啟動之后默認(rèn)從 Flash 啟動,啟動之后會將 RW 段中的 RW-data(初始化的全局變量)搬運(yùn)到 RAM 中,但不會搬運(yùn) 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)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(zhàn)—基于STM32》)

(2)當(dāng)需要分配內(nèi)存塊時,將從這個大的內(nèi)存塊上分割出相匹配的內(nèi)存塊,然后把分割出來的空閑內(nèi)存塊還回給堆管理系統(tǒng)中。每個內(nèi)存塊都包含一個管理用的數(shù)據(jù)頭,通過這個頭把使用塊與空閑塊用雙向鏈表的方式鏈接起來(內(nèi)存塊鏈表)。

小內(nèi)存管理工作機(jī)制圖(來源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(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ù)塊,實(shí)質(zhì)也是一個內(nèi)存保護(hù)字:如果這個區(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é)時,它滿足分配的要求。因?yàn)檫@個內(nèi)存塊比較大,分配器將把此內(nèi)存塊進(jìn)行拆分,余下的內(nèi)存塊(52字節(jié))繼續(xù)留在 lfree鏈表中,在每次分配內(nèi)存塊前,都會留出 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)示意圖(來源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(zhàn)—基于STM32》)

分配 64 字節(jié)后的鏈表結(jié)構(gòu)(來源[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(zhàn)—基于STM32》)

(5)釋放時則是相反的過程,分配器會查看前后相鄰的內(nèi)存塊是否空閑,如果空閑則合并成一個大的空閑內(nèi)存塊。

3、slab 管理算法

RT-Thread 的 slab 分配器是在 DragonFly BSD 創(chuàng)始人 Matthew Dillon 實(shí)現(xiàn)的 slab 分配器基礎(chǔ)上,針對嵌入式系統(tǒng)優(yōu)化的內(nèi)存分配算法。最原始的 slab 算法是 Jeff Bonwick 為 Solaris 操作系統(tǒng)而引入的一種高效內(nèi)核內(nèi)存分配算法。RT-Thread 的 slab 分配器實(shí)現(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é)點(diǎn)必然有空閑塊存在(否則它就不應(yīng)該放在這個鏈表中),那么就取相應(yīng)的空閑塊。如果分配完成后,zone 中所有空閑內(nèi)存塊都使用完畢,那么分配器需要把這個 zone 節(jié)點(diǎn)從鏈表中刪除。

(2)內(nèi)存釋放:分配器需要找到內(nèi)存塊所在的 zone 節(jié)點(diǎn),然后把內(nèi)存塊鏈接到 zone 的空閑內(nèi)存塊鏈表中。如果此時zone 的空閑鏈表指示出 zone 的所有內(nèi)存塊都已經(jīng)釋放,即 zone 是完全空閑的,那么當(dāng) zone 鏈表中全空閑 zone 達(dá)到一定數(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 工作機(jī)制如下圖所示,首先將多塊內(nèi)存加入memheap_item鏈表進(jìn)行粘合。當(dāng)分配內(nèi)存塊時,會先從默認(rèn)內(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不同,確認(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來決定是否使用memheap 管理算法,在rtconfig.h打開關(guān)閉相關(guān)宏來實(shí)現(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之后,實(shí)現(xiàn)的算法不同,比如rt_malloc()函數(shù)的實(shí)現(xiàn)。

5、內(nèi)存堆配置和初始化

(1)在使用內(nèi)存堆時,必須要在系統(tǒng)初始化的時候進(jìn)行堆的初始化,可以通過下面的函數(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)初始化的時候進(jìn)行堆內(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?。谶M(jìn)行重新分配內(nèi)存塊時,原來的內(nèi)存塊數(shù)據(jù)保持不變(縮小的情況下,后面的數(shù)據(jù)被自動截?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)存地址的多個內(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)存分配完成后進(jìn)行回調(diào)?;卣{(diào)時,會把分配到的內(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ù),設(shè)置的鉤子函數(shù)會在調(diào)用內(nèi)存釋放完成前進(jìn)行回調(diào)?;卣{(diào)時,釋放的內(nèi)存塊地址會做為入口參數(shù)傳遞進(jìn)去(此時內(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)存塊,非常靈活和方便。但其也存在明顯的缺點(diǎn):一是分配效率不高,在每次分配時,都要空閑內(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)存塊分配機(jī)制

(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)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(zhàn)—基于STM32》)

(2)內(nèi)核負(fù)責(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)存池?cá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)存池?cái)?shù)據(jù)區(qū)域大小。block_size:內(nèi)存塊容量。

(B)返回值:

RT_EOK:初始化成功。RT_ERROR:失敗。

注意:內(nèi)存池塊個數(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é);則申請的內(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)存塊,或等待時間到達(dá)。

(B)返回值:

分配的內(nèi)存塊地址:成功。RT_NULL:失敗。

(6)釋放內(nèi)存塊:任何內(nèi)存塊使用完后都必須被釋放,否則會造成內(nèi)存泄露。首先通過需要被釋放的內(nèi)存塊指針計(jì)算出該內(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)存管理實(shí)驗(yàn)

光說不練都是假把式,那么接下來就行內(nèi)存管理的實(shí)際操作,基于STM32,使用RTT&正點(diǎn)原子聯(lián)合出品潘多拉開發(fā)板,實(shí)現(xiàn)兩個實(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返回值:無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,開機(jī)按下3次KEY0,如下現(xiàn)象,會打印出申請到內(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返回值:無 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,開機(jī),打印創(chuàng)建mempool成功信息,連續(xù)按3次KEY1,打印如下信息,包括申請到內(nèi)存的地址,2秒后釋放內(nèi)存:

參考文獻(xiàn):

1、[野火?]《RT-Thread 內(nèi)核實(shí)現(xiàn)與應(yīng)用開發(fā)實(shí)戰(zhàn)—基于STM32》

2、《RT-THREAD 編程指南》

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4237

    瀏覽量

    61965
  • 內(nèi)存管理
    +關(guān)注

    關(guān)注

    0

    文章

    167

    瀏覽量

    14099
  • RT-Thread
    +關(guān)注

    關(guān)注

    31

    文章

    1239

    瀏覽量

    39430

原文標(biāo)題:社區(qū)新人的RT-Thread學(xué)習(xí)筆記8——內(nèi)存管理

文章出處:【微信號:RTThread,微信公眾號:RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    C語言內(nèi)存知識總結(jié):memset函數(shù)和calloc函數(shù)

    memset(翻譯:清零)是計(jì)算機(jī)C/C++語言初始化函數(shù)。作用是將某一塊內(nèi)存的內(nèi)容全部設(shè)置
    發(fā)表于 10-24 10:40 ?1012次閱讀

    如何使用LWIP標(biāo)準(zhǔn)C內(nèi)存堆進(jìn)行操作?

    公司用的RCT6型號,空間不大,想咨詢下大牛們?nèi)绻蚁胧褂?b class='flag-5'>標(biāo)準(zhǔn)C使用內(nèi)存堆,標(biāo)準(zhǔn)C需要初始化?
    發(fā)表于 08-21 22:25

    請問固件函數(shù)可以直接調(diào)用嗎?

    ;)。。。。。。。。。。。。。。。。。。。。。。想請問一下,固件庫里的函數(shù)是不是直接調(diào)用就可以?(我是直接調(diào)用的,.H文件也加進(jìn)去了),出現(xiàn)上面的錯誤是不是MDK版本與固件的版本號
    發(fā)表于 03-05 04:35

    標(biāo)準(zhǔn)c函數(shù)printf進(jìn)行重定向

    目錄點(diǎn)擊這里查看所有博文2.1、C代碼??由于某些原因我們的elua開源項(xiàng)目中不能采用標(biāo)準(zhǔn)c函數(shù)
    發(fā)表于 08-20 06:54

    怎樣去實(shí)現(xiàn)嵌入式裸機(jī)內(nèi)存動態(tài)管理

    嵌入式裸機(jī)內(nèi)存動態(tài)管理的實(shí)現(xiàn)與講解(一)C標(biāo)準(zhǔn)自帶了malloc和free,為啥還要自己實(shí)現(xiàn)?標(biāo)準(zhǔn)
    發(fā)表于 12-17 07:02

    AVR單片機(jī)C語言

    AVR單片機(jī)C語言庫內(nèi)容目錄: .1. Character Type Functions - 字符類型函數(shù)2. Standard C Input/Output Functions - 標(biāo)準(zhǔn)
    發(fā)表于 04-22 10:14 ?0次下載
    AVR單片機(jī)<b class='flag-5'>C</b>語言<b class='flag-5'>庫</b>

    CodeVisionAVR C語言庫函數(shù)介紹

    CodeVisionAVR C語言庫函數(shù)介紹 目錄:1. Character Type Functions - 字符類型函數(shù)2. Standard C Input/Output Fu
    發(fā)表于 04-22 10:17 ?108次下載

    c++標(biāo)準(zhǔn)手冊

    C++的標(biāo)準(zhǔn)手冊,新手學(xué)習(xí)的好資料,同時也是開發(fā)人員的必備手冊。里面涵蓋了標(biāo)準(zhǔn)C++的所有的庫函數(shù)
    發(fā)表于 11-03 14:05 ?43次下載

    MicroBlaze:malloc 函數(shù)動態(tài)分配內(nèi)存溢出

    首先說明一點(diǎn),MicroBlaze C函數(shù)庫支持標(biāo)準(zhǔn)內(nèi)存管理函數(shù),如malloc(),call
    發(fā)表于 02-11 11:43 ?1801次閱讀
    MicroBlaze:malloc <b class='flag-5'>函數(shù)</b>動態(tài)分配<b class='flag-5'>內(nèi)存</b>溢出

    linux_C函數(shù)庫中文手冊

    linux_C函數(shù)庫中文手冊linux_C函數(shù)庫中文手冊
    發(fā)表于 03-20 10:42 ?14次下載

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

    在程序設(shè)計(jì),常將一些常用的功能模塊編寫成的函數(shù)放在函數(shù)庫供公共選用,一般稱為標(biāo)準(zhǔn)函數(shù)。程序是
    的頭像 發(fā)表于 03-02 10:24 ?9125次閱讀
    <b class='flag-5'>標(biāo)準(zhǔn)</b><b class='flag-5'>函數(shù)</b>是什么_<b class='flag-5'>標(biāo)準(zhǔn)</b><b class='flag-5'>函數(shù)</b>有哪些

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

    C標(biāo)準(zhǔn)函數(shù)庫是所有符合標(biāo)準(zhǔn)的頭文件的集合,以及常用的函數(shù)庫實(shí)現(xiàn)程序,例如I/O 輸入輸出和字符串控制。不像 COBOL、Fortran 和
    的頭像 發(fā)表于 03-02 11:27 ?5051次閱讀
    <b class='flag-5'>標(biāo)準(zhǔn)</b><b class='flag-5'>C</b><b class='flag-5'>函數(shù)庫</b>的用法

    C語言常用標(biāo)準(zhǔn)分享

    有很多工程師喜歡自己封裝一些標(biāo)準(zhǔn)已有的函數(shù),其實(shí)自己封裝的函數(shù),并不一定比標(biāo)準(zhǔn)好,有時候反而
    的頭像 發(fā)表于 02-20 18:08 ?945次閱讀

    FreeRTOS內(nèi)存管理簡介

    ,比如任務(wù)創(chuàng)建函數(shù) xTaskCreateStatic(),使用此函數(shù)創(chuàng)建任務(wù)的時候需要由用戶定義任務(wù)堆棧,我們討論這種靜態(tài)方法。 使用動態(tài)內(nèi)存
    的頭像 發(fā)表于 07-30 10:26 ?566次閱讀

    C語言中的動態(tài)內(nèi)存管理講解

    本章將講解 C 的動態(tài)內(nèi)存管理。C 語言為內(nèi)存的分配和管理
    的頭像 發(fā)表于 02-23 14:03 ?310次閱讀
    <b class='flag-5'>C</b>語言中的動態(tài)<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>講解