STM32F1+HAL库+FreeTOTS学习15——互斥信号量

上期我们介绍了数值信号量。这一期我们来介绍互斥信号量

1. 优先级翻转

在接受互斥信号量之前,我们还需要先了解一下优先级翻转:我们在学习二值信号量的时候有提到过,二值信号量的使用有可能带来任务优先级翻转的问题,所谓优先级翻转:就是优先级高的任务反而慢执行,低优先级的任务先执行,这种情况在操作系统中,我们是不希望出现的,因为会导致任务的执行顺序不按预期结果执行,可能会导致未知的结果。

在这里插入图片描述
【注】:任务优先级:任务H > 任务M > 任务L

如图就是一个典型的例子:由于任务L获取了信号量,导致任务H被阻塞,进而使得优先级高的H进入阻塞,任务M一直在执行。

2. 互斥信号量

为了解决二值信号量带来的任务优先级翻转问题,我们引入互斥信号量。

互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步应用中(任务与任务或者是中断与任务的同步)二值信号量最为合适;互斥信号量则使用在需要互斥访问的常用。在互斥访问中互斥信号量就相当于一把钥匙,访问前必须获得钥匙,访问之后必须归还钥匙。

互斥信号量使用和二值信号量相同的API函数,同样可以设置阻塞时间,不同的在于互斥信号量有优先级继承机制,所谓优先级继承机制,就是当一个互斥信号量被低优先级的任务持有时,此时如果有一个高优先级的任务也要获取这个任务优先级,这个高优先级的任务会被阻塞,但是高优先级的任务会把低优先级任务的优先级提升至与自己相同,这个过程叫做优先级继承。

                                           【注】:低优先级的任务优先级只会短暂的提高,等到高优先级的任务运行时,恢复原来的优先级。

优先级继承可以有限的减少高优先级任务的阻塞时间,将优先级翻转的影响降到最低。但是无法完全消除优先级翻转问题。原因如下:

  1. 互斥信号量有优先级继承的机制,但是中断不是任务,没有优先级。所以互斥信号量在中断中并不适用
  2. 中断需要快进跨出,不允许进入阻塞。

3. 相关API函数;

互斥信号量的使用过程:创建互斥信号量->释放信号量-> 获取信号量 -> 删除信号量 ( 可选 ),下面我们围绕几个步骤介绍计数信号量的相关API函数
常用的二值信号量API函数如下表:

函数描述
xSemaphoreCreateMutex()使用动态方式创建互斥信号量
xSemaphoreCreateMutexStatic()使用静态方式创建互斥信号量
xSemaphoreTake()获取信号量
xSemaphoreGive()释放信号量
vSemaphoreDelete()删除信号量

【注】:二值、计数、互斥信号量的获取和释放函数都是相同的,不过互斥信号量没有在中断使用的函数,二值和计数信号量有。

3.1 互斥信号量创建

  1. xSemaphoreCreateMutex()

此函数用于动态方式创建互斥信号量,创建所需要的内存,有FreeRTOS自动分配,该函数实际上是一个宏定义,在semphr.h文件中有定义,具体的代码如下:

#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)

可以看到xSemaphoreCreateMutex() 内部是调用了xQueueCreateMutex() ,由于该函数不经常使用,我们这里不赘述。所以我们直接把xSemaphoreCreateMutex() 当作一个函数介绍(实际上是一个宏,我们当作函数来使用,FreeRTOS的官方文档也是这样的),下面是函数原型:

/**
 * @brief       xSemaphoreCreateMutex
 * @param       无
 * @retval      返回值为NULL,表示创建失败,其他值表示为创建互斥信号量的句柄
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );
  1. 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 获取信号量

  1. 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 释放信号量

  1. 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 删除信号量

  1. 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. 运行结果

  1. 使用互斥信号量的结果(没有优先级翻转)
    在这里插入图片描述

显然这里不是很好懂,所以我们来对比一下有优先级翻转的情况。在freertos_demo() 函数里面把信号量部分创建的代码修改一下就可以


	/* 用于优先级翻转实验 */
//	SemaphoreMutex = xSemaphoreCreateBinary();			/* 创建二值信号量 */
//	xSemaphoreGive(SemaphoreMutex);		/* 释放互斥信号量 */
	
	/* 用于互斥信号量实验 */
	SemaphoreMutex = xSemaphoreCreateMutex();			/* 创建互斥信号量 */
	
	/*
							|
							|
							|
							|
							|
							|
							↓ 									*/
							
	/* 用于优先级翻转实验 */
	SemaphoreMutex = xSemaphoreCreateBinary();			/* 创建二值信号量 */
	xSemaphoreGive(SemaphoreMutex);		/* 释放互斥信号量 */
	
	/* 用于互斥信号量实验 */
//	SemaphoreMutex = xSemaphoreCreateMutex();			/* 创建互斥信号量 */
  1. 使用二值信号量的结果(有优先级翻转)
    在这里插入图片描述
    对比之下就可以看出问题了,由于优先据翻转的存在,导致任务2很多时候都是比认为3先执行(因为任务3被阻塞了,导致优先级翻转),这个情况时我们不希望的,而互斥信号量的引入,一定程度上解决了或者问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想写代码的我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值