FreeRTOS的Heap1~Heap5有什么區(qū)別
有一天傍晚快下班的時候,我問她,項目上的事自己能hold得住嗎?有問題隨時可以找我哦。
她想了想,欲言又止。
還沒等她說話,我接著說,是遇到什么問題了嗎?
“其實是這樣的,我是遇到了OS上的一些疑惑問題,但也不是很要緊??茨隳敲疵?,不好意思打擾你……”“哦……沒事啊,什么問題呢?”她說,自從上次我跟她分析了OS占用內(nèi)存的問題(見《妹子告訴我她被欺負(fù)了》)后,她就對OS的內(nèi)存分配有不少疑惑,很想將它搞個一清二楚。這不,她現(xiàn)在的問題是,FreeRTOS里的Heap1~Heap5有什么區(qū)別?
心想:額……這次完犢子了,我還真沒怎么研究過這幾個Heap的區(qū)別。但是我不能在她面前說不行啊。
突然,心生一計,若有其事淡定地跟她說,這是個好問題,得好好探討下。然后我順手拿出手機看了看時間,問她,明天晚上有空么?
“有。”她回答很干脆。
這次,我得換個方式跟她講解這個Heap,不能像以前那樣了……
2. 熬夜研究heap于是,我背了筆記本回家,開始鉆研這個heap的用法,得用豐富的知識和強有力的技能征服妹子。
首先,我將這幾個heap的c文件移植到PC運行環(huán)境,用自動化手段分析其使用情況。我寫了個main.c文件,分別與heap_1.c~heap_5.c進行編譯。
主要思路是,向heap申請并釋放內(nèi)存,看看其地址占用空間,就一目了然了。
addr1 = alloc_x(1); addr2 = alloc_x(2); addr4 = alloc_x(4); addr8 = alloc_x(8); addr16 = alloc_x(16); addr32 = alloc_x(32); free_x(addr1); addr1 = alloc_x(1); free_x(addr4); addr2 = alloc_x(2); free_x(addr8); addr4 = alloc_x(4); addr4 = alloc_x(4); free_x(addr16); addr1 = alloc_x(1); addr2 = alloc_x(2); addr4 = alloc_x(4); addr8 = alloc_x(8); addr16 = alloc_x(16); addr1 = alloc_x(1); free_x(addr2); free_x(addr4); free_x(addr8); free_x(addr16); addr6 = alloc_x(6); addr10 = alloc_x(10);這里面的alloc_x和free_x分別調(diào)用了pvPortMalloc和vPortFree,并打印一些信息。
void* alloc_x(size_t size){ void* addr = pvPortMalloc(size); size_t res = xPortGetFreeHeapSize(); printf("Alloc : addr=0x%p, size:0x%02X, remain:0x%04X\r\n", addr, size, res); return addr;} void free_x(void* addr){ vPortFree( addr); size_t res = xPortGetFreeHeapSize(); printf("Free : addr=0x%p, remain:0x%04X\r\n", addr, res);}總以為很順利,誰知道,這heap依賴的頭文件一層套一層……一狠心,大刀闊斧屏蔽了頭文件里面的內(nèi)容,手動添加heap需要的依賴項。
好不容易,編譯通過了,在搞heap_1.c這個的時候,在運行到vPortFree,就掛了!查了下代碼,發(fā)現(xiàn)heap_1原來是不允許free內(nèi)存的。
void vPortFree( void * pv ){ /* Memory cannot be freed using this scheme. See heap_2.c, heap_3.c and * heap_4.c for alternative implementations, and the memory management pages of * https://www.FreeRTOS.org for more information. */ ( void ) pv; /* Force an assert as it is invalid to call this function. */ configASSERT( pv == NULL );}因為它只實現(xiàn)了pvPortMalloc而沒實現(xiàn)vPortFree,所以這個vPortFree是沒用的,僅僅是個兼容性接口而已。
接著,把后面的heap_2~heap_5的編譯運行問題一個個搞定了。
Note:我把整個工程打包好了,關(guān)注公眾號“嵌入式軟件實戰(zhàn)派”,在后臺回復(fù)“heap”即可獲得下載鏈接。
通過輸出的log信息,我還細(xì)心地做了個內(nèi)存分配動圖。
搞完這一波,看了下時間,竟然是晚上12點了。
然后,我居然興奮得睡不著,想著怎么跟她講解這個技術(shù)問題。
第二天,突發(fā)奇想,約她在公司附近的咖啡館!她居然爽快地答應(yīng)了??!
3. 促膝長談晚上下班,我們一起到了咖啡廳,就像一對小……伙伴程序員,背著筆記本電腦。跟異性來這種地方,我還有些不好意思。
為了避免尷尬,單刀直入,打開筆記本,把準(zhǔn)備好的材料跟她一一講解。
“師兄,我們不點個咖啡嗎?”
“呵呵呵……其實……你喜歡和什么?”
……
她說晚上不喝咖啡,于是我給她點了一杯奶茶,然后迫不及待地跟她將這個heap的情況。
一開始,我用官方的文檔解釋給她說明了下總體的情況:
我問她,“你知道FreeRTOS為什么不用C庫中的malloc()和free()函數(shù)來分配和釋放內(nèi)存嗎?”
“因為,不安全?!?
我聽她這么肯定的說,想著她肯定是對RTOS有很深入的認(rèn)識的。
是的,C庫中的malloc()和free()函數(shù):
“所以,F(xiàn)reeRTOS的幾個heap是為了解決這幾個問題的。”1. they are not always available on embedded systems,
2. they take up valuable code space,
3. they are not thread safe, and
4. they are not deterministic (the amount of time taken to execute the function will differ from call to call)
FreeRTOS
她點了點頭,然后低頭喝了一口奶茶。我看她那手捧奶茶的溫柔,恰好點綴了窗外的夕陽……多想多停留在這一刻,但我怕她看到我在看她,接著說:“那么,這幾個heap有什么區(qū)別呢?總的來說是這樣的?!?br />
“嗯!”她輕聲說。其實她已經(jīng)看過了這些解釋了,但是不是很理解,她想深入一點的,深入一點的學(xué)習(xí)。“‘Talk is cheap’,那么,我們直接上代碼吧。”heap_1 - 最簡單的實現(xiàn)形式,不支持Free內(nèi)存;
heap_2 - 允許內(nèi)存Free,但不會合并free的內(nèi)存塊;
heap_3 - 是malloc() 和free() 的抽象層,多加了線程安全措施;
heap_4 - 合并free的塊,避免碎片
heap_5 - 類似heap_4,增加了塊內(nèi)存段操作。
她呵呵大笑。笑的樣子也特別可愛。
“我們就按照官方的解釋,來驗證下。我寫了段測試代碼,你看看這段log就知道了?!?br />
Base Addr: 0x407080Alloc : addr=0x00407080, size:0x01, remain:0x27F0Alloc : addr=0x00407088, size:0x02, remain:0x27E8Alloc : addr=0x00407090, size:0x04, remain:0x27E0Alloc : addr=0x00407098, size:0x08, remain:0x27D8Alloc : addr=0x004070A0, size:0x10, remain:0x27C8Alloc : addr=0x004070B0, size:0x20, remain:0x27A8Free : addr=0x00407080, remain:0x27A8Alloc : addr=0x004070D0, size:0x01, remain:0x27A0Free : addr=0x00407090, remain:0x27A0Alloc : addr=0x004070D8, size:0x02, remain:0x2798Free : addr=0x00407098, remain:0x2798Alloc : addr=0x004070E0, size:0x04, remain:0x2790Alloc : addr=0x004070E8, size:0x04, remain:0x2788Free : addr=0x004070A0, remain:0x2788Alloc : addr=0x004070F0, size:0x01, remain:0x2780Alloc : addr=0x004070F8, size:0x02, remain:0x2778Alloc : addr=0x00407100, size:0x04, remain:0x2770Alloc : addr=0x00407108, size:0x08, remain:0x2768Alloc : addr=0x00407110, size:0x10, remain:0x2758Alloc : addr=0x00407120, size:0x01, remain:0x2750Free : addr=0x004070F8, remain:0x2750Free : addr=0x00407100, remain:0x2750Free : addr=0x00407108, remain:0x2750Free : addr=0x00407110, remain:0x2750Alloc : addr=0x00407128, size:0x06, remain:0x2748Alloc : addr=0x00407130, size:0x0A, remain:0x2738她準(zhǔn)備站起來,把頭湊過來看,我把筆記本轉(zhuǎn)過去給她。然后我繼續(xù)解釋說,“這個Free是沒有意義的”。
“哦?”她似乎有些不相信,然后試圖口算這些地址值。
“我再給個圖你看看?!?br />
“哇哦……這顏色是什么意思,代表申請的內(nèi)存塊嗎?怎么還有這么多空隙的?”她低聲問道,似乎有點好奇,又有點不是很相信的樣子。
“是的,要顏色的地方是表示內(nèi)存申請占有的地方,至于空隙嘛,我一會再跟你解釋下?!蔽医又f,“歪著頭怎么看呢!要不坐過來我這邊,我還有一個動圖?!?br />
“厲害!這都可以做出來,走心了……”
“你仔細(xì)看,其實這個vPortFree執(zhí)行的地方,內(nèi)存是沒有變化的?!?br /> “真的哦,heap_1是不能釋放內(nèi)存的?!蓖蝗唬矣X得我很有成就感。
“那么,為什么會有這么多空隙的呢?我們看看這段代碼就清楚了。”
void * pvPortMalloc( size_t xWantedSize ){ void * pvReturn = NULL; static uint8_t * pucAlignedHeap = NULL; /* Ensure that blocks are always aligned. */ { if( xWantedSize & portBYTE_ALIGNMENT_MASK ) { /* Byte alignment required. Check for overflow. */ if ( (xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) )) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); } else { xWantedSize = 0; } } }“哦……原來最大多占了一個portBYTE_ALIGNMENT的空間?!彼嗽斄艘魂囘@個代碼說。
“所以,官方有說,自從有了static的內(nèi)存分配方法,就很少用這個heap_1了?!?br /> “嗯!”她似乎很滿足的樣子,伸手把奶茶拿過來,接著喝了一口。
我也喝了一口咖啡,突然覺得這個咖啡不苦了。她起身,準(zhǔn)備要坐回去。
“等下,我這還有其他數(shù)據(jù),給你看看heap_2的情況?!蔽艺f后,她慢慢坐了下來。夕陽已慢慢落在了城市的高樓之中,道路上依然車水馬龍,而安靜的咖啡館有種說不出來的溫馨。
“那么heap_2有什么不一樣呢?我們直接看圖吧?!?br />
“咦?怎么有更多的孔隙了?”
“是啊,我也納悶,但看了下源碼,確實會這樣,其中有一段是這樣的。”
/* The wanted size must be increased so it can contain a BlockLink_t * structure in addition to the requested amount of bytes. */ if( ( xWantedSize > 0 ) && ( ( xWantedSize + heapSTRUCT_SIZE ) > xWantedSize ) ) /* Overflow check */ { xWantedSize += heapSTRUCT_SIZE; /* Byte alignment required. Check for overflow. */ if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 ); } else { xWantedSize = 0; } }我接著解釋說,“你看,用heap_2每次申請一段內(nèi)存,都要多占一段heapSTRUCT_SIZE和一段portBYTE_ALIGNMENT 空間?!?br /> “哦,怪不得,我們項目定義了32K的RAM都差不多用完了,我算了下實際的分配,還差很遠(yuǎn),原來問題在這呢!”她似乎有種茅舍頓開的感覺。
“那么heap_3又是怎樣的呢?”她主動問我要圖片來看了。
“我用PC上位機軟件模擬的,heap_3用的是標(biāo)準(zhǔn)庫的malloc()和free(),似乎沒啥規(guī)律,其實沒什么參考價值的。”
“好吧……那heap_4呢?”她似乎有點失落的樣子,而又“肆無忌憚”起來,直問我要圖看。
我不慌不忙打開了這個圖。
“好像跟heap_2一樣的哦……”
“是的,很像。但是,有個很重要的區(qū)別,你看這最后一幀內(nèi)容,特別是最后紫色的這塊。”我特意將heap_2的和heap_4的拿出來對比了下。
“看起來似乎heap_4更省空間……哈哈!”看著這兩個圖的對比,她樂了起來,像個天真的孩子。
“雖然還是很多孔隙,但是free掉的空間是可以合并的,這個heap_4的好處就在這了。”
“是的,師兄你真厲害!”
“哈哈!”
“對了,還有heap_5的呢?”
“哦?跟heap_4的沒啥區(qū)別哦……”這說話拉長的語調(diào),越來越可愛了。
“效果看起來,確實是一樣的,但是heap_5是可以跨不連續(xù)區(qū)域的。官方文檔也是這么說的?!?
“那么,它是怎么跨區(qū)域的呢?”她突然好奇起來。This scheme uses the same first fit and memory coalescence algorithms as heap_4, and allows the heap to span multiple non adjacent (non-contiguous) memory regions.
FreeRTOS
“給你看段代碼吧。其實它多了一個函數(shù)。”
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
“這個const HeapRegion_t * const pxHeapRegions就是定義不同區(qū)域段的。”“怎么分段的呢?”“那,我們看這個官方的例子。”
// HeapRegion_t xHeapRegions[] =// {// { ( uint8_t * ) 0x80000000UL, 0x10000 }, << Defines a block of 0x10000 bytes starting at address 0x80000000// { ( uint8_t * ) 0x90000000UL, 0xa0000 }, << Defines a block of 0xa0000 bytes starting at address of 0x90000000// { NULL, 0 } << Terminates the array.// };// vPortDefineHeapRegions( xHeapRegions ); << Pass the array into vPortDefineHeapRegions().