STM32CubeMX学习笔记26---FreeRTOS互斥量

一、互斥量简介

1、互斥量用于互锁,可以充当资源保护的令牌,当一个任务希望访问某个资源时,它必须先获取令牌,当任务使用完资源后,必须返还令牌,以便其他任务可以访问该资源。

2、互斥量一般用于临界资源保护。

3、用互斥量处理不同任务队临界资源的同步访问时,任务想要获取互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得到访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。

4、互斥量与递归互斥量

互斥量更适合于可能会引起优先级翻转的情况

递归互斥量更适用于任务可能会多次获取互斥量的情况下,这样可以避免同一任务多次递归持有而造成死锁的问题。

核心在于:谁上锁,就只能由谁开锁。

基本概念
        互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,**它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。**任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。

        如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务、任务与中断的同步,但是互斥量更多的是用于保护资源的互锁。

        用于互锁的互斥量可以充当保护资源的令牌,当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问该资源。是不是很熟悉,在我们的二值信号量里面也是一样的,用于保护临界资源,保证多任务的访问井然有序。当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源。但是**信号量会导致的另一个潜在问题,那就是任务优先级翻转。**而 FreeRTOS 提供的互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响,所以,用于临界资源的保护一般建议使用互斥量。

 运作机制

用互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。

二、STM32CubeMX设置

1、配置RCC、USART1、时钟72M
2、配置SYS,将Timebase Source修改为除滴答定时器外的其他定时器。
3、初始化LED的两个引脚
4、开启FreeRTOS,v1与v2版本不同,一般选用v1即可
5、创建两个线程:任务LED1用作发送,LED2用作接收。

以上步骤可参考:STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客

6、创建互斥量Mutex

        在 Mutexes 进行配置。

  • Mutex Name: 互斥量名称
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Conrol Block Name: 控制块名称
7、生成代码

三、程序编程

1、创建一个互斥量:osMutexCreate
函数osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
参数mutex_def: 引用由osMutexDef定义的互斥量
返回值成功返回互斥量ID,失败返回0

例:

osMutexId myMutex01Handle;
 
osMutexDef(myMutex01);
myMutex01Handle = osMutexCreate(osMutex(myMutex01));
2、创建一个递归互斥量:osRecursiveMutexCreate

用于创建一个递归互斥量,不是递归的互斥量由函数 osMutexCreate() 创建,且只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。递归信号量则相反,它可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以减少优先级反转的反生。

函数osMutexId osRecursiveMutexCreate (const osMutexDef_t *mutex_def)
参数mutex_def: 引用由osMutexDef定义的互斥量
返回值成功返回互斥量ID,失败返回0

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

例:

osMutexId myMutex02Handle;
 
osMutexDef(myMutex02);
myMutex02Handle = osRecursiveMutexCreate(osMutex(myMutex02));
 3、删除一个互斥量:osMutexDelete 

用于删除一个互斥量。

函数osStatus osMutexDelete (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值错误码

例:

osMutexDelete(myMutex01Handle);
4、获取互斥量:osMutexWait

用于获取互斥量,但是递归互斥量并不能使用这个 API 函数获取。

 例:

osMutexWait(myMutex01Handle,osWaitForever);//一直等待获取互斥量
5、获取递归互斥量:osRecursiveMutexWait

        用于获取递归互斥量的宏,与互斥量的获取函数一样,osMutexWait()也是一个宏定义,它最终使用现有的队列机制,实际执行的函数是 xQueueTakeMutexRecursive() 。 获取递归互斥量之前必须由 osRecursiveMutexCreate() 这个函数创建。要注意的是该函数不能用于获取由函数 osMutexCreate() 创建的互斥量。

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

例:

osRecursiveMutexWait(myMutex02Handle,osWaitForever);//一直等待获取互斥量
6、释放互斥量: osMutexRelease

        用于释放互斥量,但不能释放由函数 osRecursiveMutexCreate() 创建的递归互斥量。

函数osStatus osMutexRelease (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值

错误码

例:

osMutexRelease(myMutex01Handle);
7、释放递归互斥量:osRecursiveMutexRelease

        用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 osRecursiveMutexWait() 函数成功获取几次递归互斥量,就要使用 osRecursiveMutexRelease() 函数返还几次,在此之前递归互斥量都处于无效状态,别的任务就无法获取该递归互斥量。使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级。

函数osStatus osRecursiveMutexRelease (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值错误码

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

 例:

osRecursiveMutexRelease(myMutex02Handle);
8、互斥量使用

        如果两个线程都使用printf进行打印函数,如果同时打印可能导致资源抢夺,此时可以使用互斥量,将两个线程中打印的东西轮流进行打印,此时就不会出现资源抢夺问题。

LED1:

void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	 osStatus xReturn;
	int i=0;
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		//获取互斥量 MuxSem,没获取到则一直等待  上锁
		 xReturn = osMutexWait(myMutex01Handle,     /* 互斥量句柄 */ 
                          osWaitForever);   /* 等待时间 */
    if(osOK == xReturn) 
		{
			//获取到互斥量,此时资源上锁,其他任务不得使用该资源(printf)
			for(;i<100;)
			{
				printf("LED1 run %d ",i++);
				printf("LED1 run %d ",i++);
				printf("LED1 run %d \n",i++);
			}
			i=0;
			
		}
		osMutexRelease(myMutex01Handle);//给出互斥量
    osDelay(10);
  }
  /* USER CODE END LED1_Task1 */
}

LED2:

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	 osStatus xReturn = osErrorValue;
	int i=0;
  for(;;)
  { 
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		printf("LED2                                run %d \n",i++);
    osDelay(10);
  }
  /* USER CODE END LED2_Task03 */
}

