互斥量
一、互斥量基本概念
互斥量本质是一种特殊的二值信号量,也不具备传递数据的功能。与二值信号量不同的是:它具有优先级继承机制、支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。
任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。
二、互斥量优先级继承机制
在 uCOS 操作系统中为了降低优先级翻转问题利用了优先级继承算法。优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。
什么叫做优先级继承以及作用是什么:某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。这种机制可以确保获取该资源中的高优先级任务在阻塞等待的时间最小,并且将已经出现的“优先级翻转”危害降到最小。
三、互斥量应用场景
互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状。互斥量更适合于:
1、可能会引起优先级翻转的情况。
2、任务可能会多次获取互斥量的情况下,这样可以避免同一任务多次递归持有而造成死锁的问题。
3、保护临界资源,实现任务独占式访问。
4、对串口资源进行保护,当一个任务正在使用串口的时候,另一个任务则无法使用串口,等到任务使用串口完毕之后,另外一个任务才能获得串口的使用权。
注:互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,而在中断的上下文环境中毫无意义。
四、互斥量运作机制
互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。
特点:
1、使用互斥量对资源进行保护时,如果资源被占用的时候,无论是什么优先级的任务想要使用该资源都会被阻塞。
2、假如正在使用该资源的任务 1 比阻塞中的任务 2 的优先级还低,那么任务1 将被系统临时提升到与高优先级任务 2 相等的优先级(任务 1 的优先级从 L 变成 H),任务 1 使用完资源之后,释放互斥量,此时任务 1 的优先级会从 H 变回原来的 L。
五、互斥量创建流程
1、定义互斥量
在app.c文件中定义
/* LOCAL DEFINE */
OS_MUTEX TestMutex;
2、创建互斥量
在app.c文件中的起始任务AppTaskStart ()中创建
/* 创建互斥信号量 mutex */
OSMutexCreate ((OS_MUTEX *)&TestMutex, //指向信号量变量的指针
(CPU_CHAR *)"Mutex For Test", //信号量的名字
(OS_ERR *)&err); //错误类型
六、互斥量接口函数
1、创建互斥量函数OSMutexCreate()
void OSMutexCreate (OS_MUTEX *p_mutex, //互斥量指针
CPU_CHAR *p_name, //取互斥量的名称
OS_ERR *p_err) //返回错误类型
2、删除互斥量函数 OSMutexDel()
互斥量删除函数是根据互斥量结构(互斥量句柄)直接删除的,删除之后这个互斥量的所有信息都会被系统清空,而且不能再次使用这个互斥量了,但是需要注意的是,如果某个互斥量没有被定义,那也是无法被删除的,如果有任务阻塞在该互斥量上,那么尽量不要删除该互斥量。想要使用互斥量删除函数就必须将OS_CFG_MUTEX_DEL_EN 宏定义配置为 1。该宏位于os_cfg.h文件中。
OS_OBJ_QTY OSMutexDel (OS_MUTEX *p_mutex, //互斥量指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
opt | 含义 |
---|---|
OS_OPT_DEL_NO_PEND) | 需要在没有任务等待互斥量的条件下删除互斥量。判断目前等待列表中是否有任务,没有则删除,有则返回有任务在等待的错误 |
OS_OPT_DEL_ALWAYS | 如果等待列表有任务,则要把等待列表中的任务恢复为就绪态,再删除;如果没有,直接删除 |
例:
OS_SEM mutex;; //声明互斥量
OS_ERR err;
/* 删除互斥量 mutex*/
OSMutexDel ((OS_MUTEX *)&mutex, //指向互斥量的指针
OS_OPT_DEL_NO_PEND, //在没有任务等待互斥量的条件下删除互斥量
(OS_ERR *)&err); //返回错误类型
3、获取互斥量函数 OSMutexPend()
当互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。
void OSMutexPend (OS_MUTEX *p_mutex, //互斥量指针
OS_TICK timeout, //超时时间(节拍)超值时间为0时,表示永久等待
OS_OPT opt, //选项
CPU_TS *p_ts, //时间戳,
OS_ERR *p_err) //返回错误类型
opt | 含义 |
---|---|
OS_OPT_PEND_BLOCKING | 阻塞调用方式,直到信号量可用或发生超时 |
OS_OPT_PEND_NON_BLOCKING | 不阻塞调用方式,如果信号量不可用,则ossempende()不会阻塞,而是返回给调用者使用适当的错误代码; |
p_ts
是一个指向时间戳的指针,该时间戳获取互斥锁被发布、挂起被中止的时间,或者互斥锁被删除。如果传递一个NULL指针(即(CPU_TS )0),调用者将不会收到的时间戳。换句话说,传递NULL指针是表示不需要获取时间。当一个任务需要知道互斥锁是什么时候发布的,时间戳是很有用的,或者在发布互斥锁后,任务恢复需要多长时间。在后一种情况下,用户必须调用OS_TS_GET(),并计算当前值与的差值时间戳和p_ts。换句话说:delta = OS_TS_GET() - *p_ts;
例:
OS_MUTEX mutex; //声明互斥量
OS_ERR err;
OSMutexPend ((OS_MUTEX *)&mutex, //申请互斥量 mutex
(OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到互斥量就堵塞任务
(CPU_TS *)0, //不想获得时间戳
(OS_ERR *)&err); //返回错误类
4、释放互斥量函数 OSMutexPost()
任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。uCOS 给我们提供了互斥量释放函数 OSMutexPost(),任务可以调用该函数进行释放互斥量,表示我已经用完了,别人可以申请使用,但是要注意的是,互斥量的释放只能在任务中,不允许在中断中释放互斥量。
使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用OSMutexPost()函数时会释放一次互斥量,当互斥量的成员变量 OwnerNestingCtr 为 0 的时候,互斥量状态才会成为开锁状态,等待获取该互斥量的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被完全释放后,任务的优先级将恢复为原本设定的优先级。
void OSMutexPost (OS_MUTEX *p_mutex, //互斥量指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
opt | 含义 |
---|---|
OS_OPT_POST_NONE | 释放互斥锁后希望其他等待锁的任务(最高优先级且就绪)得到立即执行 |
OS_OPT_POST_NO_SCHED | 释放互斥锁但是不进行任务调度 |
例:
OS_MUTEX mutex; //声明互斥互斥量
OS_ERR err;
OSMutexPost ((OS_MUTEX *)&mutex, //释放互斥互斥量 mutex
(OS_OPT )OS_OPT_POST_NONE, //进行任务调度
(OS_ERR *)&err); //返回错误类型
七、例程
将程序编译好,用 USB 线连接电脑和开发板的 USB 接口(对应丝印为 USB 转串口),用 DAP 仿真器把配套程序下载到野火 STM32 开发板(具体型号根据你买的板子而定,每个型号的板子都配套有对应的程序),在电脑上打开串口调试助手,然后复位开发板就可以在调试助手中看到串口的打印信息,它里面输出了信息表明任务正在运行中,并且很明确可以看到在低优先级任务运行的时候,中优先级任务无法抢占低优先级的任务,这是因为互斥量的优先级继承机制,从而最大程度降低了优先级翻转产生的危害
#include <includes.h>
OS_MUTEX TestMutex;
static OS_TCB AppTaskStartTCB;
static OS_TCB AppTaskLed1TCB;
static OS_TCB AppTaskLed2TCB;
static OS_TCB AppTaskLed3TCB;
static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
static CPU_STK AppTaskLed1Stk [ APP_TASK_LED1_STK_SIZE ];
static CPU_STK AppTaskLed2Stk [ APP_TASK_LED2_STK_SIZE ];
static CPU_STK AppTaskLed3Stk [ APP_TASK_LED3_STK_SIZE ];
static void AppTaskStart (void *p_arg);
static void AppTaskLed1 ( void * p_arg );
static void AppTaskLed2 ( void * p_arg );
static void AppTaskLed3 ( void * p_arg );
int main (void)
{
OS_ERR err;
OSInit(&err); /* Init uC/OS-III. */
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, /* Create the start task */
(CPU_CHAR *)"App Task Start",
(OS_TASK_PTR ) AppTaskStart,
(void *) 0,
(OS_PRIO ) APP_TASK_START_PRIO,
(CPU_STK *)&AppTaskStartStk[0],
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSStart(&err); /* Start multitasking (i.e. give control to uC/OS-III). */
}
static void AppTaskStart (void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
BSP_Init(); /* Initialize BSP functions */
CPU_Init();
cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine nbr SysTick increments */
OS_CPU_SysTickInit(cnts); /* Init uC/OS periodic time src (SysTick). */
Mem_Init(); /* Initialize Memory Management Module */
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); /* Compute CPU capacity with no task running */
#endif
CPU_IntDisMeasMaxCurReset();
/* 创建互斥信号量 mutex */
OSMutexCreate ((OS_MUTEX *)&TestMutex, //指向信号量变量的指针
(CPU_CHAR *)"Mutex For Test", //信号量的名字
(OS_ERR *)&err); //错误类型
OSTaskCreate((OS_TCB *)&AppTaskLed1TCB, /* Create the Led1 task */
(CPU_CHAR *)"App Task Led1",
(OS_TASK_PTR ) AppTaskLed1,
(void *) 0,
(OS_PRIO ) APP_TASK_LED1_PRIO,
(CPU_STK *)&AppTaskLed1Stk[0],
(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskCreate((OS_TCB *)&AppTaskLed2TCB, /* Create the Led2 task */
(CPU_CHAR *)"App Task Led2",
(OS_TASK_PTR ) AppTaskLed2,
(void *) 0,
(OS_PRIO ) APP_TASK_LED2_PRIO,
(CPU_STK *)&AppTaskLed2Stk[0],
(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskCreate((OS_TCB *)&AppTaskLed3TCB, /* Create the Led3 task */
(CPU_CHAR *)"App Task Led3",
(OS_TASK_PTR ) AppTaskLed3,
(void *) 0,
(OS_PRIO ) APP_TASK_LED3_PRIO,
(CPU_STK *)&AppTaskLed3Stk[0],
(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskDel ( & AppTaskStartTCB, & err );
}
static void AppTaskLed1 ( void * p_arg )
{
OS_ERR err;
static uint32_t i;
(void)p_arg;
while (DEF_TRUE) {
printf("AppTaskLed1 获取互斥量\n");
//获取 互斥量 ,没获取到则一直等待
OSMutexPend ((OS_MUTEX *)&TestMutex, //申请互斥信号量 mutex
(OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到信号量就堵塞任务
(CPU_TS *)0, //不想获得时间戳
(OS_ERR *)&err); //返回错误类型
for(i=0;i<600000;i++) //模拟低优先级任务占用互斥量
{
OSSched();//发起任务调度
}
printf("AppTaskLed1 释放互斥量\n");
OSMutexPost ((OS_MUTEX *)&TestMutex, //释放互斥信号量 mutex
(OS_OPT )OS_OPT_POST_NONE, //进行任务调度
(OS_ERR *)&err); //返回错误类型
LED1_TOGGLE;
OSTimeDlyHMSM (0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
}
}
static void AppTaskLed2 ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) {
printf("AppTaskLed2 Running\n");
OSTimeDlyHMSM (0,0,0,200,OS_OPT_TIME_PERIODIC,&err);
}
}
static void AppTaskLed3 ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) {
printf("AppTaskLed3 获取互斥量\n");
//获取 互斥量 ,没获取到则一直等待
OSMutexPend ((OS_MUTEX *)&TestMutex, //申请互斥信号量 mutex
(OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到信号量就堵塞任务
(CPU_TS *)0, //不想获得时间戳
(OS_ERR *)&err); //返回错误类型
LED2_TOGGLE;
printf("AppTaskLed3 释放互斥量\n");
OSMutexPost ((OS_MUTEX *)&TestMutex, //释放互斥信号量 mutex
(OS_OPT )OS_OPT_POST_NONE, //进行任务调度
(OS_ERR *)&err); //返回错误类型
OSTimeDlyHMSM (0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
}
}