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被阻塞了,导致优先级翻转),这个情况时我们不希望的,而互斥信号量的引入,一定程度上解决了或者问题。