文章

FreeRTOS递归互斥信号量

递归互斥信号量

1. 递归互斥信号量

递归互斥信号量是一种特殊的互斥信号量,已经获取了互斥信号量的任务不能再次获取这个互斥信号量,但是递归互斥信号量不同;已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量(即可以嵌套使用),且次数不限。

递归互斥信号量也有优先级继承的问题。一个任务获取了多少次递归互斥信号量就必须释放多少次。比如,若某个任务成功获取了 3 次递归互斥量,那么该任务也需要同样释放 3 次递归信号量。同互斥信号量一样,递归互斥信号量不能用在中断服务函数中

2. 递归互斥信号量的 API 函数

2.1 创建递归互斥信号量
1
2
3
4
5
6
7
/********************动态创建递归互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void)
/********************静态创建递归互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic(StaticSemaphore_t * pxSemaphoreBuffer)
//参数:pxSemaphoreBuffer 指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回互斥信号量句柄;失败返回NULL

动态递归互斥信号量创建函数是一个宏,最终是通过 xQueueCreateMutex() 函数来完成,其源码如下:

1
2
3
4
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateRecursiveMutex()  	\
		xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX)		
#endif

xQueueCreateMutex 函数的源码分析,可参考互斥信号量章节 3.1 的介绍

2.2 释放递归互斥信号量

递归互斥信号量有专门的释放函数:xSemaphoreGiveRecursive()

1
2
3
4
/********************递归互斥信号量释放*************************************/
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t  xMutex)
/************************************************************************/
返回值:释放成功返回pdPASS;释放失败返回pdFAIL

递归互斥信号量释放函数是一个宏,最终调用 xQueueGiveMutexRecursive() 函数,源码如下示:

1
#define xSemaphoreGiveRecursive(xMutex)		xQueueGiveMutexRecursive((xMutex))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**************xQueueGiveMutexRecursive()函数*********************/
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex ){
  BaseType_t xReturn;
  Queue_t * const pxMutex = ( Queue_t * ) xMutex;
  /* 检查递归互斥信号量是不是被当前任务获取 */
  if(pxMutex->pxMutexHolder == (void *)xTaskGetCurrentTaskHandle()){
	( pxMutex->u.uxRecursiveCallCount )--;//用来记录递归信号量被释放的次数
	/* uxRecursiveCallCount为0时,说明是最后一次释放 */
	if(pxMutex->u.uxRecursiveCallCount == (UBaseType_t) 0){
	  /* 此时调用xQueueGenericSend完成真正的释放  */
	  (void)xQueueGenericSend(pxMutex,NULL,queueMUTEX_GIVE_BLOCK_TIME,queueSEND_TO_BACK);
	}
	else{
	  mtCOVERAGE_TEST_MARKER();
	}
	xReturn = pdPASS;//释放成功
  }
  else{
	xReturn = pdFAIL;//不是被当前任务获取,释放失败
  }
  return xReturn;
}
2.3 获取递归互斥信号量

递归互斥信号量有专门的获取函数:xSemaphoreTakeRecursive()

1
2
3
4
5
/********************递归互斥信号量获取*******************************************/
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t  xMutex//要获取的信号量句柄
						  		   TickType_t xBlockTime)//阻塞时间
/******************************************************************************/
返回值:获取成功返回pdPASS;释放失败返回pdFALSE

递归互斥信号量获取函数是一个宏,最终调用 xQueueTakeMutexRecursive() 函数,源码如下示:

1
2
3
4
#if( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) \
xQueueTakeMutexRecursive(( xMutex ), ( xBlockTime ))
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**************xQueueTakeMutexRecursive()函数*********************/
BaseType_t xQueueTakeMutexRecursive(QueueHandle_t xMutex,TickType_t xTicksToWait){
  BaseType_t xReturn;
  Queue_t * const pxMutex = ( Queue_t * ) xMutex;
  /* 检查当前任务是不是递归互斥信号量的拥有者 */
  if(pxMutex->pxMutexHolder == (void *) xTaskGetCurrentTaskHandle()){
	( pxMutex->u.uxRecursiveCallCount )++;//若是,表示本次是重复获取信号量
	xReturn = pdPASS;
  }
  else{//如果任务是第一次获取信号量,就需要调用以下函数完成真正的信号量获取
	xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
	/* 第一次获取信号量成功后,将uxRecursiveCallCount加1 */
	if( xReturn != pdFAIL ){
	  ( pxMutex->u.uxRecursiveCallCount )++;
	}
	else{
	  traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
	}
  }
  return xReturn;
}

