参考博文连接:
示例工程代码库地址如下:
1. 问题描述
博主在使用 heap_4 的 pvPortMalloc 和 vPortFree 堆内存操作函数时,
遇到个奇怪问题,即:调用 vPortFree 函数不能正常释放内存
FreeRTOSConfig.h
#define configASSERT( x ) \
if( ( x ) == 0 ) { \
taskDISABLE_INTERRUPTS(); \
OLOGF("<!!!OS Assert!!!> func = %s, line=%d\n", __func__, __LINE__); \
for( ;; ); \
}
heap_4.c
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* The memory being freed will have an BlockLink_t structure immediately
* before it. */
puc -= xHeapStructSize;
/* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc;
/* Check the block is actually allocated. */
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
* allocated. */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
xNumberOfSuccessfulFrees++;
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
不能正常释放具体表现在调用 vPortFree 函数停留在如下断言处:
configASSERT( pxLink->pxNextFreeBlock == NULL );
2. 求真之路
博主想封装一个输出 FreeRTOS 任务列表以及任务运行时间占比的函数,如下:
void OS_SysInfo(void)
{
//此处博主一开始根据 vTaskList 每一行的输出字符数外加一点余地得出 10 字节的基础字节数
char *pBuf = OS_MemAlloc(uxTaskGetNumberOfTasks() * (10 + configMAX_TASK_NAME_LEN));
OLOGF("taskname state priority freestack number\n");
if(pBuf){
vTaskList(pBuf);
OLOGF("%s\n\n", pBuf);
OLOGF("taskname count usage\n");
/*
* 这里也使用一开始申请的内存,却不曾想过会内存溢出,
* vTaskGetRunTimeStats 内部首先也会申请一段内存,大概率是紧挨着 pBuf 指向的内存段之后,
* 当其内部拷贝数据到 pBuf 时,一旦溢出便会写入其内部申请的内存块,继而改写内存块头部数据
* 当 vTaskGetRunTimeStats 函数退出前释放申请的内存时,由于内存头部数据已被改写,导致释放失败
*/
vTaskGetRunTimeStats(pBuf);
OLOGF("%s\n\n", pBuf);
OS_MemFree(pBuf);
}else{
OLOGF("Heap space is not enough\n");
}
}
/**
* @brief OS memory alloc
* @param {u32} size - alloc size
* @retval memory address, NULL is failed
*/
void *OS_MemAlloc(u32 size)
{
return pvPortMalloc(size);
}
/**
* @brief OS memory free
* @param {void} *m - memory address
*/
void OS_MemFree(void *m)
{
vPortFree(m);
}
tasks.c
void vTaskGetRunTimeStats( char * pcWriteBuffer )
{
TaskStatus_t * pxTaskStatusArray;
UBaseType_t uxArraySize, x;
uint32_t ulTotalTime, ulStatsAsPercentage;
#if ( configUSE_TRACE_FACILITY != 1 )
{
#error configUSE_TRACE_FACILITY must also be set to 1 in FreeRTOSConfig.h to use vTaskGetRunTimeStats().
}
#endif
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = ( char ) 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
* function is executing. */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! If
* configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
* equate to NULL. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* For percentage calculations. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0UL )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* What percentage of the total run time has the task used?
* This will always be rounded down to the nearest integer.
* ulTotalRunTimeDiv100 has already been divided by 100. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* Write the task name to the string, padding with
* spaces so it can be printed in tabular form more
* easily. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
* printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
else
{
/* If the percentage is zero here then the task has
* consumed less than 1% of the total run time. */
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
* printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
pcWriteBuffer += strlen( pcWriteBuffer ); /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
* is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
3. 扩展延伸
FreeRTOS 的 heap 内存的使用,需要特别注意内存溢出的问题,
当出现内存溢出时,从 vPortFree 的函数实现来看,
会造成很多意想不到的问题。
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* The memory being freed will have an BlockLink_t structure immediately
* before it. */
puc -= xHeapStructSize;
/* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc;
/* Check the block is actually allocated. */
/* 一旦该内存块被前一个内存块数据溢出修改了数据时,此处断言大概率无法通过通,
* 因为上一个内存块数据溢出首先就意外修改了紧挨着的内存块的头部数据
* /
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
* allocated. */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
/* 一旦被修改,xBlockSize 也可能跟着会被修改,变大或者变小
* 继而可能导致 xFreeBytesRemaining 剩余字节数大于配置的总数
* 或者小于配置的总数
*/
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
xNumberOfSuccessfulFrees++;
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
总结下来,内存块数据溢出将导致以下情况:
- 内存块数据溢出将修改紧挨着的后一个内存块的数据
- 内存块数据溢出将可能导致紧挨着的后一个申请的内存块无法释放
- 内存块数据溢出将可能导致记录整体可用空闲内存的变量数值增大或减小