HAL库freertos对一块内存连续释放两次会导致程序卡死

文章讲述了在使用Freertos时,作者遇到程序在STM32CubeMX环境下对同一内存块连续释放两次导致死机的问题,通过深入分析内存释放函数vPortFree和configASSERT,发现是configASSERT的默认行为导致。解决方案是修改configASSERT内容并调整main.h和FreeRTOSConfig.h中的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习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;

### HALFreeRTOS结合使用时出现的卡死问题解决方案 当在FreeRTOS环境中使用STM32 HAL时,如果遇到`HAL_UART_Transmit`或其他阻塞函数导致系统卡死的情况,这通常是由于中断优先级设置不当引起的[^1]。 #### 中断优先级配置 为了确保FreeRTOS能够正常工作,在初始化阶段应当调整全局中断优先级分组。具体来说,需要降低外设中断(如UART)的抢占优先级,使其低于调度器使用的最高优先级。可以通过修改NVIC配置实现这一点: ```c void MX_NVIC_Init(void) { /* 配置USARTx全球使能和优先级 */ HAL_NVIC_SetPriority(USARTx_IRQn, 5, 0); // 将抢占优先级设为较低值 HAL_NVIC_EnableIRQ(USARTx_IRQn); } ``` 此外,还需要注意的是,某些版本的HAL默认实现了等待标志位清除的功能,该功能可能导致任务挂起时间过长甚至永久等待。对于这种情况,建议采用非阻塞模式发送数据或自定义回调处理机制来替代标准API调用。 #### 使用非阻塞传输方法 为了避免因长时间占用CPU而导致其他高优先级任务得不到及时响应,推荐改用DMA方式进行串口通信。这样不仅可以提高效率还能有效防止因为缓冲区满等原因造成的延迟现象发生。以下是启用DMA传送的一个简单例子: ```c // 初始化 UART 和 DMA static void SystemClock_Config(void); int main(void) { ... huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.Mode = UART_MODE_TX_RX; if (HAL_UART_Init(&huart1) != HAL_OK){ Error_Handler(); } __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx); // 开启DMA通道并启动传输过程 HAL_DMA_Start_IT(hdma_usart1_tx, (uint32_t)&txBuffer[0], sizeof(txBuffer)); HAL_UART_Transmit_DMA(&huart1, NULL, 0); while (1){ osDelay(1000); } } // 定义完成后的回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle) { // 发送完成后执行的操作... } ``` 通过上述措施可以大大减少由硬件资源竞争引起的应用层逻辑错误概率,并且提高了系统的稳定性和实时性能表现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值