下载验证:

编译无误后下载到板子上验证,可以看到没有发生资源被占夺的情况

假如将LED的互斥量屏蔽了之后:

void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	 osStatus xReturn;
	int i=0;
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		//获取互斥量 MuxSem,没获取到则一直等待  上锁
//		 xReturn = osMutexWait(myMutex01Handle,     /* 互斥量句柄 */ 
//                          osWaitForever);   /* 等待时间 */
//    if(osOK == xReturn) 
		{
			//获取到互斥量,此时资源上锁,其他任务不得使用该资源(printf)
			for(;i<100;)
			{
				printf("LED1 run %d ",i++);
				printf("LED1 run %d ",i++);
				printf("LED1 run %d \n",i++);
			}
			i=0;
			
		}
//		osMutexRelease(myMutex01Handle);//给出互斥量
    osDelay(10);
  }
  /* USER CODE END LED1_Task1 */
}

可以看到在LED1运行的过程中资源被LED2占夺了,

9、递归互斥量使用

创建递归互斥量:

	osMutexId myMutex02Handle;

	osMutexDef(myMutex02);
	myMutex02Handle = osRecursiveMutexCreate(osMutex(myMutex02));

LED1任务:

void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	 osStatus xReturn;
	int i=0;
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		xReturn =osRecursiveMutexWait(myMutex02Handle,osWaitForever);//一直等待获取递归互斥量
		
		printf("LED1 获取递归互斥量 %s \r\n",osOK == xReturn?"成功":"失败");
    if(osOK == xReturn) 
		{
			//获取到互斥量,此时资源上锁,其他任务不得使用该资源
			for(;i<10;)
			{
				xReturn =osRecursiveMutexWait(myMutex02Handle,osWaitForever);//一直等待获取递归互斥量
				printf("LED1 获取递归互斥量 %s 第%d次\r\n",osOK == xReturn?"成功":"失败",i++);
				osMutexRelease(myMutex02Handle);
			}
			i=0;
			
		}
		osMutexRelease(myMutex02Handle);
    osDelay(300);
  }
  /* USER CODE END LED1_Task1 */
}

LED2任务:

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	 osStatus xReturn = osErrorValue;
	int i=0;
  for(;;)
  { 		
		xReturn =osRecursiveMutexWait(myMutex02Handle,osWaitForever);//一直等待获取递归互斥量
				printf("LED2         获取递归互斥量 %s 第%d次\r\n",osOK == xReturn?"成功":"失败",i++);
				
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		if(osOK == xReturn) 
			printf("LED2                                          run %d \n",i++);
		osMutexRelease(myMutex02Handle); //释放递归互斥量
    osDelay(300);
  }
  /* USER CODE END LED2_Task03 */
}

下载验证:

可以看到两个任务正常依次运行:

四、常见问题

1、 优先级反转

假设任务A、B都想使用串口,A优先级比较低:

  • 任务A获得了串口的互斥量
  • 任务B也想使用串口,它将会阻塞、等待A释放互斥量
  • 高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)

如果涉及3个任务,可以让"优先级反转"的后果更加恶劣。

本节代码为: FreeRTOS_17_mutex_inversion 。

互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二级制信号量的差别。

2、优先级继承

        优先级反转的问题在于,LPTask低优先级任务获得了锁,但是它优先级太低而无法运行。

如果能提升LPTask任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?

        把LPTask任务的优先级提升到什么水平?

优先级继承:

  • 假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
  • 任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
  • 于是任务A就继承了任务B的优先级
  • 这就叫:优先级继承
  • 等任务A释放互斥锁时,它就恢复为原来的优先级

互斥锁内部就实现了优先级的提升、恢复。

3、死锁的概念

 日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!

  • 假设有2个互斥量M1、M2,2个任务A、B:
  • A获得了互斥量M1
  • B获得了互斥量M2
  • A还要获得互斥量M2才能运行,结果A阻塞
  • B还要获得互斥量M1才能运行,结果B阻塞
  • A、B都阻塞,再无法释放它们持有的互斥量
  • 死锁发生!

3.1、自我死锁
假设这样的场景:

  • 任务A获得了互斥锁M
  • 它调用一个库函数
  • 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
  • 死锁发生!

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:

任务A获得递归锁M后,它还可以多次去获得这个锁
"take"了N次,要"give"N次,这个锁才会被释放

五、参考文献

韦东山freeRTOS系列教程之【第七章】互斥量(mutex)_freertos的互斥量获取返回失败,实际获取成功-CSDN博客

STM32CubeMX学习笔记(31)——FreeRTOS实时操作系统使用(互斥量)_学习cube freertc 互锁-CSDN博客 

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值