FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)_freertos堆栈溢出

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取




#### 目录


* [任务堆栈问题的出现](#_16)
* [FreeRTOS任务栈溢出检测](#FreeRTOS_149)
* + [vApplicationStackOverflowHook](#vApplicationStackOverflowHook_157)
* [FreeRTOS任务运行情况查询](#FreeRTOS_179)
* + [vTaskList](#vTaskList_188)
* [临界区的使用](#_321)
* + [临界区API介绍](#API_332)
	+ - [taskENTER\_CRITICAL](#taskENTER_CRITICAL_373)
		- [taskEXIT\_CRITICAL](#taskEXIT_CRITICAL_420)
		- [taskENTER\_CRITICAL\_FROM\_ISR](#taskENTER_CRITICAL_FROM_ISR_455)
		- [taskEXIT\_CRITICAL\_FROM\_ISR( x )](#taskEXIT_CRITICAL_FROM_ISR_x__485)
	+ [任务挂起与临界区区别](#_512)




说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。



> 
> 本 FreeRTOS 专栏记录的开发环境:
> 
> 
> 1. [FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)](https://bbs.csdn.net/topics/618679757)
> 2. [FreeRTOS记录(二、FreeRTOS任务API认识和源码简析)](https://bbs.csdn.net/topics/618679757)
> 3. [FreeRTOS记录(三、FreeRTOS任务调度原理解析\_Systick、PendSV、SVC)](https://bbs.csdn.net/topics/618679757)
> 
> 
> 


## 任务堆栈问题的出现


为了写记录,自己先建立好了几个任务,其中就有 I2C读取温湿度传感器数据的任务,最初每个任务是使用的系统默认128字(512字节)作为默认大小,但是我感觉有些任务512字节有些浪费,比如提示系统运行的跑马灯,于是我把一些任务改成了64字大小,当然设置成为64,需要先把系统的最小任务大小设置为64。


![在这里插入图片描述](https://img-blog.csdnimg.cn/e8dc9bcb4d664974984a891bedeab69b.png)


刚开始我把这些简单的任务都设置成为64了,然后发现运行的时候,就会死掉,死掉的原因当然是堆栈溢出了,当然,很容易想到是我的 I2C 读取温湿度的任务导致的溢出,其他任务实在没干什么事情,  
 I2C任务和其他任务的大小如下:



osThreadDef(Led_toggle, Start_Led_toggle, osPriorityLow, 0,64);
Led_toggleHandle = osThreadCreate(osThread(Led_toggle), NULL);

/* definition and creation of printfTask */
osThreadDef(printfTask, StartprintfTask, osPriorityLow, 0, 128);
printfTaskHandle = osThreadCreate(osThread(printfTask), NULL);

/* definition and creation of KeyTask */
osThreadDef(KeyTask, StartKeyTask, osPriorityIdle, 0, 64);
KeyTaskHandle = osThreadCreate(osThread(KeyTask), NULL);

/* definition and creation of THread */
osThreadDef(THread, StartTHread, osPriorityNormal, 0, 64);
THreadHandle = osThreadCreate(osThread(THread), NULL);

/* definition and creation of spiflash */
osThreadDef(spiflash, Startspiflash, osPriorityIdle, 0, 64);
spiflashHandle = osThreadCreate(osThread(spiflash), NULL);


/* USER CODE END Header_StartTHread */
void StartTHread(void const * argument)
{
/* USER CODE BEGIN StartTHread */
float T=0,H=0;
/*64会溢出字的内存空间不够SHT21 协议读取*/
/* Infinite loop */
for(;😉
{
SHT2X_THMeasure();
T=(getTemperature()/100.0);
H=(getHumidity()/100.0);
osThreadSuspendAll();
printf(“\r\n%4.2f C\r\n%4.2f%%\r\n”,T,H);
osThreadResumeAll();
osDelay(3000);
}
/* USER CODE END StartTHread */
}


后面还是把`THread`任务大小改成128,看上去每隔一定时间采集一定的次数,一切正常  
 (实际上有个bug就存在了,只是其他任务比较简单,看不出任何问题)。


本来准备把任务通知、消息队列的使用说明一下,然后开启了一个硬件定时器准备周期采集温湿度数据,从中断时发送任务通知,和从任务中发送任务通知。


开启定时器以后,定时器中断优先级改为6,定时器时间为1S一次中断,受FreeRTOS管理的中断优先级我是按照默认的设置为5:


![在这里插入图片描述](https://img-blog.csdnimg.cn/d5bb125814f043999f158db6eb90dda9.png)


通过我们前面的知识就知道,FreeRTOS进入临界区以后,硬件定时器的中断是会被屏蔽的。所以在检查问题的时候这个问题不用考虑。


在定时器中断中做了个简单的计数:



/**
* @brief This function handles TIM3 global interrupt.
*/
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
time3_count++;
if(time3_count >= 10){
time3_count = 0;
}
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */

/* USER CODE END TIM3_IRQn 1 */
}


通过按键任务查看一下计数,这么做只是为了测试一切正常:



/* USER CODE END Header_StartKeyTask */
void StartKeyTask(void const * argument)
{
/* USER CODE BEGIN StartKeyTask */
/* Infinite loop */
for(;😉
{
if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
osDelay(10);
if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
osThreadSuspendAll();
printf(“K2 pushed!!,time3_count is :%d\r\n”,time3_count);
osThreadResumeAll();
while(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
osDelay(10);
}
}
}
osDelay(1);
}
/* USER CODE END StartKeyTask */
}

void Start_Led_toggle(void const * argument)
{
/* USER CODE BEGIN Start_Led_toggle */
/* Infinite loop */
for(;😉
{
osDelay(500);
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
osDelay(500);
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
osDelay(500);
}
/* USER CODE END Start_Led_toggle */
}


于是问题就出来了。


现象是:我只要按了一次按键,I2C读取任务 和 LED灯任务就永远不会运行了,但是按键任务,包括硬件定时器数据都是正常的,就是程序没有死机,只是有2个任务死掉了。


能够想到应该是堆栈的问题,因为确实没有什么复杂的程序运行,优先级的问题我也考虑过了,每个任务是否会释放CPU控制权也都检测过。


还将FreeRTOS能够用的总堆大小改成了10K:


![在这里插入图片描述](https://img-blog.csdnimg.cn/0f4e38e718094c39ac5514d5aaaca64b.png)


依然不行,最后还是将所有的任务大小还是改回了128字,任务看上去解决了,按键按下,能够获取数值,而且所有任务能够周期运行。


## FreeRTOS任务栈溢出检测


然后想着FreeRTOS是有检测任务堆栈溢出功能的,于是找到相关的内容,参考了一些网上的资料。


在CubeMX中,选择使用堆栈溢出钩子函数,启动栈溢出检测方案为方案二,如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/30e21d5a920f44c3bcbb1940e7d687d6.png)


### vApplicationStackOverflowHook


选中以后生成的代码,多了一个`vApplicationStackOverflowHook`函数,直接在里面打印溢出的任务名,如下图:



/* Hook prototypes */
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName);

/* USER CODE BEGIN 4 */
__weak void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
/* Run time stack overflow checking is performed if
configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is
called if a stack overflow is detected. */
printf(“任务:%s 溢出\r\n”,pcTaskName);
}


终于,问题的根本原因终于找到了:


![在这里插入图片描述](https://img-blog.csdnimg.cn/027af8c2e3de40a1a25d2ee33e22c581.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_18,color_FFFFFF,t_70,g_se,x_16)  
 顿时心中压抑一阵的疑问终于打开,温湿度读取使用128字的大小还是不够,那么解决办法当然还是增加这个任务的空间大小,改成192字,终于所有任务都正常了。


## FreeRTOS任务运行情况查询


问题虽然已经解决,但是我们以后要怎样才能确定自己的任务大小呢,这时候,我们可以使用到可视化追踪功能,查看所有任务的运行情况和堆栈使用大小,在CubeMX中使能相应的功能,如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/8acc6a6a20ec49909229b5fe0f7564c6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


然后还需要在`Include definetions`部分使能`eTaskGetState`:


![在这里插入图片描述](https://img-blog.csdnimg.cn/1cede20e0a7b472fbdf5df058a2ed4d6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_18,color_FFFFFF,t_70,g_se,x_16)


### vTaskList


然后在程序中,我们使用的是`osThreadList` 函数,其实也就是调用了`vTaskList`函数:



/**
* @brief Lists all the current threads, along with their current state
* and stack usage high water mark.
* @param buffer A buffer into which the above mentioned details
* will be written
* @retval status code that indicates the execution status of the function.
*/
osStatus osThreadList (uint8_t *buffer)
{
#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS == 1 ) )
vTaskList((char *)buffer);
#endif
return osOK;
}


打印任务查看任务状态:


我们为了说明问题,下面的代码中THread任务的大小还是128字,因为还没有讲任务通知,信号量之类的知识,我这里使用自己定义的一个标志位`printfstate_on` 来判断是否需要打印任务状态:



/* definition and creation of Led_toggle */
osThreadDef(Led_toggle, Start_Led_toggle, osPriorityLow, 0, 128);
Led_toggleHandle = osThreadCreate(osThread(Led_toggle), NULL);

/* definition and creation of printfTask */
osThreadDef(printfTask, StartprintfTask, osPriorityLow, 0, 256);
printfTaskHandle = osThreadCreate(osThread(printfTask), NULL);

/* definition and creation of KeyTask */
osThreadDef(KeyTask, StartKeyTask, osPriorityIdle, 0, 128);
KeyTaskHandle = osThreadCreate(osThread(KeyTask), NULL);

/* definition and creation of THread */
osThreadDef(THread, StartTHread, osPriorityNormal, 0, 128);
THreadHandle = osThreadCreate(osThread(THread), NULL);

/* definition and creation of spiflash */
osThreadDef(spiflash, Startspiflash, osPriorityIdle, 0, 128);
spiflashHandle = osThreadCreate(osThread(spiflash), NULL);

/* USER CODE END Header_StartprintfTask */
void StartprintfTask(void const * argument)
{
/* USER CODE BEGIN StartprintfTask */
/* Infinite loop */
for(;😉
{
if(printfstate_on){
printfstate_on =0;
uint8_t mytaskstatebuffer[500];
printf(“==================================\r\n”);
printf(“任务名 任务状态 优先级 剩余栈 任务序号\r\n”);
osThreadList((uint8_t *)&mytaskstatebuffer);
printf(“%s\r\n”,mytaskstatebuffer);
}
osDelay(10);//释放CPU占用权不要忘了延时
}
/* USER CODE END StartprintfTask */
}

/* USER CODE END Header_StartKeyTask */
void StartKeyTask(void const * argument)
{
/* USER CODE BEGIN StartKeyTask */
/* Infinite loop */
for(;😉
{
if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
osDelay(10);
if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
taskENTER_CRITICAL();
printf(“K2 pushed!!,time3_count is :%d\r\n”,time3_count);
taskEXIT_CRITICAL();
printfstate_on = 1;
while(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){
osDelay(10);
}
}
}
osDelay(1);

}
/* USER CODE END StartKeyTask */
}


结果如下图:


![在这里插入图片描述](https://img-blog.csdnimg.cn/92153ce397d2432bbf70867bde1076f1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)  
 图中有2个地方有问题,第一个是温湿度中的剩余栈为0 ,溢出了,第二个是KeyTask的任务序号异常。


这个基本上可以确定是,THread任务和 KeyTask任务 在内存空间上是使用的连续的内存空间,一前一后,THread任务溢出导致改写了属于 KeyTask任务所在内存的数据,导致他的任务序号异常。这是内存空间相关的知识。


然后还需要说一下任务状态的意思:



/* Task states returned by eTaskGetState. */
typedef enum
{
eRunning = 0, /* X A task is querying the state of itself, so must be running. */
eReady, /* R The task being queried is in a read or pending ready list. */
eBlocked, /* B The task being queried is in the Blocked state. */
eSuspended, /* S The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
eDeleted, /* D The task being queried has been deleted, but its TCB has not yet been freed. */
eInvalid /* Used as an ‘invalid state’ value. */
} eTaskState;


我们把THread任务大小改成 192 字,运行结果如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/38461940eb2f4d678746d92922f12430.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_17,color_FFFFFF,t_70,g_se,x_16)


为了更加说明任务堆栈大小的问题,根据推荐视频里面的教程我也做了测试,我们可以看到 printfTask 的剩余栈只剩下12个字,所以我们在 printfTask 中把剩余的空间占用测试一下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/253a7909ab8e47dab90b1d4618c54665.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


测试结果如下,`printfTask` 任务栈大小多用了2个字:


![在这里插入图片描述](https://img-blog.csdnimg.cn/83f06777860a4809982d32513f0b5b82.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


这个剩余栈的大小在视频中定义了一个为0的数组,直接会占用任务使用的栈,我测试并没有出现这种小现象,可能是因为gcc编译器的优化处理。


![在这里插入图片描述](https://img-blog.csdnimg.cn/e108a8f2d02b4d4fb2b0bd80f6a8ed45.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)


任务状态的查看是需要占用一定的的内存空间的,尤其是当任务多了以后,我们这里的使用只是方便调试阶段查找确定问题。


## 临界区的使用


![img](https://img-blog.csdnimg.cn/img_convert/38eb4bae13b98f681afc2f4250b5c12c.png)
![img](https://img-blog.csdnimg.cn/img_convert/8ff9dbe8f9a6d1c1764c72e2d17319a4.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

,g_se,x_16)


任务状态的查看是需要占用一定的的内存空间的,尤其是当任务多了以后,我们这里的使用只是方便调试阶段查找确定问题。


## 临界区的使用


[外链图片转存中...(img-B1zUdpYS-1715874730916)]
[外链图片转存中...(img-xJTxn7Jk-1715874730916)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值