前言
(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:gitee仓库
前期准备
(1)复制上一篇博客的工程下来
实战
使用STM32CubeMX创建递归锁
(1)按照如下步骤
使用keil端创建递归锁
(1)创建递归锁(按
Ctrl+F
搜索add mutexes
)
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
// 普通互斥量
KeilMutexHandle = xSemaphoreCreateMutex();
// 递归锁
KeilRecursiveMutexHandle = xSemaphoreCreateRecursiveMutex();
/* USER CODE END RTOS_MUTEX */
应用代码
代码
(1)补充如下代码(按
Ctrl+F
搜索Header_StartCubemxTask
)
/* USER CODE END Header_StartCubemxTask */
#define Test_RecursiveMutex 1 //如果为1,测试递归锁。为0测试普通互斥量的死锁问题
void Test_DeadLock(void)
{
#if Test_RecursiveMutex
xSemaphoreTakeRecursive(KeilRecursiveMutexHandle, portMAX_DELAY);
#else
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#endif /* Test_RecursiveMutex */
printf("Test_RecursiveMutex\r\n");
#if Test_RecursiveMutex
xSemaphoreGiveRecursive(KeilRecursiveMutexHandle);
#else
xSemaphoreGive(KeilMutexHandle);
#endif /* Test_RecursiveMutex */
}
void StartCubemxTask(void *argument)
{
/* USER CODE BEGIN StartCubemxTask */
/* Infinite loop */
for(;;)
{
#if Test_RecursiveMutex
xSemaphoreTakeRecursive(KeilRecursiveMutexHandle, portMAX_DELAY);
#else
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#endif /* Test_RecursiveMutex */
// 这里是刻意采用阻塞延时,目的是增加低优先级任务执行时间
HAL_Delay(10);
Task_H = 0;
Task_M = 0;
Task_L = 1;
//测试互斥量死锁问题
Test_DeadLock();
#if Test_RecursiveMutex
xSemaphoreGiveRecursive(KeilRecursiveMutexHandle);
#else
xSemaphoreGive(KeilMutexHandle);
#endif /* Test_RecursiveMutex */
}
/* USER CODE END StartCubemxTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
void StartKeilTask_M(void *argument)
{
vTaskDelay(50/portTICK_PERIOD_MS);
BaseType_t xStatus;
while(1)
{
Task_H = 0;
Task_M = 1;
Task_L = 0;
}
}
void StartKeilTask_H(void *argument)
{
vTaskDelay(100/portTICK_PERIOD_MS);
while(1)
{
#if Test_RecursiveMutex
xSemaphoreTakeRecursive(KeilRecursiveMutexHandle, portMAX_DELAY);
#else
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#endif /* Test_RecursiveMutex */
Task_H = 1;
Task_M = 0;
Task_L = 0;
#if Test_RecursiveMutex
xSemaphoreGiveRecursive(KeilRecursiveMutexHandle);
#else
xSemaphoreGive(KeilMutexHandle);
#endif /* Test_RecursiveMutex */
}
}
/* USER CODE END Application */
使用存在的小问题—FreeRTOS的堆空间配置
(1)
FreeRTOS
创建任务时默认的任务栈大小为128
字,在32位系统中即为128*4=512Byte
,再加上TCB
块占用84Byte
,一共596Byte
。而大小为3072Byte
的堆允许创建3个这样的任务,占用约1800Byte
。堆中剩余的部分则存放了系统内核、信号量、队列、任务通知等数据。
(2)而这里如果模拟运行,会发现第三个任务无法成功创建。
(3)
<1>此时我们可以看一下打印的错误数据,会发现是返回的errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
。而这个错误表示内存分配失败。
<2>当时我任务是因为STM32F103C8T6
的RAM
空间不足导致的问题。之后我更换为STM32F103RCT6
,依旧存在这个问题。
<3>后面查阅资料后才发现,我们可以通过FreeRTOSConfig.h
的configTOTAL_HEAP_SIZE
这个宏来设置RAM
空间分配给FreeRTOS
的堆。于是我将默认的3072
修改为3500
,程序就成功运行起来了。
//修改前
#define configTOTAL_HEAP_SIZE ((size_t)3072)
// 修改后
#define configTOTAL_HEAP_SIZE ((size_t)3500)
(4)注意,如果是使用的
STM32CubeMX
,是按照如下步骤修改
FreeRTOS堆分配技巧
(1) 在前面我们介绍静态创建任务的时候曾经讲解过,如何分配任务栈空间。而如果是动态创建任务,那么就需要在
FreeRTOS
的堆上使用类似malloc
函数进行申请空间。这里正好遇到了这个问题,那么我正好做补充,如何分配FreeRTOS
的堆空间。
(2)介绍静态创建任务的时候,我们介绍了uxTaskGetStackHighWaterMark()
函数,可以通过这个函数知道当前任务剩余的栈空间。而FreeRTOS
的堆空间剩余,官方也提供了两个API
接口。
(3)注意:
<1>xPortGetFreeHeapSize()
在使用heap_3.c
时不能被调用。
<2>xPortGetMinimumEverFreeHeapSize()
只能在使用heap_4.c
或heap_5.c
时生效。
(4)FreeRTOS
一般默认使用的是heap_4.c
。
size_t xPortGetFreeHeapSize( void ); //获取当前未分配的内存堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ); //获取未分配的内存堆历史最小值
(5)如果需要修改
FreeRTOS
内存管理策略,进入FreeRTOSConfig.h
按Ctrl+F
搜索USE_FreeRTOS_HEAP
将其设置为自己想要的内存管理策略。
/* 像如下写法,就是使用的heap_3.c */
//#define USE_FreeRTOS_HEAP_1
//#define USE_FreeRTOS_HEAP_2
#define USE_FreeRTOS_HEAP_3
//#define USE_FreeRTOS_HEAP_4
//#define USE_FreeRTOS_HEAP_5
(6)如果是使用的
STM32CubeMX
,按照如下方法调整FreeRTOS
内存管理策略。
仿真结果
(1)将
Test_RecursiveMutex
置为0,进行死锁实验,会发现互斥量永远被低优先级任务持有并且卡死。导致高优先级任务永远无法被执行。
(2)将
Test_RecursiveMutex
置为1,利用递归锁解决死锁问题。高优先级任务想要获取锁的时候,发现锁被低优先级任务持有,进行优先级继承。
理论
(1)在前文我们介绍了互斥量的知识,我们知道,互斥量是谁上锁谁解锁。我们知道,互斥量是谁上锁设谁解锁。但是互斥量有一个问题,就是只能上锁一次。
(2)这样存在一个问题,假如我们在临界区中,再一次想办法获取互斥量会怎么样呢?从上面的实验结果我们能够发现,会发生死锁。也就是高优先级任务永远无法获取到互斥量,也就永远无法被执行。
注意:之所以说永远,是因为我这里Take
设置的等待时间是portMAX_DELAY
。如果是其他值,例如10s,那么结果就是10s内高优先级任务无法被执行。
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
//临界区
xSemaphoreGive(KeilMutexHandle);
(3)为了解决这个问题,
FreeRTOS
提出了一个新的处理办法——递归锁。递归锁可以解决在临界区中,依然需要进行获取锁的情况。(我暂时想不到有啥实际例子)
(4)需要注意,递归锁需要注意两点:
<1>谁上锁谁解锁。
<2>递归锁上锁了几次,那么就需要解锁几次。
API函数介绍
创建
(1)动态创建递归锁
/**
* @brief 动态创建递归锁
*
* @param 无
*
* @return 如果成功创建了递归互斥锁,那么将返回创建的互斥锁的句柄。否则返回 NULL。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
(2)静态创建递归锁
/**
* @brief 静态创建递归锁
*
* @param 无
*
* @return 如果已成功创建递归互斥锁,则返回创建的创建的互斥锁的句柄。 如果由于pxMutexBuffer为NULL而导致 递归互斥锁未创建,则返回 NULL。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic(StaticSemaphore_t *pxMutexBuffer );
/* --- 使用方法 --- */
SemaphoreHandle_t xSemaphore = NULL;
StaticSemaphore_t xMutexBuffer;
void vATask( void * pvParameters )
{
/* 创建一个不使用动态内存分配的递归锁。
* 递归锁的数据结构会被保存到xMutexBuffer变量中
*/
xSemaphore = xSemaphoreCreateRecursiveMutexStatic( &xMutexBuffer );
/* pxMutexBuffer不是NULL,所以句柄不应该是NULL */
configASSERT( xSemaphore );
/* 任务代码的其余部分放在这里 */
}
获取
(1)互斥量和信号量的获取函数都是
xSemaphoreTake()
,而递归锁的获取函数为xSemaphoreTakeRecursive()
。
/**
* @brief 获取递归锁
*
* @param xMutex 正在获得的互斥锁的句柄
* -xTicksToWait 等待递归锁变为可用的时间(以滴答为单位)
*
* @return 如果获得信号量,则返回 pdTRUE。 如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE。
*/
xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex,TickType_t xTicksToWait );
释放
(1)互斥量和信号量的释放函数都是
xSemaphoreGive()
,而递归锁的是xSemaphoreGiveRecursive()
。
/**
* @brief 释放递归锁
*
* @param xMutex 正在释放或“给出”的互斥锁的句柄
*
* @return 如果成功给出信号量,则返回 pdTRUE
*/
xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex );
参考
(1)韦东山freeRTOS快速入门:10-3_互斥量的缺陷和递归锁
(2)FreeRTOS官方手册:信号量/互斥锁
(3)C站:STM32内存结构介绍,FreeRTOS内存分配技巧,Stack_Size和Heap_Size大小设置
(4)FreeRTOS官方手册:内存管理