STM32F1+HAL库+FreeTOTS学习15——互斥信号量
- 1. 优先级翻转
- 2. 互斥信号量
- 3. 相关API函数;
- 3.1 互斥信号量创建
- 3.2 获取信号量
- 3.3 释放信号量
- 3.4 删除信号量
- 4. 操作实验
- 1. 实验内容
- 2. 代码实现
- 3. 运行结果
上期我们介绍了数值信号量。这一期我们来介绍互斥信号量
1. 优先级翻转
在接受互斥信号量之前,我们还需要先了解一下优先级翻转:我们在学习二值信号量的时候有提到过,二值信号量的使用有可能带来任务优先级翻转的问题,所谓优先级翻转:就是优先级高的任务反而慢执行,低优先级的任务先执行,这种情况在操作系统中,我们是不希望出现的,因为会导致任务的执行顺序不按预期结果执行,可能会导致未知的结果。
【注】:任务优先级:任务H > 任务M > 任务L
如图就是一个典型的例子:由于任务L获取了信号量,导致任务H被阻塞,进而使得优先级高的H进入阻塞,任务M一直在执行。
2. 互斥信号量
为了解决二值信号量带来的任务优先级翻转问题,我们引入互斥信号量。
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步应用中(任务与任务或者是中断与任务的同步)二值信号量最为合适;互斥信号量则使用在需要互斥访问的常用。在互斥访问中互斥信号量就相当于一把钥匙,访问前必须获得钥匙,访问之后必须归还钥匙。
互斥信号量使用和二值信号量相同的API函数,同样可以设置阻塞时间,不同的在于互斥信号量有优先级继承机制,所谓优先级继承机制,就是当一个互斥信号量被低优先级的任务持有时,此时如果有一个高优先级的任务也要获取这个任务优先级,这个高优先级的任务会被阻塞,但是高优先级的任务会把低优先级任务的优先级提升至与自己相同,这个过程叫做优先级继承。
【注】:低优先级的任务优先级只会短暂的提高,等到高优先级的任务运行时,恢复原来的优先级。
优先级继承可以有限的减少高优先级任务的阻塞时间,将优先级翻转的影响降到最低。但是无法完全消除优先级翻转问题。原因如下:
- 互斥信号量有优先级继承的机制,但是中断不是任务,没有优先级。所以互斥信号量在中断中并不适用
- 中断需要快进跨出,不允许进入阻塞。
3. 相关API函数;
互斥信号量的使用过程:创建互斥信号量->释放信号量-> 获取信号量 -> 删除信号量 ( 可选 ),下面我们围绕几个步骤介绍计数信号量的相关API函数
常用的二值信号量API函数如下表:
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方式创建互斥信号量 |
xSemaphoreCreateMutexStatic() | 使用静态方式创建互斥信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreGive() | 释放信号量 |
vSemaphoreDelete() | 删除信号量 |
【注】:二值、计数、互斥信号量的获取和释放函数都是相同的,不过互斥信号量没有在中断使用的函数,二值和计数信号量有。
3.1 互斥信号量创建
- xSemaphoreCreateMutex()
此函数用于动态方式创建互斥信号量,创建所需要的内存,有FreeRTOS自动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
可以看到xSemaphoreCreateMutex() 内部是调用了xQueueCreateMutex() ,由于该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateMutex() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreCreateMutex
* @param 无
* @retval 返回值为NULL,表示创建失败,其他值表示为创建互斥信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
- xSemaphoreCreateMutexStatic()
此函数用于静态方式创建互斥信号量,创建互斥信号量所需要的内存,需要用户手动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreCreateMutexStatic( pxMutexBuffer) \
xQueueCreateMutexStatic( queueQUEUE_TYPE_MUTEX, \
( pxMutexBuffer ) )
可以看到xSemaphoreCreateMutexStatic() 内部是调用了xQueueCreateMutexStatic() ,有用该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateMutexStatic() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreCreateMutexStatic
* @param pxMutexBuffer :指向StaticSemaphore_t 类型的指针,用于保存互斥信号量的状态值
* @retval 返回值为NULL,表示创建失败,其他值表示为创建数值信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(
StaticSemaphore_t *pxMutexBuffer );
3.2 获取信号量
- xSemaphoreTake()
此函数用于获取信号量,如果信号量处于没有资源的状态,那么可以选择将任务进入阻塞状态,如果成功获取到了信号量,那么信号的资源数减1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreTake( xSemaphore, \
xBlockTime) \
xQueueSemaphoreTake( ( xSemaphore ), \
( xBlockTime ))
可以看到xSemaphoreTake() 内部是调用了xQueueSemaphoreTake() ,关于xQueueSemaphoreTake函数的定义和使用,我们这里不赘述。所以我们直接把xSemaphoreTake() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreTake
* @param xSemaphore:需要获取信号量的句柄
* @param xTicksToWait:阻塞时间
* @retval 返回值为pdTRUE,表示获取成功,如果返回值为pdFALSE,表示获取失败。
*/
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
3.3 释放信号量
- xSemaphoreGive()
此函数用于释放信号量,如果信号量处于资源满的状态,那么可以选择将任务进入阻塞状态,如果成功释放了信号量,那么信号的资源数加1,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:
#define xSemaphoreGive( xSemaphore) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK)
可以看到xSemaphoreGive() 内部是调用了xQueueGenericSend() ,该函数在 STM32F1+HAL库+FreeTOTS学习12——队列 中有介绍,我们这里不赘述。所以我们直接把xSemaphoreGive() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief xSemaphoreGive
* @param xSemaphore:需要释放信号量的句柄
* @retval 返回值为pdTRUE,表示释放成功,如果返回值为pdFALSE,表示释放失败。
*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
3.4 删除信号量
- vSemaphoreDelete()
此函数用于删除已创建的信号量。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示
#define vSemaphoreDelete(xSemaphore) \
vQueueDelete ( QueueHandle_t ) \
( xSemaphore ))
可以看到vSemaphoreDelete() 内部是调用了vQueueDelete () ,关于vQueueDelete 函数的定义和使用,我们这里不赘述。所以我们直接把vSemaphoreDelete() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:
/**
* @brief vSemaphoreDelete
* @param xSemaphore :需要删除信号量的句柄
* @retval 无
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
4. 操作实验
1. 实验内容
在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的数值信号量操作,具体要求如下:
- 定义一个互斥信号量,任务1、2、3,优先级分别为1、2、3(任务1优先级最低,任务3优先级最高)
- 任务3:获取互斥信号量,打印相关信息,完成之后释放互斥信号量
- 任务2:打印“中断优先级任务正在运行”。
- 任务1:和任务3操作一样,只不过延时一段时间,让优先级低的任务占用信号量久一点。
2. 代码实现
#include "freertos_demo.h"
#include "main.h"
#include "queue.h" //需要包含队列和任务相关的头文件
#include "task.h"
#include "key.h" //包含按键相关头文件
/*FreeRTOS*********************************************************************************************/
/******************************************************************************************************/
/*FreeRTOS配置*/
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK1_PRIO 1 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /*任务函数*/
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK2_PRIO 2 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /*任务函数*/
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK3_PRIO 3 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /*任务函数*/
SemaphoreHandle_t SemaphoreMutex; /* 定义互斥信号量 */
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
taskENTER_CRITICAL(); /* 进入临界区,关闭中断,此时停止任务调度*/
/* 用于优先级翻转实验 */
// SemaphoreMutex = xSemaphoreCreateBinary(); /* 创建二值信号量 */
// xSemaphoreGive(SemaphoreMutex); /* 释放互斥信号量 */
/* 用于互斥信号量实验 */
SemaphoreMutex = xSemaphoreCreateMutex(); /* 创建互斥信号量 */
if(SemaphoreMutex != NULL)
{
printf("互斥信号量创建成功!!!\r\n");
}
else
{
printf("互斥信号量创建失败!!!\r\n");
}
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
taskEXIT_CRITICAL(); /* 退出临界区,重新开启中断,开启任务调度 */
vTaskStartScheduler(); //开启任务调度
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
BaseType_t errMessage; /* 错误信息 */
while(1)
{
errMessage = xSemaphoreTake(SemaphoreMutex,portMAX_DELAY); /* 获取互斥信号量 */
if(errMessage == pdTRUE)
{
printf("低优先级获取信号量成功\r\n");
}
else
{
printf("低优先级获取获取信号量失败\r\n");
}
HAL_Delay(3000);
errMessage = xSemaphoreGive(SemaphoreMutex); /* 释放互斥信号量 */
if(errMessage == pdTRUE)
{
printf("低优先级释放信号量成功\r\n");
}
else
{
printf("低优先级释放信号量失败\r\n");
}
vTaskDelay(1000);
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
while(1)
{
printf("中等优先级任务执行\r\n");
vTaskDelay(1000);
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
BaseType_t errMessage; /* 错误信息 */
while(1)
{
errMessage = xSemaphoreTake(SemaphoreMutex,portMAX_DELAY); /* 获取互斥信号量 */
if(errMessage == pdTRUE)
{
printf("高优先级获取信号量成功\r\n");
}
else
{
printf("高优先级获取信号量失败\r\n");
}
errMessage = xSemaphoreGive(SemaphoreMutex); /* 释放互斥信号量 */
if(errMessage == pdTRUE)
{
printf("高优先级释放信号量成功\r\n");
}
else
{
printf("高优先级释放信号量失败\r\n");
}
vTaskDelay(1000);
}
}
3. 运行结果
- 使用互斥信号量的结果(没有优先级翻转)
显然这里不是很好懂,所以我们来对比一下有优先级翻转的情况。在freertos_demo() 函数里面把信号量部分创建的代码修改一下就可以
/* 用于优先级翻转实验 */
// SemaphoreMutex = xSemaphoreCreateBinary(); /* 创建二值信号量 */
// xSemaphoreGive(SemaphoreMutex); /* 释放互斥信号量 */
/* 用于互斥信号量实验 */
SemaphoreMutex = xSemaphoreCreateMutex(); /* 创建互斥信号量 */
/*
|
|
|
|
|
|
↓ */
/* 用于优先级翻转实验 */
SemaphoreMutex = xSemaphoreCreateBinary(); /* 创建二值信号量 */
xSemaphoreGive(SemaphoreMutex); /* 释放互斥信号量 */
/* 用于互斥信号量实验 */
// SemaphoreMutex = xSemaphoreCreateMutex(); /* 创建互斥信号量 */
- 使用二值信号量的结果(有优先级翻转)
对比之下就可以看出问题了,由于优先据翻转的存在,导致任务2很多时候都是比认为3先执行(因为任务3被阻塞了,导致优先级翻转),这个情况时我们不希望的,而互斥信号量的引入,一定程度上解决了或者问题。