FreeRTOS学习篇六:互斥量
1.理解基本概念
①互斥量:互斥量是特殊的二值信号量,其主要特殊之处在于有一个优先级继承机制,互斥量以锁的形式存在,可以用线程中的锁的角度去理解。
②互斥量的两种状态:开锁和闭锁。
③互斥量的三个特性:
- 互斥量所有权:互斥量被任务持有时,互斥量处于闭锁状态,任务获得互斥量的所有权,直到任务释放,互斥量开锁,所有权释放。
- 递归访问:一个任务获取互斥量后,可以再次获取到互斥量,而不是获取不到而进入阻塞状态形成死锁。可以理解为,互斥量被任务获取,那么这个互斥量的作用域就是在这个任务中,只要不是释放后被其它任务获取了,就能在这个任务中继续获取到互斥量而不用进入阻塞状态。(递归互斥量才有的特性)
- 防止优先级翻转。
④优先级翻转与优先级继承机制:
要知道优先级继承作何用,就得先知道优先级翻转的问题。
优先级翻转,简而言之就是低优先级的比高优先级的任务先执行,为什么会发生这种情况呢?在某些情况下,可访问的资源只有一个并且同一时间内只能被一个任务所访问,当低优先级的先访问了这个资源,此时如果出现从阻塞态醒来了的更高优先级的任务也要访问这个资源,可是由于资源已经被霸占了(被锁了),高优先级的这个任务只能进入阻塞状态等待低优先级的将资源释放,就出现了低优先级的任务先于高优先级的任务运行的情况。
那这有什么问题呢?如果只有这两个任务那当然没什么问题;但是,如果在这个低优先级的任务执行过程中出现了一个比其优先级高的任务,那么其也就被打断了,直到这个出现的高优先级的执行完,其继续执行直到释放资源,这个过程将会导致原来那个等待访问被锁资源的高优先级任务的阻塞时间变长。如果高优先级的阻塞时间变长了,那就会有因处理不及时而出现问题的风险,一旦出现,对于系统来说可是致命的。而优先级继承机制正是将这种危害降到最低的一种解决方法。
高优先级的任务代表着需要尽快地被执行。优先级继承机制,是用来确保高优先级任务进入阻塞状态的时间尽可能短的,比如确保优先级翻转时高优先级的阻塞时间不会过长。那是如何实现的?思路也很简单,那就把占用资源的那个低优先级任务的优先级临时提高,等执行完后再恢复到原来的优先级。这样就能极大地使得这个低优先级任务尽快执行完成以释放保护资源,将高优先级任务的阻塞延时降到最低。
⑥FreeRTOS的互斥量的运作机制:任务获取互斥量后,互斥量立刻被变成闭锁状态,互斥量释放后才变为开锁状态,闭锁状态下,任何试图获取互斥量的都将进入阻塞状态。
⑦注意:互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在普通任务中起作用,在中断的上下文环境毫无意义。
2.应用场景
应用场景:有优先级翻转的情况或需要递归访问的场景。
多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响。
比如有两个任务需要对串口进行发送数据,其硬件资源只有一个,那么两个任务肯定不能同时发送,不然会导致数据错误,那么,就可以用互斥量对串口资源进行保护,当一个任务正在使用串口的时候,另一个任务则无法使用串口,等到任务使用串口完毕之后,另外一个任务才能获得串口的使用权。《FreeRTOS内核实现与应用开发实战——基于STM32》 P281
3.常用互斥量操作函数
①创建
1、创建互斥量 —— xSemaphoreCreateMutex():
/* 创建互斥量 */
#if(configSUPPORT_DYNAMIC_ALLOCATION == 1)
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
#endif
/* 互斥量,其类型就是 queueQUEUE_TYPE_MUTEX */
/* 无形参,返回值为互斥量句柄或者NULL */
必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION
定义为 1,即开启动态内存分配;还要将将宏configUSE_MUTEXES
定义为 1,即使用互斥量。
2、创建递归互斥量 —— xSemaphoreCreateRecursiveMutex():
/* 创建递归互斥量 */
#if((configSUPPORT_DYNAMIC_ALLOCATION==1) && (configUSE_RECURSIVE_MUTEXES ==1))
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX)
#endif
/* 无形参,返回值为递归互斥量句柄或者NULL */
必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION
定义为 1,即开启动态内存分配;还要将configUSE_RECURSIVE_MUTEXES
定义为1,即使用递归互斥量。
/* 使用示例 */
SemaphoreHandle_t xMutex;
void vATask( void * pvParameters )
{
/* 创建一个递归互斥量 */
xMutex = xSemaphoreCreateRecursiveMutex();
if ( xMutex != NULL ) {
/* 递归互斥量创建成功 */
}
②删除
调用vSemaphoreDelete()函数,传入互斥量句柄进行删除即可,与二值信号量、计数信号量的删除操作一样。
③获取
1、获取互斥量 —— xSemaphoreTake():
/* 获取互斥量 */
#define xSemaphoreTake(xSemaphore, xBlockTime) \
xQueueGenericReceive((QueueHandle_t) (xSemaphore),NULL,(xBlockTime ), pdFALSE)
- 形参:传入互斥量句柄以及阻塞超时时间。
- 返回值:pdTRIE、errQUEUE_EMPTY 。
2、获取递归互斥量 —— xSemaphoreTakeRecursive():
/* 获取递归互斥量 */
#if(configUSE_RECURSIVE_MUTEXES == 1)
#define xSemaphoreTakeRecursive(xMutex, xBlockTime)
xQueueTakeMutexRecursive((xMutex), (xBlockTime))
#endif
- 形参:信号量句柄和阻塞超时时间。如果宏
INCLUDE_vTaskSuspend
定义为 1 且形参xTicksToWait
设置为portMAX_DELAY
, 则任务将一直阻塞在该递归互斥量上(即没有超时时间)。 - 返回值:获取成功则返回 pdTRUE, 在超时之前没有获取成功则返回errQUEUE_EMPTY。
注意:在任务中,获取递归信号量多少次,就要释放递归信号量多少次。
④释放
1、释放互斥量 —— xSemaphoreGive():
/* 释放互斥量 */
#define xSemaphoreGive(xSemaphore) \
xQueueGenericSend((QueueHandle_t)(xSemaphore), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)
- 形参:互斥量句柄。
- 返回值:pdTRUE、errQUEUE_EMPTY。
2、释放递归互斥量 —— xSemaphoreGiveRecursive():
/* 释放递归互斥量 */
#if(configUSE_RECURSIVE_MUTEXES == 1)
#define xSemaphoreGiveRecursive(xMutex) xQueueGiveMutexRecursive((xMutex))
#endif
- 形参:互斥量句柄。
- 返回值:pdTRUE、errQUEUE_EMPTY。
使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次。
/* 使用示例 */
SemaphoreHandle_t xMutex = NULL;
void vATask(void* pvParameters)
{
/* 创建一个递归互斥量用于保护共享资源 */
xMutex = xSemaphoreCreateRecursiveMutex();
}
void vAnotherTask(void* pvParameters)
{
if ( xMutex != NULL ) {
/* 尝试获取递归互斥量,如果不可用则等待 10 个 ticks */
if(xSemaphoreTakeRecursive(xMutex,( TickType_t ) 10 )== pdTRUE) {
/* 获取到递归信号量,可以访问共享资源 */
/* ... 其他功能代码 */
/* 重复获取递归互斥量 */
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
/* 释放递归互斥量,获取了多少次就要释放多少次 */
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
/* 现在递归互斥量可以被其他任务获取 */
} else {
/* 没能成功获取互斥量,所以不能安全的访问共享资源 */
}
}
}
4.模拟优先级翻转现象
思路:
在 FreeRTOS 中创建了三个任务与一个二值信号量, 任务分别是高优先级任务、中优先级任务、低优先级任务, 用于模拟产生优先级翻转。低优先级任务在获取信号量后,被中优先级打断,中优先级的任务执行时间较长,因为此时低优先级还未释放信号量,所以高优级任务也就无法取得信号量继续运行,此时就发生了任务优先级翻转。任务在运行中,使用串口打印出相关信息。
结果显示:
代码:
0、在FreeRTOSConfig.h
配置文件中添加以下:
#define configSUPPORT_DYNAMIC_ALLOCATION 1
1、STM32F103C8_BSP.c:
#include "stm32f10x.h" // Device header
void USART1_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
void KEY_GPIO_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void USART1_SendByte(uint8_t byte){
USART_SendData(USART1, byte);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void USART1_SendString(char* str)
{
uint16_t i;
for(i = 0; str[i] != '\0'; i++){
USART1_SendByte(*(str+i));
}
}
2、main.c:
#include "stm32f10x.h" // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"
#include "semphr.h"
static void STM32F103_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART1_Init();
}
/* 定义二值信号量句柄 */
SemaphoreHandle_t BinarySem_Handle = NULL;
/* 声明任务 */
void LowPriority_Task(void);
void MiddlePriority_Task(void);
void HighPriority_Task(void);
int main(void)
{
STM32F103_Init();
taskENTER_CRITICAL();
/* 二值信号量 */
BinarySem_Handle = xSemaphoreCreateBinary();
xSemaphoreGive(BinarySem_Handle);
/* 创建任务 */
xTaskCreate((TaskFunction_t)LowPriority_Task, "LowPriority_Task",512, NULL, 2, NULL);
xTaskCreate((TaskFunction_t)MiddlePriority_Task, "MiddlePriority_Task",512, NULL, 3, NULL);
xTaskCreate((TaskFunction_t)HighPriority_Task, "HighPriority_Task",512, NULL, 4, NULL);
vTaskStartScheduler();
taskEXIT_CRITICAL();
while(1);
}
void LowPriority_Task(void)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;
while(1){
USART1_SendString("LowPriority_Task ======> Obtaining semaphores.\n");
xReturn = xSemaphoreTake(BinarySem_Handle, portMAX_DELAY);
/* 获取信号量成功,则任务开始执行后续处理 */
if ( xReturn == pdTRUE )
USART1_SendString("LowPriority_Task Runing\n\n");
/* 模拟低优先级任务占用信号量, 发起任务调度 */
for (i=0; i < 2000000; i++){
taskYIELD();
}
USART1_SendString("LowPriority_Task ======> Release Semaphore.\n");
xReturn = xSemaphoreGive(BinarySem_Handle);
vTaskDelay(500);
}
}
void MiddlePriority_Task(void)
{
while(1){
USART1_SendString("MiddlePriority_Task Runing\n\n");
vTaskDelay(500);
}
}
void HighPriority_Task(void)
{
BaseType_t xReturn = pdTRUE;
while(1){
USART1_SendString("HighPriority_Task ======> Obtaining semaphores.\n");
xReturn = xSemaphoreTake(BinarySem_Handle, portMAX_DELAY);
/* 获取信号量成功,则任务开始执行后续处理 */
if (xReturn == pdTRUE)
USART1_SendString("HighPriority_Task Runing\n\n");
/* 释放二值信号量 */
xReturn = xSemaphoreGive( BinarySem_Handle );
vTaskDelay(500);
}
}
5.互斥量使用示例
示例: 基于优先级翻转,测试互斥量的优先级继承释放有效。
最终结果:
代码:
0、在FreeRTOSConfig.h
配置文件中添加以下:
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configUSE_MUTEXES 1
1、STM32F103C8_BSP.c:和上面一样。
2、main.c:与上面的差不多,将二值信号量改为互斥量即可。
#include "stm32f10x.h" // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"
#include "semphr.h"
static void STM32F103_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART1_Init();
}
/* 定义互斥量句柄 */
SemaphoreHandle_t MuxSem_Handle = NULL;
/* 声明任务 */
void LowPriority_Task(void);
void MiddlePriority_Task(void);
void HighPriority_Task(void);
int main(void)
{
STM32F103_Init();
taskENTER_CRITICAL();
/* 互斥量 */
MuxSem_Handle = xSemaphoreCreateMutex();
xSemaphoreGive(MuxSem_Handle);
/* 创建任务 */
xTaskCreate((TaskFunction_t)LowPriority_Task, "LowPriority_Task",512, NULL, 2, NULL);
xTaskCreate((TaskFunction_t)MiddlePriority_Task, "MiddlePriority_Task",512, NULL, 3, NULL);
xTaskCreate((TaskFunction_t)HighPriority_Task, "HighPriority_Task",512, NULL, 4, NULL);
vTaskStartScheduler();
taskEXIT_CRITICAL();
while(1);
}
void LowPriority_Task(void)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;
while(1){
USART1_SendString("LowPriority_Task ======> Obtaining semaphores.\n");
xReturn = xSemaphoreTake(MuxSem_Handle, portMAX_DELAY);
/* 获取信号量成功,则任务开始执行后续处理 */
if ( xReturn == pdTRUE )
USART1_SendString("LowPriority_Task Runing\n\n");
/* 模拟低优先级任务占用信号量, 发起任务调度 */
for (i=0; i < 2000000; i++){
taskYIELD();
}
USART1_SendString("LowPriority_Task ======> Release Semaphore.\n");
xReturn = xSemaphoreGive(MuxSem_Handle);
vTaskDelay(1000);
}
}
void MiddlePriority_Task(void)
{
while(1){
USART1_SendString("MiddlePriority_Task Runing\n\n");
vTaskDelay(1000);
}
}
void HighPriority_Task(void)
{
BaseType_t xReturn = pdTRUE;
while(1){
USART1_SendString("HighPriority_Task ======> Obtaining semaphores.\n");
xReturn = xSemaphoreTake(MuxSem_Handle, portMAX_DELAY);
/* 获取信号量成功,则任务开始执行后续处理 */
if (xReturn == pdTRUE)
USART1_SendString("HighPriority_Task Runing\n\n");
/* 释放二值信号量 */
xReturn = xSemaphoreGive(MuxSem_Handle);
vTaskDelay(1000);
}
}
END