在学习freertos的内存管理函数时,我跟着哔站正点原子的视频代码学习时发现,当我对一块内存进行两次内存释放时会导致整个程序卡死,而视频中的例程却不会卡死只会报错。由于我的freertos是基于stm32cubemx生成的,和视频例程手动移植的不一样,于是我就猜测可能内存释放函数vPortFree( )中的函数实现有些许不同;
硬件:正点原子miniV3.0开发板
软件:keil5、stm32cubemx
最初的实验代码如下:
freertos.c:
void *buf; //保存内存申请函数返回的内存块首地址
/* USER CODE END Header_StartTask03 */
void StartTask03(void *argument)
{
/* USER CODE BEGIN StartTask03 */
/* Infinite loop */
uint32_t key = 0;
for(;;)
{
key = KEY_Scan(0); //保存返回值,方便后面判断
if(key == KEY0_PRES)
{
buf = pvPortMalloc(100);
if(buf != NULL)
myprintf("申请内存成功\r\n");
else
myprintf("申请内存失败\r\n");
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
if(key == KEY1_PRES)
{
if(buf != NULL)
{
vPortFree(buf);
myprintf("释放内存成功\r\n");
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
}
if(key == WKUP_PRES)
{
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
osDelay(10);
}
/* USER CODE END StartTask03 */
}
实现现象为:
按下按键key0申请内存,按下按键key1释放内存,当连续按下两次key1,即对同一块内存连续释放两次时,程序死机,指示灯不闪烁且按任何按键都无反应;
于是我进入内存释放函数vPortFree( )中一探究竟,根据下图代码我们可以发现,使用内存释放函数时首先会判断内存块首地址是否为NULL,不为NULL再判断内存块大小是否符合规范以及pxNextFreeBlock是否为NULL(当内存块不为空闲时pxNextFreeBlock为NULL);当我们第一次对一个被使用的内存块释放时,该内存块会变为空闲内存块,因此pxNextFreeBlock!=NULL,第二次再对该内存块释放时,那么在configASSERT( pxLink->pxNextFreeBlock == NULL );中pxLink->pxNextFreeBlock == NULL的判断条件为0;
让我们看看cubemx生成的configASSERT函数是怎样的,当判断条件为0时关闭所有中断进入死循环,这也就解释了为什么我们对一个内存块释放两次会导致程序卡死,原因就是第二次调用内存释放函数时configASSERT( pxLink->pxNextFreeBlock == NULL );判断条件为0执行函数内容,进入了死循环了;
那么我们只需要将configASSERT()内容改一下,当触发时不进入死循环即可,我们设置了一个全局变量标志位ASSERT_warning,当进入onfigASSERT()时将其置1;
然后实验代码改为:
freertos.c:
void *buf; //保存内存申请函数返回的内存块首地址
char ASSERT_warning; //是否进入configASSERT( x )标志位,为1说明进入
/* USER CODE END Header_StartTask03 */
void StartTask03(void *argument)
{
/* USER CODE BEGIN StartTask03 */
/* Infinite loop */
uint32_t key = 0;
for(;;)
{
key = KEY_Scan(0); //保存返回值,方便后面判断
if(key == KEY0_PRES)
{
buf = pvPortMalloc(100);
if(buf != NULL)
myprintf("申请内存成功\r\n");
else
myprintf("申请内存失败\r\n");
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
if(key == KEY1_PRES)
{
if(buf != NULL)
{
vPortFree(buf);
if(ASSERT_warning == 0)
{
myprintf("释放内存成功\r\n");
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
else
{
myprintf("释放内存失败\r\n");
ASSERT_warning = 0;
}
}
}
if(key == WKUP_PRES)
{
myprintf("剩余空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
}
osDelay(10);
}
/* USER CODE END StartTask03 */
}
当对一个内存块多次释放时标志位ASSERT_warning置1,输出“内存释放失败”,按理来说这样程序就没问题了,但是编译报错:
上网查询后,说在main.h中加入下述代码即可解决:
#if 1
#ifdef __NVIC_PRIO_BITS
#undef __NVIC_PRIO_BITS
#define __NVIC_PRIO_BITS 4
#endif
#endif
该错误确实消失,但又有了新的错误:
又上网查询,说在FreeRTOSConfig.h中将下述语句注释掉即可:
注释掉之后果然错误消失,也没有新的错误产生,编译通过,接下来让我们看看实验结果:
现在对同一块内存多次释放之后程序不会卡死,只会报错
总结:HAL库的freertos对申请后的同一块内存释放两次会死机的问题主要是由于内存释放函数中的configASSERT()函数的内容会导致程序死机,我们只需要更改其中的内容,再在main.h中加一段代码,FreeRTOSConfig.h中注释一句话即可;
另外:当内存申请函数申请失败时会返回NULL;