1. 程式人生 > >STM32移植cJson和FreeRTOS時,cjson解析和建立失敗問題

STM32移植cJson和FreeRTOS時,cjson解析和建立失敗問題

版權宣告:本文為博主原創文章,未經博主允許不得轉載。https://blog.csdn.net/u013184273/article/details/84870672

一,環境:STM32+cJson+FreeRTOS+Heap_4.c

二,FREERTOS的記憶體:Heap_4

FreeRTOS8.0.1記憶體管理的最後一個堆模型Heap_4,貌似是在這一個版本才有的。所以找到的說明幾乎沒有。程式碼的開頭註釋也只是簡單地說了一下實現了pvPortMalloc()和vPortFree()兩個函式,並且能夠對回收的記憶體塊進行合併,減少碎片的出現。(A sample implementation of pvPortMalloc() and vPortFree() that combines (coalescences) adjacent memory blocks as they are freed, and in so doing limits memory fragmentation.)不過經過這一次的剖析之後,發現Heap_4所用的記憶體管理演算法為首次適配法(first fit algorithm)。

和Heap_2一樣,Heap_4先申請了一個數組ucHeap[ configTOTAL_HEAP_SIZE ]作為自己管理的堆空間,同樣也有空閒塊頭結構BlockLink_t來管理空閒塊。但是和Heap_2不一樣的是,Heap_4用了BlockLink_t中xBlockSize的最高一位來標識某個記憶體塊是否處於空閒狀態。所以這就是為什麼會有一個巨集heapBITS_PER_BYTE的出現,而且定義為( ( size_t ) 8 )。這樣一來,每一個分配出去的記憶體塊大小就有限制了。例如,我用的是STM32F103,size_t是定義為unsigned int型別的,32位,可支援到4G的記憶體空間。但是最高1位用來指示空間狀態的話,那就只有31位去標識記憶體塊地址,即只支援到2G的記憶體空間。所以用Heap_4還是有一點點代價的,特別是用在16位或8位的微控制器上。
還是先剖析一下堆空間的初始化過程prvHeapInit()。首先還是先將記憶體堆進行首地址對齊。接下來就是運用xStart和pxEnd來組織整個空閒塊連結串列。要注意的是,xStart是BlockLink_t的一個實體變數,儲存在靜態儲存區,而pxEnd只是BlockLink_t的一個指標,儲存在靜態儲存區中,卻指向了記憶體堆的最後一個BlockLink_t大小的位置上。也就是說,記憶體堆最後的空間是儲存著一個BlockLink_t,用來指示空閒塊連結串列的終結,這是和Heap_2有所不同的地方。下圖說明了初始化流程最終將空閒塊連結串列組織成的樣子。

 

