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