rt-thread的內(nèi)存管理分析
掃描二維碼
隨時隨地手機看文章
rt-thread的內(nèi)存管理分析
-
1.概述
-
2.靜態(tài)內(nèi)存與動態(tài)內(nèi)存
-
3.小內(nèi)存管理
-
4.slab內(nèi)存管理
-
4.1 rt-thread上slab基本介紹
-
4.2 rt-thread上slab內(nèi)存的管理
-
5.memheap 管理算法
-
6.內(nèi)存池管理算法
-
7.總結(jié)
1.概述
內(nèi)存是計算機中十分重要的資源。隨著芯片性能的提升,容量的變大,內(nèi)存資源的管理顯得非常重要。內(nèi)存管理是操作系統(tǒng)中一個基本功能,一般操作系統(tǒng)的功能可以概括為五個部分:處理器管理、內(nèi)存管理、任務(wù)管理、I/O設(shè)備管理、文件管理。對于嵌入式操作系統(tǒng),一個好的內(nèi)存管理策略,將大大提高系統(tǒng)的性能,對系統(tǒng)穩(wěn)定性也至關(guān)重要。
本文主要從RT-Thread的內(nèi)存管理策略的角度出發(fā),梳理一下目前RT-Thread系統(tǒng)中的內(nèi)存管理,同時從實際應(yīng)用的角度出發(fā),選擇合適的方案進行內(nèi)存管理。
2.靜態(tài)內(nèi)存與動態(tài)內(nèi)存
一般來說,靜態(tài)內(nèi)存就是系統(tǒng)在啟動之前,已經(jīng)獲得了系統(tǒng)運行的所有內(nèi)存。不需要在程序運行的時候,另外進行內(nèi)存的分配。這種方式使用內(nèi)存,一般實時性比較強,省去了單獨分配內(nèi)存的時間。但是由于是靜態(tài)內(nèi)存,使用的效率比較低下,不用單獨進行管理。
動態(tài)內(nèi)存就是通過分配(malloc)和釋放(free)對內(nèi)存進行一定的管理,這種內(nèi)存的使用效率比較高,但反復(fù)的申請與釋放,容易產(chǎn)生內(nèi)存碎片的問題。
對于RT-Thread的內(nèi)存管理方式,主要從小內(nèi)存管理、slab、memheap以及內(nèi)存池這四種內(nèi)存管理策略上去分析對比,從而選擇最佳的內(nèi)存管理策略。
3.小內(nèi)存管理
rt-thread的小內(nèi)存管理算法是一種比較簡單的內(nèi)存分配管理算法。應(yīng)用的場景在內(nèi)存在1MB以下使用比較合適。其原理是當(dāng)需要分配時,從內(nèi)存堆上分配出一塊內(nèi)存供系統(tǒng)使用,后面需要的時候,接著從后面空閑的內(nèi)存區(qū)域進行分配。
當(dāng)調(diào)用rt_system_heap_init(RT_HW_HEAP_BEGIN, RT_HW_HEAP_END);會分配出一段堆空間,其中l(wèi)free指向這段堆空間的頭部位置。
此時分配一個32字節(jié)的數(shù)據(jù),會首先申請一個內(nèi)存的頭部信息,這個頭部信息為12字節(jié)。
heap_mem一般為12字節(jié)。
#define HEAP_MAGIC 0x1ea0 struct heap_mem { /* magic and used flag */ rt_uint16_t magic; rt_uint16_t used; #ifdef ARCH_CPU_64BIT rt_uint32_t resv; #endif rt_size_t next, prev; #ifdef RT_USING_MEMTRACE #ifdef ARCH_CPU_64BIT rt_uint8_t thread[8]; #else rt_uint8_t thread[4]; /* thread name */ #endif #endif };
其中magic為魔數(shù),為0x1ea0,英文為heap。rt_size_t數(shù)據(jù)類型為rt_ubase_t。在32位程序中為4字節(jié)。所以合起來,頭部字節(jié)的大小為12字節(jié)。此時依次分配50字節(jié)和64字節(jié)。內(nèi)存布局如下所示:
由于lfree永遠指向第一個空閑的內(nèi)存的起始地址,所以此時lfree在為分配的內(nèi)存塊后面。依次向后去分配合適的內(nèi)存塊大小。
此時如果釋放了一個50字節(jié)的內(nèi)存塊。此時的內(nèi)存布局如下:
由于空間被釋放,所以導(dǎo)致內(nèi)存的中間有一塊空閑的區(qū)域。此時如果要申請25字節(jié),那么可以在空閑的這個區(qū)域里面找到。但是如果此時申請100字節(jié)的時候,那么導(dǎo)致空閑的區(qū)域的內(nèi)存不夠了,接著向后面去找空閑的大內(nèi)存片段。
此時,會向后面的大區(qū)域去申請內(nèi)存塊,導(dǎo)致前面的內(nèi)存區(qū)域被空出來了。如果在內(nèi)存中間有很多這種小的空余的片段出現(xiàn),那么一定會導(dǎo)致內(nèi)存碎片的問題。
這種方式內(nèi)存使用的比較簡單,適合內(nèi)存比較小的情況下使用,并且操作內(nèi)存的申請和釋放不要太頻繁的情況下去使用比較理想。一般在內(nèi)存為1M以下的情況下使用這種算法比較理想。
4.slab內(nèi)存管理
4.1 rt-thread上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分配器的時候,需要理解兩個概念:ZONE、CHUNK。
CHUNK:
chunk是一段固定大小的空間,一般來說,slab分配器是支持72種不同大小的chunk。數(shù)據(jù)量分別如下:
8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128、144、160、176、192、208、224、240、256、288、320、352、384、416、448、480、512、576、640、704、770、834、898、962、1024、1152、1280、1408、1536、1664、1792、1920、2048、2304、2560、2816、3072、3328、3584、3840、4096、4608、5120、5632、6144、6656、7168、7680、8192、9216、10240、11264、12288、13312、14336、15360、16384
一般來說,slab分配器只會去尋找上述可以分配的空間,如果分配的chunk大小不相等,那么會通過zoneindex函數(shù)去找到最合適的可以分配的內(nèi)存的chunk大小。
ZONE:
每個zone有范圍從32k~128K的字節(jié),每個zone里都包含相同大小的chunk。
#define ZALLOC_MIN_ZONE_SIZE (32 * 1024) /* minimum zone size */ #define ZALLOC_MAX_ZONE_SIZE (128 * 1024) /* maximum zone size */
一般來說,在初始化的時候,進行void rt_system_heap_init(void *begin_addr, void *end_addr)操作的時候,會根據(jù)給系統(tǒng)的heap空間,動態(tài)的調(diào)整zone的大小。
/* calculate zone size */ zone_size = ZALLOC_MIN_ZONE_SIZE;//32K while (zone_size < ZALLOC_MAX_ZONE_SIZE && (zone_size << 1) < (limsize / 1024)) zone_size <<= 1;
根據(jù)上述的調(diào)整方式,可以算出,當(dāng)heap空間在64M及以下時,zone的大小都為32K。當(dāng)系統(tǒng)的heap空間在256M及以上時,zone的大小為最大128K。這個值在兩者中間時是動態(tài)進行調(diào)整的。
按照上述的介紹,可以看出這兩者的關(guān)系如圖所示:
4.2 rt-thread上slab內(nèi)存的管理
采用slab算法的時候,主要有兩種情況:
1.底層的頁分配器
2.上層的slab分配器
其中頁分配器是用page分配的一個片段。在宏定義中RT_MM_PAGE_SIZE表示page大小,一般定義的大小為4K。
初始化的時候,會將zone_array[NZONES]會被置為空,表示沒有可以用的zone。例如此時申請一個30字節(jié)的數(shù)據(jù)空間。當(dāng)調(diào)用rt_malloc(30),那么會通過zoneindex函數(shù)找到一個合適的32字節(jié)的chunk。找到zone的下標(biāo)為3,查詢到zone_array[3]為空。
(1)當(dāng)發(fā)現(xiàn)zone_array[3]為空時,會通過rt_page_alloc去分配page。假如動態(tài)調(diào)整的zone大小為128K。需要分配出32個page,一個page的大小為4K。32*4K=128K。
(2)此時當(dāng)前zone中的內(nèi)存布局如下:
由于第一次分配時,可以分配到32字節(jié)的chunk1,并標(biāo)記chunk1被使用,然后下次申請符合這個chunk條件的時候直接可以申請到chunk2。如果中間被釋放了,那么就可以被其他申請到了。
如果該zone上的所有的chunk都申請完了,那么可以申請同等類型的zone,繼續(xù)這樣操作。
使用zone管理,可以從一定意義上避免內(nèi)存片段的問題,但是內(nèi)存管理起來比較的麻煩,比較耗時。適合內(nèi)存比較大,并且需要頻繁申請和釋放內(nèi)存的時候使用。
5.memheap 管理算法
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 分配。
這種內(nèi)存分配的特點在于可以把不連續(xù)的空間變得連續(xù)起來。具體的使用方法就是當(dāng)開啟了RT_USING_MEMHEAP_AS_HEAP這個宏定義,第一次需要使用rt_system_heap_init分配堆空間,然后需要用戶定義一個struct rt_memheap結(jié)構(gòu)體靜態(tài)變量,然后再使用rt_memheap_init函數(shù)初始化。這樣可以把兩端不連續(xù)的空間作為一塊連續(xù)的空間來管理。具體來說,比如sram與sdram的地址管理。
關(guān)于內(nèi)存的管理部分,可以查看小內(nèi)存管理部分。
6.內(nèi)存池管理算法
在使用操作系統(tǒng)做業(yè)務(wù)邏輯時,往往有這樣一種需求。比如做通信協(xié)議,或者使用動態(tài)內(nèi)存比較多的情況。這時候可以在內(nèi)存堆上單獨分配一塊內(nèi)存作為具體業(yè)務(wù)邏輯的內(nèi)存池使用。
做內(nèi)存管理的時候,調(diào)用下面函數(shù)去創(chuàng)建一個內(nèi)存池。
rt_mp_t rt_mp_create(const char* name, rt_size_t block_count, rt_size_t block_size);
內(nèi)存池中會分配出一些列的內(nèi)存塊。當(dāng)實際使用時,如果申請內(nèi)存池中的數(shù)據(jù)申請不到了,那么無法分配,會使得線程掛起,等待釋放內(nèi)存池中的數(shù)據(jù)塊時,會得到內(nèi)存塊。
采用內(nèi)存池的好處是自己的任務(wù)用自己的內(nèi)存,不會造成頻繁分配釋放造成的內(nèi)存碎片問題。
7.總結(jié)
內(nèi)存管理是操作系統(tǒng)中比較重要的一個部分,使用得當(dāng),會大大提高系統(tǒng)軟件的質(zhì)量。rt-thread提供的小內(nèi)存管理、slab、memheap、內(nèi)存池這些內(nèi)存管理策略,一方面針對不同大小,不同用途的內(nèi)存做了區(qū)分,可以選擇合適的內(nèi)存管理策略對內(nèi)存進行使用。想要使用好rtos操作系統(tǒng),需要不斷的思考內(nèi)存的使用方式,靜態(tài)內(nèi)存、動態(tài)內(nèi)存、全局變量、局部變量、臨界區(qū)等等、在注意這些問題的條件下去做設(shè)計,才能設(shè)計出好的系統(tǒng)架構(gòu),才能做出更好的穩(wěn)定可靠的產(chǎn)品。
- END -