接下來剖析Heap_4的第一個重點:pvPortMalloc()。和以前一樣,分配記憶體之前還是先呼叫vTaskSuspendAll()掛起所有任務,以確保分配記憶體的過程不被中斷。下一步通過判斷pxEnd是否為空來決定是否需要初始化記憶體堆和空閒塊連結串列。因此,初始化之後pxEnd就不為空了,以後再呼叫pvPortMalloc()也因此不再呼叫初始化函式。但是這一個判斷的另一個分支(else分支卻呼叫了一個mtCOVERAGE_TEST_MARKER()的巨集,這個巨集的定義在FreeRTOS.h裡,定義為空。因此目前還不知道這一個巨集具體作用,看名字應該是用來測試什麼的。接下來是判斷使用者申請記憶體大小的最高位是否為0,為0即合法(之前說過,最高位用來標識空閒塊的空閒狀態,因此最高位為1則說明使用者申請的記憶體大小已超出空閒塊的最大大小)。然後還是一個size_t型別的資料與0比較的判斷(雖然這個判斷總為真,但也不知道作者為啥要寫這麼一個判斷,要是有人知道這一個判斷的意途,請告訴我),裡面是增大使用者申請的空間大小以便容納空閒塊塊頭BlockLink_t以及將最終申請的記憶體大小進行對齊。
以上的預處理完成了,開始進入分配演算法的核心了。只要最終申請的空間大小仍在空閒空間大小的範圍內,則進入記憶體的分配。首先遍歷連結串列,找到第1塊能比申請空間大小大的空閒塊,修改空閒塊的資訊,記錄使用者可用的記憶體首地址。接下來,如果分配出去的空閒塊比申請的空間大很多,則將該空閒塊進行分割,把剩餘的部分重新新增到連結串列中。
分配記憶體的主要流程基本結束了,和之前分析的一樣,pvPortMalloc()繼續呼叫除錯巨集traceMALLOC()輸出除錯資訊,恢復所有掛起的任務,並按設定呼叫勾子函式vApplicationMallocFailedHook(),最終把使用者可用的記憶體首地址返回。到這裡整個pvPortMalloc()就結束了。
但是,有一個地方剛剛沒怎麼詳細講,就是把分割出來的空閒塊重新新增到連結串列中的過程。現在來詳細分析一下,這也是Heap_4的一個重點。和Heap_2不同,這一次的prvInsertBlockIntoFreeList()並不是寫成一個巨集,而是寫成了一個函式。進入函式的開始,可以看到,FreeRTOS實際上是將這個空閒塊連結串列裡的所有空閒塊按地址順序排列的。當然,如果不這麼排列,怎麼能將相鄰的空閒塊進行合併呢?將要回收的空閒塊為pxBlockToInsert,這個空閒塊將被插到pxIterator的後面。通過一次連結串列的遍歷,就把pxIterator找出來了。接下來,FreeRTOS先試著將pxIterator和pxBlockToInsert進行合併,可以合併的標準為pxIterator的首地址加上pxIterator的塊大小之後等於pxBlockToInsert的首地址。相等就說明兩個塊是相鄰的。如果不能合併,就什麼事都不做。然後,FreeRTOS再試著將pxBlockToInsert和pxIterator指向的下一個空閒塊進行合併。可合併的標準和剛剛說的一樣,只是這次用pxBlockToInsert的首地址加上pxBlockToInsert的塊大小與pxIterator指向的下一個塊地址比較。能合併是最好的,不能合併,則要修改pxBlockToInsert的Next指標,指向pxIterator的下一個空閒塊。這是連結串列插入的基本操作,不用再細講了。最後,要是pxBlockToInsert沒有和pxIterator合併,則還要修改pxIterator的Next指標,這樣整條連結串列才完整無誤。
最後一個重點是vPortFree()。不過這裡的vPortFree()的流程和Heap_2的差不多,只是判斷指標合法性的時候多了兩個條件,一個是檢查回收的塊大小最高位是否為1,為1才是合法的,畢竟是分配出去了嘛。第二個是Next指標是否為空,為空了說明那是pxEnd,那就不能回收了。在這兩個判斷之前也有這兩個條件的斷言configASSERT(),定義在FreeRTOS.h裡,同樣也是定義為空,可能是留給使用者另外用的吧。
Heap_4的其它三個函式,一個名字看上去是做什麼初始化的,卻什麼都沒有實現,所以沒啥好講的,另外兩個只是用來返回記憶體堆的一些狀態而已,所以也沒啥好講的。到這裡,整個Heap_4就剖析完成了。

三,cjson的記憶體釋放

cjson只有2個檔案,一個cJSON.c 一個cJSON.h檔案。

cJSON的記憶體管理,提供了使用者自主方式的介面。可以通過方法InitHooks來設定自己的記憶體管理,預設使用malloc,free

static void *(*cJSON_malloc)(size_t sz) = malloc;
static void(*cJSON_free)(void *ptr) = free;

void cJSON_InitHooks(cJSON_Hooks* hooks)
{
    if (!hooks) { /* Reset hooks */
        cJSON_malloc = malloc;
        cJSON_free = free;
        return;
    }

    cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc;
    cJSON_free = (hooks->free_fn) ? hooks->free_fn : free;
}

四,如何避免cJson解析和列印為空

還是有兩三處需要微調一下<針對stm32f103  + FreeRTOS>。其中修改一下malloc & free & size_t 這三個東西:

free & malloc就這麼兩個地方要修改一下吧,其中size_t 這個應該是在 .h 檔案修改,主要是RTOS的 rt_malloc 和這裡的malloc使用的不同。