1.为什么引入互斥量
普通的信号量在任务之间同步时,会出现优先级翻转问题,如下:
执行流程如下:
1)此时,任务就绪列表中,低优先级任务L的优先级最高,因而运行,低任务有个共享信号量,但还未释放;
2)高优先级的任务H得到CPU的运行权,开始运行H,其中H任务也要获得共享信号量,但因为L任务没有释放且被挂起,那么任务H因得不到信号量而挂起;
3)任务M此时被唤醒,H任务被挂起,因而在此时的就绪任务列表中,M任务优先级最高,从而得到执行;
4)M任务执行结束后,延时引起任务调度,那么L任务得到CPU的运行权从而接着上次的L任务继续执行,共享信号量得到释放;
5)H任务得到信号量从而立即被唤醒,此时任务就绪表中的优先级也最高从而立即执行。
可见,以上任务在执行中,任务H因得不到信号量而被任务M抢占,任务H也在等待任务L的运行结束。也就是说一旦任务H去获取信号量,那么就会出现执行顺序:task_M>task_L>task_H,这样任务H的优先级反而比任务M和任务L的优先级低。这在实时操作系统中是不允许的。
2.互斥量的执行过程
因而引入互斥量,互斥量也是二值信号量的一种,也是只有0和1的值存在。但是互斥量多了一个特性,继承优先级,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。
所谓优先级继承,即高优先级的任务在获取低优先级的任务中的互斥量时,互斥量会将低优先级的任务提高到高优先级任务所在的优先级。并且将原来高优先级的任务挂起,启动任务调度,而提高到高优先级的任务在就绪列表中因优先级最高而得到优先执行,执行结束后恢复原先的任务的优先级。
互斥量的具体执行过程,如图2所示
1)低优先级任务L在执行,且还未释放互斥量
2)此时高优先级任务被唤醒获得优先执行权,执行中需要获取互斥量,因为低优先级任务L暂未释放而将自己的任务挂起,同时将自己高优先级继承给低优先级,即将低优先级的任务拉高,从而在就绪列表中低优先级的任务优先级最高;
3)低优先级释放互斥量后,原高优先即任务马上得到互斥量并执行。园低优先级的任务也恢复到原来的低优先级。
3.互斥量的特点
1)互斥量会将低优先级的任务从高优先级继承过来,即优先级继承;
2)本质也是一个二值信号量,区别于二值信号量是优先级继承。
3)优先级继承并不能完全的消除优先级翻转, 它只是尽可能的降低优先级翻转带来的影响。
硬实时应用应该在设计之初就要避免优先级翻转的发生。
4)互斥信号量不能用于中断服务函数中,
原因如下:
● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态
4.demo分析
#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "event_groups.h"
#include "semphr.h"
#define START_TASK_PRIO 1
#define START_STACK_SIZE 128
TaskHandle_t startTask_handler; //开始任务句柄
static void start_task(void *param);
#define HIGH_TASK_PRIO 3
#define HIGH_TASK_STACK_SIZE 128
TaskHandle_t high_task_handler; //高优先级任务句柄
static void high_sem_task(void *pvPara);
#define MIDDLE_TASK_PRIO 2
#define MIDDLE_TASK_STACK_SIZE 128
TaskHandle_t middle_task_handler; //中优先级任务句柄
static void middle_sem_task(void *pvPara);
#define LOW_TASK_PRIO 1
#define LOW_TASK_STACK_SIZE 128
TaskHandle_t low_task_handler; //低优先级任务句柄
static void low_sem_task(void *pvPara);
uint16_t count = 0;
void APP_task(void)
{
xTaskCreate((TaskFunction_t)start_task,
(const char *)"start_task",
(uint16_t)START_STACK_SIZE,
NULL,
(UBaseType_t)START_TASK_PRIO,
(TaskHandle_t *)&startTask_handler);
vTaskStartScheduler(); //开启任务调度
}
static void start_task(void *param)
{
/*创建一个mutex*/
SemaphoreHandle_t key_mutex = xSemaphoreCreateMutex();
taskENTER_CRITICAL(); //进入临界区
//创建获取event的任务
xTaskCreate((TaskFunction_t )high_sem_task,
(const char* )"high_mutex",
(uint16_t )HIGH_TASK_STACK_SIZE,
(void* )key_mutex,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&high_task_handler);
//创建设置event的任务
xTaskCreate((TaskFunction_t )middle_sem_task,
(const char* )"middle_mutex",
(uint16_t )MIDDLE_TASK_STACK_SIZE,
(void * )key_mutex,
(UBaseType_t )MIDDLE_TASK_PRIO,
(TaskHandle_t* )&middle_task_handler);
xTaskCreate((TaskFunction_t )low_sem_task,
(const char * )"low_mutex",
(uint16_t )LOW_TASK_STACK_SIZE,
(void * )key_mutex,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&low_task_handler);
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*
high task
*/
static void high_sem_task(void *param)
{
//引用key sem
SemaphoreHandle_t key_mutex = (SemaphoreHandle_t)param;
while(1){
xSemaphoreTake(key_mutex, portMAX_DELAY);
printf("\r\n receive low\r\n");
printf("high task is running!\r\n");
xSemaphoreGive(key_mutex);
vTaskDelay(1000);
}
}
/*
middle task
*/
static void middle_sem_task(void *param)
{
while(1){
LED1 = ~LED1;
printf("mid task is running!\r\n");
vTaskDelay(1000);
}
}
/*
low task
*/
static void low_sem_task(void *param)
{
int times = 0;
//引用key mutex
SemaphoreHandle_t key_mutex = (SemaphoreHandle_t)param;
while(1){
xSemaphoreTake(key_mutex, portMAX_DELAY);
printf("low task is running!\r\n");
for( times = 0; times < 20000; times++){
taskYIELD(); //发起任务调度
}
xSemaphoreGive(key_mutex);
printf("send low");
vTaskDelay(1000);
}
}
代码执行结果
可见每次低优先级的任务释放互斥量后,高优先级的任务可以马上执行,中等任务不会抢占低优先级的任务,中优先级任务只有在高优先级任务执行完后才能执行。