3. 递归互斥信号量的应用实例

本实例介绍递归互斥信号量的使用。使用 STM32CubeMX 将 FreeRTOS 移植到工程中,创建优先级为高中低的三个任务、一个递归互斥信号量

High_Task:高优先级任务,会获取递归互斥信号量 2 次,获取成功后进行相应的处理,处理完后释放递归互斥信号量两次Middle_Task:中优先级任务,简单的应用任务Low_Task:低优先级任务,会获取递归互斥信号量,获取成功后进行相应的处理,处理完后释放递归互斥信号量。但是任务信号量的时间比高优先级任务占用的时间要长

3.1 STM32CubeMX 设置
  • RCC 设置外接 HSE,时钟设置为 72M
  • PC0/PC1 设置为 GPIO 推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1 选择为异步通讯方式,波特率设置为 115200Bits/s,传输数据长度为 8Bit,无奇偶校验,1 位停止位;
  • 激活 FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数

img

  • 动态创建递归互斥信号量

img

  • 使用 FreeRTOS 操作系统,一定要将 HAL 库的 Timebase Source 从 SysTick 改为其他定时器,选好定时器后,系统会自动配置 TIM
  • 输入工程名,选择路径(不要有中文),选择 MDK-ARM V5;勾选 Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击 GENERATE CODE,生成工程代码
3.2 MDK-ARM 软件编程
  • 添加 High_Task、Middle_Task、Low_Task 任务函数代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/******************HighTask**************************/
void HighTask(void const * argument){
  for(;;){
    vTaskDelay(500);
	printf("High task take RecursiveMutex1\r\n");
	xSemaphoreTakeRecursive(RecursiveMutexHandle,portMAX_DELAY);
	printf("High task running...!\r\n");
	printf("High task take RecursiveMutex2\r\n");
	xSemaphoreTakeRecursive(RecursiveMutexHandle,portMAX_DELAY);
	printf("High task running...!\r\n");
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);	
	printf("High task give RecursiveMutex1\r\n");
	xSemaphoreGiveRecursive(RecursiveMutexHandle);
	printf("High task give RecursiveMutex2\r\n");
	xSemaphoreGiveRecursive(RecursiveMutexHandle);
	vTaskDelay(500);
  }
}
/******************MiddleTask***********************/
void MiddleTask(void const * argument){
  for(;;){
    printf("Middle task running!\r\n");
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
	vTaskDelay(1000);
  }
}
/******************LowTask**************************/
void LowTask(void const * argument){
  for(;;){
  	printf("Low task take RecursiveMutex\r\n");
    xSemaphoreTakeRecursive(RecursiveMutexHandle,portMAX_DELAY);
	printf("Low task running...!\r\n");
	for(int i=0;i<20000000;i++){
	  taskYIELD();			
	}
	printf("Low task give RecursiveMutex\r\n");		
	xSemaphoreGive(RecursiveMutexHandle);
	vTaskDelay(1000);
  }
}
3.3 下载验证

编译无误下载到开发板后,打开串口调试助手,串口输出如下图示的调试信息:

由于 High_Task 任务延时 500ms,因此 Middle_Task 最先开始运行;之后 Low_Task 运行,获取递归互斥信号;High_Task 开始运行,阻塞在请求递归互斥信号量这里,等待 Low_Task 释放递归互斥信号量此时由于 Low_Task 正在使用递归互斥信号量,因此 Low_Task 的优先级暂时提升到了与 High_Task 相同的优先级,所以 Middle_Task 无法打断 Low_Task 的运行。Low_Task 运行完后,释放递归互斥信号量,High_Task 获取两次互斥信号量并运行,最后释放两次递归互斥信号量

img

以上转载自博主“安迪西嵌入式”的FreeRTOS专栏,仅作学习记录,如有侵权,请联系删除。

本文由作者按照 CC BY 4.0 进行授权