前言
(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:gitee仓库
前期准备
(1)将5.0章节的工程复制下来
实战
使用STM32CubeMX创建互斥量
(1)按照如下方法创建互斥量
使用keil端创建互斥量
(1)添加互斥量的句柄(按
Ctrl+F
,搜索CODE BEGIN Variables
补充如下代码)
/* USER CODE BEGIN Variables */
static SemaphoreHandle_t KeilMutexHandle; //互斥量
(2)创建互斥量(按
Ctrl+F
,搜索RTOS_MUTEX
补充如下代码)
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
KeilMutexHandle = xSemaphoreCreateMutex();
/* USER CODE END RTOS_MUTEX */
应用代码
(1)原来只有两个任务,现在再增加一个任务用于实验。(按
Ctrl+F
,搜索CODE BEGIN Variables
补充如下代码)
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
xReturned = xTaskCreate(StartKeilTask_M,"KeilTask_M", 128, "StartKeilTask_M\r\n", osPriorityLow2, &keilTaskHandle);
if(xReturned != pdPASS)
{
printf("KeilTask_M creation failed\r\n");
}
xReturned = xTaskCreate(StartKeilTask_H,"KeilTask_H", 128, "StartKeilTask_H\r\n", osPriorityLow3, NULL);
if(xReturned != pdPASS)
{
printf("KeilTask_H creation failed\r\n");
}
/* USER CODE END RTOS_THREADS */
(2)补充如下的应用程序。(按
Ctrl+F
,搜索Header_StartCubemxTask
补充如下代码)
/* USER CODE END Header_StartCubemxTask */
#define Test_Mutex 1 //如果为1测试互斥量,否则是测试二进制信号量
#define Test_MutexLock 0 //如果为1测试互斥量的谁上锁谁解锁,进行这个测试之前,需要先将Test_Mutex置1
void StartCubemxTask(void *argument)
{
/* USER CODE BEGIN StartCubemxTask */
/* Infinite loop */
for(;;)
{
#if Test_Mutex
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#else
xSemaphoreTake(KeilBinarySemHandle, portMAX_DELAY);
#endif /* Test_Mutex */
// 这里是刻意采用阻塞延时,目的是增加低优先级任务执行时间
HAL_Delay(10);
Task_H = 0;
Task_M = 0;
Task_L = 1;
#if Test_MutexLock
while(1);
#endif /* Test_MutexLock */
#if Test_Mutex
xSemaphoreGive(KeilMutexHandle);
#else
xSemaphoreGive(KeilBinarySemHandle);
#endif /* Test_Mutex */
}
/* 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)
{
#if Test_MutexLock
// 获取互斥量,立即返回
xStatus = xSemaphoreTake(KeilMutexHandle, 0);
printf("Task_M\r\n");
if(xStatus == pdFALSE)//获取互斥量失败,自己开锁
{
printf("xSemaphoreGive before\r\n");
xSemaphoreGive(KeilMutexHandle);
printf("xSemaphoreGive after\r\n");
}
// 获取互斥量
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#endif /* Test_MutexLock */
Task_H = 0;
Task_M = 1;
Task_L = 0;
}
}
void StartKeilTask_H(void *argument)
{
vTaskDelay(100/portTICK_PERIOD_MS);
while(1)
{
#if Test_Mutex
xSemaphoreTake(KeilMutexHandle, portMAX_DELAY);
#else
xSemaphoreTake(KeilBinarySemHandle, portMAX_DELAY);
#endif /* Test_Mutex */
Task_H = 1;
Task_M = 0;
Task_L = 0;
#if Test_Mutex
xSemaphoreGive(KeilMutexHandle);
#else
xSemaphoreGive(KeilBinarySemHandle);
#endif /* Test_Mutex */
}
}
/* USER CODE END Application */
仿真结果
(1)将
Test_Mutex
置0,Test_MutexLock
置0。结果如下
(2)将
Test_Mutex
置1,Test_MutexLock
置0。结果如下
(3)将
Test_Mutex
置1,Test_MutexLock
置1。结果如下
理论
为什么需要互斥量
(1)互斥量规定了谁上锁谁解锁。这个可以理解为你上厕所,如果厕所里面没有人,那么你是可以直接打开的,然后在厕所的内侧锁门。当你在上厕所的过程中,还有一个人要来上厕所,将无法打开门。需要等你上完厕所,你从厕所里面解锁出来,其他人才能重新使用厕所。
(2)而二进制信号量不同,他无法保证谁上锁谁解锁。例如你在上厕所,突然保洁阿姨来了。她直接从厕所外面开门,而你还在上厕所。这无疑是非常尴尬的。二进制信号量就是将厕所门的锁放在了外面而不是里面。
(3)互斥量本意是为了保证谁上锁谁解锁,在FreeRTOS
和Linux
中都没有解决一般的互斥锁谁上锁谁解锁的问题,因此只有程序员自己来实现。但是如果是RT-Thread
就能够解决了这个问题。
注意:上面这一部分是韦东山老师原话。但是我实测之后发现,FreeRTOS
的互斥量似乎实现了谁上锁谁解锁。个人猜测是因为老版本的FreeRTOS
没有实现,而我现在使用的是新版本的FreeRTOS
,因此存在差异。
(4)上面这一堆话,各位看了肯定感觉自相矛盾了,你互斥量要实现谁上锁谁解锁,但是又没有实现,这不脱了裤子放屁麻。互斥量岂不是就是一个累赘?显然不是,互斥量能够解决优先级反转问题,而信号量不行。
(5)在上面的实操案例中,我们将Test_Mutex
置0,先测试二值信号量处理三个任务。
<1>按照常理来说,优先级高的任务如果没有主动释放CPU,那么低优先级任务将永远无法被执行。因此任务执行流程如下
- 首先是
Task_L
处于高电平,其他两个为低电平。- 50ms之后,中优先级任务响应,
Task_M
为高电平。- 100ms之后,
Task_H
将会一直处于高电平,其他两个为低电平。<2>但是测试结果会发现会发现,首先是
Task_L
处于高电平,之后Task_M
一直处于高电平。Task_H
从始至终为低电平。这是明显发生了优先级反转的问题,中等优先级可以执行任务,但是高优先级反而执行不了。
<3>这是为什么呢?原因很简单,首先低优先级任务执行,50ms
之后,中优先级任务被唤醒,因此会打断低优先级的任务。低优先级任务被打断了,但是信号量依旧在低优先级手中,那么就会导致100ms
之后,高优先级任务响应,高优先级想去获取信号量获取失败。那么高优先级任务就会进入阻塞态。导致最终中优先级任务一直占领CPU
资源。
(6)在上面的实操案例中,我们将
Test_Mutex
置1,此时测试一下互斥量,会发现电平变化是预期的那样了。因此,我们可以看出FreeRTOS
的互斥量虽然没有实现自己谁上锁谁解锁的功能,但是很好的解决了优先级翻转的问题。
(7)现在我们知道了互斥量能够解决上述的优先级翻转的问题,那么如何理解互斥量是如何处理这种问题的呢?原因很简单,100ms
之后,高优先级任务想要获取互斥量,但是会发现互斥量现在由一个低优先级的任务持有。那么高优先级任务就会将自己的权限借给低优先级任务,让低优先级任务快点执行完任务,然后释放互斥量。当低优先级任务释放互斥量的瞬间,高优先级任务获取互斥量,开始占领CPU
资源。
互斥量和二进制信号量的异同
(1)前面我们说了,互斥量其实就一个特殊的二进制信号量。那么我们能否总结一下他们之间的异同点呢?
<1>不同点:
- 互斥量能够进行优先级继承,解决优先级翻转的问题。而二进制信号量存在优先级翻转问题。
- 二进制信号量创建的初始值是0,因此创建完成之后还需要
Give
一次。而互斥量创建的初始值是1,不需要再进行Give
。- 二进制信号量可以由其他任务进行释放。而互斥量必须是谁上锁谁解锁。
<2>相同点:
- 互斥量和二进制信号量的
Give
/Take
函数完全一样。
如何理解谁上锁谁解锁
(1)前面我们说了,互斥量要实现谁上锁谁解锁,但是也说了
FreeRTOS
没有实现谁上锁谁解锁的功能。那么有没有什么办法验证一下这个讲法呢?
(2)方法很简单,玩一手监守自盗。StartCubemxTask
任务获取到互斥量之后,就加一个死循环,不释放互斥量,主打一个占着茅坑不拉。然后StartKeilTask_M
任务尝试获取信号量,不管能否获取到互斥量,都立刻返回。如果发现获取互斥量失败,就自己释放互斥量,然后再获取。
(3)如果是看的韦东山老师的教学视频,并且是使用的他的FreeRTOS
版本,会发现程序能够往下执行,Task_M
将会变为1。
(4)但是当前我使用的
FreeRTOS
版本,测试会发现程序最终会卡死在xSemaphoreGive(KeilMutexHandle);
中。调试后发现,我当前使用的FreeRTOS
存在如下判断语句,永远判断互斥量是谁上锁,因此只有谁能够进行解锁。
关于中断函数使用互斥量问题
(1)在FreeRTOS的官方文档中存在这么一句话:中断服务例程中, 不能使用互斥锁。
(2)但是在韦东山老师的文档里面还是看到了关于中断服务程序中,对互斥量的操作介绍。这个是有问题的,需要注意!
(3)在FreeRTOS
中,通常不建议在中断服务函数(ISR
)中直接使用互斥量。这是因为中断服务函数需要尽可能地快速执行,而互斥量涉及到任务切换和内核调度,可能导致中断响应时间变长,不符合实时系统的要求。
API函数介绍
(1)互斥量和信号量只有创建的时候有区别,获取、释放、删除等操作都是使用的相同的函数。
(2)这也一定程度上说明了,互斥量其实就是一个特殊的二进制信号量。
创建
(1)互斥量的创建分为静态创建和动态创建,一般推荐动态创建互斥量。
/**
* @brief 动态创建一个互斥量,返回它的句柄
*
* @param 无
*
* @return 如果已成功创建互斥量,则将返回该互斥量的句柄,如果因为保留互斥量所需的RAM 无法分配而无法创建互斥量,则会返回 NULL。
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
(2)下面是静态创建互斥量的函数。
/**
* @brief 静态创建一个互斥量,返回它的句柄
*
* @param 指向StaticSemaphore_t类型的变量, 该变量将用于保存互斥锁型信号量的状态
*
* @return 如果已成功创建互斥锁类型信号量,则返回创建的 互斥锁的句柄。 如果 因为 pxMutexBuffer 是 NULL 而未创建互斥锁,那么返回 NULL。
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer );
/* --- 使用方法 --- */
SemaphoreHandle_t xSemaphore = NULL;
StaticSemaphore_t xMutexBuffer;
void vATask( void * pvParameters )
{
/* 创建一个不使用动态内存的互斥信号量分配。
互斥量的数据结构将被保存到xMutexBuffer变量。
*/
xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );
/* pxMutexBuffer不是NULL,所以句柄不应该是NULL。*/
configASSERT( xSemaphore );
/* 任务代码的其余部分放在这里 */
}
参考
(1)FreeRTOS官方手册:信号量/互斥锁
(2)C站:韦东山freeRTOS系列教程之【第七章】互斥量(mutex)