一、互斥量简介
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或任务与中断之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号相当于一个钥匙,当任务想要使用资源的时候便就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙使用资源。
二、优先级继承制
图1演示的任务发生优先级翻转的情况。假设图中三个任务分别为L,M,H,且任务优先级L<M<H。正常运行情况下任务优先级越高,它获取cpu的使用权的优先级更高,也就是H可以打断L,与M的对cpu的使用。假设系统中有一个资源被保护了,只允许一个任务对其进行访问,此时在访问的任务如果为L,H任务无法打断L对该资源的访问,H 任务则因为申请不到资源而进入阻塞态,于是就出现了优先级高H的任务在等待优先级低的任务L ,这就是产生了优先级翻转的现象。如果L任务在执行任务的过程中,此时M任务刚好被唤醒,因为M任务的优先级高于L任务,这是M任务就会抢占L任务对CPU的使用权,直到任务M执行完毕,再把CPU的使用权归还给L任务,L任务才得以继续进行,等L任务执行完毕之后归还资源的使用权,此时H任务才能开始执行。这个过程,本 来是最高优先级的 H 任务,在等待了更低优先级的 L 任务与 M 任务,其阻塞的时间是 M 任务运行时间+L 任务运行时间,这只是只有 3 个任务的系统,假如很多个这样子的任务打 断最低优先级的任务,那这个系统最高优先级任务岂不是崩溃了,这个现象是绝对不允许 出现的,高优先级的任务必须能及时响应。所以,没有优先级继承的情况下,使用资源保 护,其危害极大。
图1优先级翻转图解
图1(1) L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并 未执行完毕,此时临界资源还未释放。
图1(2):这个时刻 H 任务也要对该临界资源进行访问,但 L 任务还未释放资源, 由于保护机制,H 任务进入阻塞态,L 任务得以继续运行,此时已经发生了优先级翻转现 象。
图 1(3):某个时刻 M 任务被唤醒,由于 M 任务的优先级高于 L 任务, M 任务抢 占了 CPU 的使用权,M任务开始运行,此时 L 任务尚未执行完,临界资源还没被释放。
图 1(4):M 任务运行结束,归还 CPU 使用权,L 任务继续运行。
图 1(5):L任务运行结束,释放临界资源,H 任务得以对资源进行访问,H 任务开 始运行。
图2优先级继承制
图2(1)L 任务正在使用某临界资源,L 任务正在使用某临界资源, H 任务被唤醒, 执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。
图 2(2):某一时刻 H 任务也要对该资源进行访问,由于保护机制,H 任务进入阻 塞态。此时发生优先级继承,系统将 L 任务的优先级暂时提升到与 H 任务优先级相同,L 任务继续执行。
图 2(3):在某一时刻 M 任务被唤醒,由于此时 M 任务的优先级暂时低于 L 任务, 所以 M 任务仅在就绪态,而无法获得 CPU 使用权。(因为它此时不是对保护的临界资源进行访问,因此它不会像H任务一样进入阻塞态,如果L的任务优先级低于M任务的话)
图 2(4):L任务运行完毕,H 任务获得对资源的访问权,H 任务从阻塞态变成运行 态,此时 L 任务的优先级会变回原来的优先级。
图 2(5):当 H 任务运行完毕,M任务得到 CPU 使用权,开始执行。
图 2(6):系统正常运行,按照设定好的优先级运行。 但是使用互斥量的时候一定需要注意:在获得互斥量后,请尽快释放互斥量,同时需 要注意的是在任务持有互斥量的这段时间,不得更改任务的优先级。FreeRTOS 的优先级继 承机制不能解决优先级反转,只能将这种情况的影响降低到最小,硬实时系统在一开始设 计时就要避免优先级反转发生。
三、互斥量应用场景
互斥量适用于:可能会引起优先级翻转的情况。
递归互斥量更适用于:任务可能会多次获取互斥量的情况下,选择可以避免同一任务多次递归持有而造成的死锁问题。
四、互斥量的运作机制
当临界资源被一个任务占用时,是不允许其它任务对其进行访问。当任务获取到互斥量时,互斥量会立即变为闭锁状态,任务1由低优先级转变为高优先级防止出现CPU被抢夺的情况,其它的任务因无法获取到互斥量而无法对资源进行访问,则其它任务进入阻塞状态。当任务1运行完毕并释放共享资源,此时任务2就会立马去占取共享资源,执行任务二的任务。
五、互斥量函数接口讲解
1.互斥量创建函数 xSemaphoreCreateMutex()
xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄。该句柄的原 型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。要想使用该函数 必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1, 即开启动态内存分配,其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象 在创建的时候都默认使用动态内存分配方案,同时还需在 FreeRTOSConfig.h 中把 configUSE_MUTEXES 宏定义打开,表示使用互斥量。
SemaphoreHandle_t MuxSem_Handle;
void vATask( void * pvParameters )
{
/* 创建一个互斥量 */
MuxSem_Handle= xSemaphoreCreateMutex();
if (MuxSem_Handle!= NULL ) {
/* 互斥量创建成功 */
}
}
2.递归xSemaphoreCreateRecursiveMutex()
用于创建一个递归互斥量,递归互斥量可以被一个任务多次获取,获取多少次就需要释放多少次互斥量,且同时具有优先级继承制,避免出现优先级翻转的危害。
SemaphoreHandle_t xMutex;
void vATask( void * pvParameters )
{
/* 创建一个递归互斥量 */
xMutex = xSemaphoreCreateRecursiveMutex();
if ( xMutex != NULL ) {
/* 递归互斥量创建成功 */
}
}
3.互斥量删除函数 vSemaphoreDelete()
互斥量的本质是信号量,直接调用 vSemaphoreDelete()函数进行删除即可。
4.互斥量获取函数 xSemaphoreTake()
互斥量处于开锁状态时,任务才能获取互斥量成功。当某个任务获取到互斥量后,其它任务将无法获取该互斥量,需等待该互斥量被释放才能获取成功。反之互斥量处于闭锁状态时,任务无法获取该互斥量,该任务将被挂起,若被挂起的任务优先级高于获得互斥量的任务的优先级,则获得互斥量的任务的优先级将被临时提升,当该任务释放互斥量时便会将任务的优先级恢复原状。
void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("LowPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("LowPriority_Task Runing\n\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
LED1_TOGGLE;
vTaskDelay(1000);
}
}
5.递归互斥量获取函数 xSemaphoreTakeRecursive()
递归互斥量可以在一个任务中多次获取,当第一次获取递归互斥量时,队列结构体成 员指针 pxMutexHolder 指向获取递归互斥量的任务控制块,当任务再次尝试获取这个递归 互斥量时,如果任务就是拥有递归互斥量所有权的任务,那么只需要将记录获取递归次数 的 成 员 变 量 u.uxRecursiveCallCount 加 1 即 可 , 不 需 要 再 操 作 队 列
6.互斥量释放函数 xSemaphoreGive()
任务需要获取某个资源的时候,需要获取互斥量,在获取了互斥量之后才能对该资源进行访问,当任务使用该资源结束之后,便要释放互斥量让其它任务能够获取到互斥量来对该资源进行访问。需注意的是该函数只能在任务中使用,无法在中断中使用。使用该函数的前提是已经获取到了互斥量。
7.递归互斥量释放函数 xSemaphoreGive()
xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏。要想使用该函数必须在 头文件 FreeRTOSConfig.h 把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。互斥量和递归互斥量的最大区别在于一个递归互斥量可以被已经获取这个递归互斥量 的 任务 重复 获取, 而不 会形 成死 锁。这 个递 归调 用功能 是通 过队 列结 构体成 员 u.uxRecursiveCallCount 实现的,这个变量用于存储递归调用的次数,每次获取递归互斥量 后,这个变量加 1,在释放递归互斥量后,这个变量减 1。只有这个变量减到 0,即释放和 获取的次数相等时,互斥量才能变成有效状态,然后才允许使用 xQueueGenericSend()函数 释放一个递归互斥量。
六、互斥量实验
1.模拟优先级翻转实验
模拟二值信号量在没有优先级继承机制下出现优先级翻转造成程序紊乱的现象。
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LowPriority_TASK_PRIO 3
//任务堆栈大小
#define LowPriority_STK_SIZE 512
//任务句柄
TaskHandle_t LowPriority_Task_Handle;
//任务函数
void LowPriority_Task(void *pvParameters);
//任务优先级
#define MidPriority_TASK_PRIO 4
//任务堆栈大小
#define MidPriority_STK_SIZE 512
//任务句柄
TaskHandle_t MidPriority_Task_Handle;
//任务函数
void MidPriority_Task(void *pvParameters);
//任务优先级
#define HighPriority_TASK_PRIO 5
//任务堆栈大小
#define HighPriority_STK_SIZE 512
//任务句柄
TaskHandle_t HighPriority_Task_Handle;
//任务函数
void HighPriority_Task(void *pvParameters);
SemaphoreHandle_t BinarySem_Handle =NULL;
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS优先级翻转实验\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;
taskENTER_CRITICAL(); //进入临界区
/* 创建 BinarySem */
BinarySem_Handle = xSemaphoreCreateBinary();
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建低优先级任务
xTaskCreate((TaskFunction_t )LowPriority_Task,
(const char* )"LowPriority_Task",
(uint16_t )LowPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )LowPriority_TASK_PRIO,
(TaskHandle_t* )&LowPriority_Task_Handle);
//创建中优先级任务
xTaskCreate((TaskFunction_t )MidPriority_Task,
(const char* )"MidPriority_Task",
(uint16_t )MidPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )MidPriority_TASK_PRIO,
(TaskHandle_t* )&MidPriority_Task_Handle);
//创建高优先级任务
xTaskCreate((TaskFunction_t )HighPriority_Task,
(const char* )"HighPriority_Task",
(uint16_t )HighPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )HighPriority_TASK_PRIO,
(TaskHandle_t* )&HighPriority_Task_Handle);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//低优先级任务函数
void LowPriority_Task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
static uint32_t i=0;
while(1)
{
printf("LowPriority_Task 获取信号量\n");
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if( xReturn == pdTRUE )
printf("LowPriority_Task Running\n\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用信号量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放信号量!\r\n");
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
LED2=!LED2;
vTaskDelay(500);
}
}
//中优先级任务函数
void MidPriority_Task(void *pvParameters)
{
while(1)
{
printf("MidPriority_Task Running\n");
vTaskDelay(500);
}
}
//高优先级任务函数
void HighPriority_Task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE;
while(1)
{
printf("HighPriority_Task 获取信号量\n");
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Running\n");
LED2=!LED2;
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
vTaskDelay(500);
}
}
2.互斥量实验
具有优先级继承机制,能够很好的避免因为优先级问题使得任务因争夺CPU的使用权造成的程序紊乱的问题。
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LowPriority_TASK_PRIO 3
//任务堆栈大小
#define LowPriority_STK_SIZE 512
//任务句柄
TaskHandle_t LowPriority_Task_Handle;
//任务函数
void LowPriority_Task(void *pvParameters);
//任务优先级
#define MidPriority_TASK_PRIO 4
//任务堆栈大小
#define MidPriority_STK_SIZE 512
//任务句柄
TaskHandle_t MidPriority_Task_Handle;
//任务函数
void MidPriority_Task(void *pvParameters);
//任务优先级
#define HighPriority_TASK_PRIO 5
//任务堆栈大小
#define HighPriority_STK_SIZE 512
//任务句柄
TaskHandle_t HighPriority_Task_Handle;
//任务函数
void HighPriority_Task(void *pvParameters);
SemaphoreHandle_t MuxSem_Handle =NULL;
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS互斥量实验\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建低优先级任务
xTaskCreate((TaskFunction_t )LowPriority_Task,
(const char* )"LowPriority_Task",
(uint16_t )LowPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )LowPriority_TASK_PRIO,
(TaskHandle_t* )&LowPriority_Task_Handle);
//创建中优先级任务
xTaskCreate((TaskFunction_t )MidPriority_Task,
(const char* )"MidPriority_Task",
(uint16_t )MidPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )MidPriority_TASK_PRIO,
(TaskHandle_t* )&MidPriority_Task_Handle);
//创建高优先级任务
xTaskCreate((TaskFunction_t )HighPriority_Task,
(const char* )"HighPriority_Task",
(uint16_t )HighPriority_STK_SIZE,
(void* )NULL,
(UBaseType_t )HighPriority_TASK_PRIO,
(TaskHandle_t* )&HighPriority_Task_Handle);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//低优先级任务函数
void LowPriority_Task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
static uint32_t i=0;
while(1)
{
printf("LowPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if( xReturn == pdTRUE )
printf("LowPriority_Task Running\n\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
LED2=!LED2;
vTaskDelay(500);
}
}
//中优先级任务函数
void MidPriority_Task(void *pvParameters)
{
while(1)
{
printf("MidPriority_Task Running\n");
vTaskDelay(500);
}
}
//高优先级任务函数
void HighPriority_Task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE;
while(1)
{
printf("HighPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Running\n");
LED2=!LED2;
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
vTaskDelay(500);
}
}
在L任务获取互斥量后开始执行任务,临界资源因L占用所以串口仅显示L任务Runing,因为L任务正在占用资源因此提高了L的任务优先级保证不被M任务抢夺CPU的使用权,此时H已经调用了获取互斥量函数,当L任务释放互斥量后,串口立马显示H任务正在执行,M任务也紧跟着执行。