2021 嵌入式作業系統分析與實做 Lab4 report === [Lab4](https://docs.google.com/presentation/d/1SAb8jS3tac0BpAkFxz-q6QqMzWEK63KdGZw4KxRxsn8/edit#slide=id.gd16f07b711_2_50) contribute by `Liao712148` ###### tags: `FreeRTOS` ### Lab要求 * Step1: * Use Uart to show Free linked list. * Create a task to call vPrintFreeList. * Implement your vPrintFreeList function in heap_2.c. * Step2: * Modifying prvInsertBlockIntoFreeList code in heap_2.c to implement memory merge. * Create six tasks * RedLEDTask * GreenLEDTask * Task1 * Task2 * Task3 * PrintTask * Task1、Task2、Task3 will delete themselves. * PrintTask will call vPrintFreeList function to show free list information. ### 實做流程 #### Heap_2 * introduce * Heap_2 記憶體分配使用的是`best fit algorithm`. * Heap_2 支持記憶體回收,但是==不會把相鄰的回收後的記憶體做合併==,所以有可能造成**記憶體破碎化**。 * 適合用在雖然需要釋放記憶體但是每個task需要的TCB、Stack是相同的。這樣釋放後的記憶體空間就能直接被其他的task使用,可以避免**記憶體破碎化**。 * 在call xTaskCreate()會調用 pvPortMalloc() 來跟memory要空間,可以從xTaskCreate()發現task中的TCB, Stack 是分開跟memory要空間的。 ```cpp= BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) { /*...*/ /* Allocate space for the TCB. Where the memory comes from depends on the implementation of the port malloc function and whether or not static allocation is being used. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*...*/ /* Allocate space for the stack used by the task being created. */ pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*...*/ } ``` --- * 因為heap2有支持記憶體釋放,但是釋放後的記憶體不能互相合併,所以我們必須將釋放後的記憶體區塊,用一個list串接起來。並且依照區塊的大小由小到大串接起來。以方便做`best fit algorithm`。所以有必要在第一次調用pvPortMalloc()的時候,初始化一個free list。 ```cpp= static void prvHeapInit( void ) { BlockLink_t *pxFirstFreeBlock; uint8_t *pucAlignedHeap; pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; xStart.xBlockSize = ( size_t ) 0; xEnd.xBlockSize = configADJUSTED_HEAP_SIZE; xEnd.pxNextFreeBlock = NULL; pxFirstFreeBlock = ( void * ) pucAlignedHeap; pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE; pxFirstFreeBlock->pxNextFreeBlock = &xEnd; } ``` * 第`5`行:記憶體對齊 * ucHeap是一個由`uint8_t`組成,大小為`configTOTAL_HEAP_SIZE`的陣列。所以分配記憶體其實是在一個陣列上做劃分。 * 但是ucHeap並沒有保證會是記憶體對齊的,所以並不是整個ucHeap都能夠使用。最先能使用的地方為第一個滿足記憶體對齊的記憶體位址。舉例來說假設&ucHeap[0]的記憶體位址是0x00000003並不滿足記憶體對齊的條件。而&ucHeap[8]的記憶體位址是0x0000000B,所以如果遮蔽最低3個bit。可以得到0x00000008。此記憶體位址滿足記憶體對齊。所以就能找出第一個能使用的記憶體位址。但是浪費了0x00000003~0x00000007的記憶體。 * 第`7~16`行: 並將這個記憶體位址,和將剩下可以用的空間(configTOTAL_HEAP_SIZE - 8 )一起存入pxFirstFreeBlock。然後在用xStart串起,xStart為free list的開頭,xEnd則為結尾。 --- * 以下為pvPortMalloc() trace ```cpp= void *pvPortMalloc( size_t xWantedSize ) { BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; static BaseType_t xHeapHasBeenInitialised = pdFALSE; void *pvReturn = NULL; size_t BlockSize,WantedSize; WantedSize = xWantedSize; vTaskSuspendAll(); { /* If this is the first call to malloc then the heap will require initialisation to setup the list of free blocks. */ if( xHeapHasBeenInitialised == pdFALSE ) { prvHeapInit(); xHeapHasBeenInitialised = pdTRUE; } /* The wanted size is increased so it can contain a BlockLink_t structure in addition to the requested amount of bytes. */ if( xWantedSize > 0 ) { xWantedSize += heapSTRUCT_SIZE; /* Ensure that blocks are always aligned to the required number of bytes. */ if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ) { /* Byte alignment required. */ xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); } } if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) ) { /* Blocks are stored in byte order - traverse the list from the start (smallest) block until one of adequate size is found. */ pxPreviousBlock = &xStart; pxBlock = xStart.pxNextFreeBlock; while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ) { pxPreviousBlock = pxBlock; pxBlock = pxBlock->pxNextFreeBlock; } /* If we found the end marker then a block of adequate size was not found. */ if( pxBlock != &xEnd ) { /* Return the memory space - jumping over the BlockLink_t structure at its start. */ pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE ); /* This block is being returned for use so must be taken out of the list of free blocks. */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into two. */ if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { /* This block is to be split into two. Create a new block following the number of bytes requested. The void cast is used to prevent byte alignment warnings from the compiler. */ pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); /* Calculate the sizes of two blocks split from the single block. */ pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */ prvInsertBlockIntoFreeList( ( pxNewBlockLink ) ); } xFreeBytesRemaining -= pxBlock->xBlockSize; } } traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll(); #if( configUSE_MALLOC_FAILED_HOOK == 1 ) { if( pvReturn == NULL ) { extern void vApplicationMallocFailedHook( void ); vApplicationMallocFailedHook(); } } #endif return pvReturn; } ``` * 第`9`行:調用了vTaskSuspendAll(),suspend所有的task,已保證thread safe,避免分配記憶體的過程被打斷。 * 第`13`行:透過`xHeapHasBeenInitialised`來檢查是否已經初始化free list。如果已經有初始化過的話,該變數會被改成`pdTRUE`,否則為`pdFALSE`。因為有用static修飾該變數,因此下次調用pvPortMalloc()時,該變數仍然存在。 * 第`23`行:由於除了我們要allocate的記憶體大小外,還要多allocate一個heapSTRUCT_SIZE的空間用來存放下一個free block的記憶體位址以及本次allocate的所有空間, i.e. xWantedSize = heapSTRUCT_SIZE + 我們要allocate的記憶體大小。 * 第`26~30`行:檢查總共要allocate的空間(xWantedSize)是否能滿足記憶體對齊,如果不滿足就要allocate到最小能夠記憶體對齊的記憶體空間。 * 第`39~43`行:在free list中找尋與大於xWantedSize且最接近的free block。 * 第`50`行:找到的適合的free block後,就可以將該記憶體位址返回給使用者,要注意的是==記憶體區塊的一開始是存放BlockLink_t的資料節構==,所以我們真正會使用的地方應該是要在這個資料結構之後,所以我們的回傳的記憶體位址要是` pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE ); ` * 第`54`行:將該free block從記憶體串列中刪除。 * 第`57~71`行:我們分配出去的free block雖然會滿足`best fit algorithm`但是有可能free block的空間還是超過xWantedSize很多。這時候就可以把過多的部份回收回來。透過`pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );`可以得到於的記憶體位址,形成新的free block,在將多餘的記憶體大小存入該free block中,再利用`prvInsertBlockIntoFreeList( ( pxNewBlockLink ) )`將這個新的free block串回 free list中。 * 第`73`行:更改剩餘的記憶體空間的大小。 --- ##### Step 1: Lab要求1,是要print出free list中的每個free block的起始位址、結束的位址、block的大小、剩餘的記憶體空間。 透過上述對free list的講解可以知道每個free block是透過linked list根據free block的大小,由小到大的方式串接起來的。所以透過free list的走訪即可找出每個free block中的相關資訊。 ```cpp= void vPrintFreeList( void ) { char MonitorTset[60] = "StartAddress|heapSTRUCT_SIZE|xBlockSize|EndAddress"; char startadderss[15]; char endaddress[15]; char pxStack[15]; memset(pxStack,'\0',sizeof(pxStack)); sprintf(pxStack," %s \n\r",MonitorTset); HAL_UART_Transmit(&huart2,(uint8_t *)pxStack,strlen(pxStack),0xffff); BlockLink_t *temp ; temp = xStart.pxNextFreeBlock; while (temp != NULL && temp->pxNextFreeBlock != NULL) { memset(pxStack,'\0',sizeof(pxStack)); memset(pxStack,'\0',sizeof(startadderss)); memset(pxStack,'\0',sizeof(endaddress)); memset(MonitorTset,'\0',sizeof(MonitorTset)); Uint32ConvertHex((uint32_t)(temp), startadderss); Uint32ConvertHex((uint32_t)(temp)+(uint32_t)(temp->xBlockSize), endaddress); sprintf(MonitorTset,"%s %0d %0d %s\n\r",startadderss, heapSTRUCT_SIZE,temp->xBlockSize,endaddress); HAL_UART_Transmit(&huart2,(uint8_t *)MonitorTset,strlen(MonitorTset),0xffff); temp = temp->pxNextFreeBlock; } memset(MonitorTset,'\0',sizeof(MonitorTset)); sprintf(MonitorTset," configADJUSTED_HEAP_SIZE:%0d xFreeBytesRemaining:%d \n\r",configADJUSTED_HEAP_SIZE, xFreeBytesRemaining ); HAL_UART_Transmit(&huart2,(uint8_t *)MonitorTset,strlen(MonitorTset),0xffff); } ``` * 第`10,11`行:宣告一個變數,其型別為poniter to BlockLink_t,用來對free list做走訪。 * 第`12`行:為走訪的判斷式 * 第`18`行:因為是用pointer做走訪,所以pointer的值就是該free block的記憶體起始位址。所以直接將pointer的值,轉成16進位即為所求。 * 第`19`行:將pointer的值加上該free block的block size即可得到該free block的中止位址。 ##### Step 2: 根據lab的要求要將相鄰的free block合併成一個大塊的free block,在free list中的排序依就是要從小的free block size排到大的。 思路:在每加入一塊new_free block的同時就需要根據新的free block的記憶體起始、中止位址,然後在free list中找看由沒有相鄰近的free block。如果有的話就將該free block與本次要加進去的new_free block合併成一個更大塊的new_free block,==並將原先的free block從free list中移除==。 由於free block串入 free list是透過`prvInsertBlockIntoFreeList`,所以我們需要對這個macro做修改。 ```cpp= #define prvInsertBlockIntoFreeList( pxBlockToInsert ) \ { \ BlockLink_t *pxIterator, *pxprevious, *target; \ size_t xBlockSize; \ uint32_t startaddress, endaddress, tempeendaddress; \ tempeendaddress = (uint32_t)pxBlockToInsert + (uint32_t)pxBlockToInsert->xBlockSize; \ pxprevious = &xStart; \ target = xStart.pxNextFreeBlock; \ while(target != &xEnd) \ { \ startaddress = (uint32_t) target; \ endaddress = startaddress + (uint32_t)target->xBlockSize; \ if (startaddress == tempeendaddress||endaddress ==(uint32_t)pxBlockToInsert ) \ { \ pxBlockToInsert->xBlockSize = pxBlockToInsert->xBlockSize +target->xBlockSize; \ pxprevious->pxNextFreeBlock = target->pxNextFreeBlock; \ target->pxNextFreeBlock = NULL; \ target = pxprevious->pxNextFreeBlock; \ } \ else{ \ target = target->pxNextFreeBlock; \ pxprevious = pxprevious->pxNextFreeBlock; \ } \ } \ xBlockSize = pxBlockToInsert->xBlockSize; \ /* Iterate through the list until a block is found that has a larger size */ \ /* than the block we are inserting. */ \ for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock ) \ { \ /* There is nothing to do here - just iterate to the correct position. */ \ } \ /* Update the list to include the block being inserted in the correct */ \ /* position. */ \ pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; \ pxIterator->pxNextFreeBlock = pxBlockToInsert; \ } ``` * 第`3`行:宣告target類型為pointer to BlockLink_t,用來走訪整個free list。 * 第`6`行:tempeendaddress用來存放新加入的pxBlockToInsert的中止位址。 * 第`10,11`行:startaddress、endaddress為當下走訪的free block他的起始位址和中止位址 * 第`13~19`行:如果有發現該free block的位址與我們要加入的pxBlockToInsert相鄰就要將兩個不同的 block合併。 * 第`15`行:將pxBlockToInsert的blocl size更改成兩個free block的blocl size相加。 * 第`16`行:把找到的free block從free list中移除。 * 第`17`行:並將free block指向NULL。以防止錯誤發生。 * 第`25~35`行:此時pxBlockToInsert的Block Size已經被修改,爾且相鄰的free block也已經移除。引此可以透過原先的方式將pxBlockToInsert依據free block size由小到大的插入free list中。 ##### Step 3: 初始化task ```cpp= int main(void) { /* ... */ xTaskCreate(RedLEDTask,"RedLEDTask",100,NULL,0,NULL); xTaskCreate(Task1,"Task1",50,NULL,0,NULL); xTaskCreate(Task2,"Task2",30,NULL,0,NULL); xTaskCreate(GreenLEDTask,"GreenLEDTask",130,NULL,0,NULL); xTaskCreate(Task3,"Task3",40,NULL,0,NULL); xTaskCreate(PrintTask,"PrintTask",130,NULL,0,NULL); vTaskStartScheduler(); /* ... */ } void RedLEDTask(void const * argument) { for(;;) { HAL_GPIO_TogglePin(Red_LED_GPIO_Port, GPIO_PIN_14); vTaskDelay(500); } } void GreenLEDTask(void const * argument) { for(;;) { HAL_GPIO_TogglePin(Green_LED_GPIO_Port, GPIO_PIN_12); vTaskDelay(1000); } } void Task1(void const * argument) { for(;;) { vTaskDelete( NULL ); } } void Task2(void const * argument) { for(;;) { vTaskDelete( NULL ); } } void Task3(void const * argument) { for(;;) { vTaskDelete( NULL ); } } void PrintTask(void const * argument) { for(;;) { vPrintFreeList(); vTaskDelay(3000); } } ``` ### 心得 謝謝助教的幫忙。 ### 參考資料 [FreeRTOS程式碼閱讀筆記:heap_2.c](https://www.itread01.com/content/1540952709.html) [FreeRTOS程式碼閱讀筆記:heap_4.c](https://www.itread01.com/content/1540952703.html) [FreeRTOS(五)——heap檔案解析](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/101847/) [FreeRTOS 内存 Heap管理](https://www.jianshu.com/p/c5fbad706e9f)