文章目录
UCOS-Ⅲ:互斥量
一、互斥量基本概念
上一节我分享了有关UCOS-Ⅲ信号量的知识,对信号量有了一个初步的认识,这一节主要分享UCOS-Ⅲ互斥量有关的知识,互斥量的本质和信号量相似,都是用于资源的访问限制,但信号量是多值信号量,允许多个任务获取同一个资源运行,在面对临界资源时可以通过设置二值信号量的方式来进行互斥访问,但在上一章讲到,二值信号量的引入会导致优先级反转的发生,高优先级被低优先级抢占,在本身就强调优先级顺序的UCOS里面,这是一件非常危险的情况,所以UCOS引入了互斥量这个概念来作为二值信号量的修补,用于临界资源的访问,任意时刻互斥量的状态只有两种,开锁或着闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权,不允许其他任务获取。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。
互斥信号量结构体
成员 | 作用 |
---|---|
OwnerTCBPtr | 这个元素记录了唯一拥有mutex 的任务控制块指针。 |
OwnerOrignalPrio | 在任务准备等待mutex,即调用OSMutexPend 的时候,系统会调整拥有mutex 的任务的优先级,当任务发布信号量的时候,要还原拥有mutex 的任务的优先级。这个变量就保存了优先级改变之前的优先级,以便还原时候的操作。 |
OwnerNestingCtr | 任务不仅仅可以占用mutex,还可以多层嵌套,即是当某一得到了Mutex 的使用权,还可以继续使用OSMutexPend 函数进行获取信号,每使用一次OSMutexPend,这个元素就会加1,如果其他任务要获取这个mutex,需要拥有这个mutex 的任务多次调用OSMutexPost 直到OwnerNestingCtr 为0 才可以。可以说OwnerNestingCtr 数值越大,表示任务对整个mutex 拥有程度越高。 |
二、调用API
创建函数OSMutexCreate()
函数OSMutexCreate 用来创建mutex
void OSMutexCreate (OS_MUTEX *p_mutex, //互斥信号量指针
CPU_CHAR *p_name, //取信号量的名称
OS_ERR *p_err) //返回错误类型
参数 | 作用 |
---|---|
p_mutex | 指向mutex 变量的指针。 |
p_name | 指向mutex 变量名字字符串的指针。 |
p_err | 指向返回错误类型指针,主要有以下几种类型。(只包含部分) |
返回错误类型
错误类型 | 作用 |
---|---|
OS_ERR_CREATE_ISR | 任务企图在中断中引用mutex 创建函数。 |
OS_ERR_NAME | 参数p_name 是空指针。 |
OS_ERR_OBJ_CREATED | mutex 变量已经被创建,但是OSMutexCreate 函数中没有涉及这个错误的相关代码。 |
OS_ERR_OBJ_PTR_NULL | 变量p_mutex 是空指针。 |
注:OS_ERR_NONE为无错误,所有的API调用成功都会返回,不多赘述
使用实例:
OS_MUTEX mutex; //声明互斥量
/* 创建互斥量 mutex */
OSMutexCreate ((OS_MUTEX *)&mutex, //指向互斥量变量的指针
(CPU_CHAR *)"Mutex For Test", //互斥量的名字
(OS_ERR *)&err); //错误类型
删除互斥量函数OSMutexDel()
OSSemDel()用于删除一个互斥量,互斥量删除函数是根据互斥量结构(互斥量句柄)直接删除的,删除之后这个互斥量的所有信息都会被系统清空,而且不能再次使用这个互斥量了,但是需要注意的是,如果某个互斥量没有被定义,那也是无法被删除的,如果有任务阻塞在该互斥量上,那么尽量不要删除该互斥量。想要使用互斥量删除函数就必须将OS_CFG_MUTEX_DEL_EN 宏定义配置为1,
OS_OBJ_QTY OSMutexDel (OS_MUTEX *p_mutex, //互斥信号量指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
参数
参数 | 功能 |
---|---|
p_mutex | 指向mutex 变量指针。 |
opt | 选项可分为以下两种。 |
p_err | 指向返回错误类型的指针,主要有以下几种类型。(只包含部分) |
OPT选项
选项 | 功能 |
---|---|
OS_OPT_DEL_NO_PEND | 要在mutex 等待列表上没有等待任务的时候才可以删除mutex。 |
OS_OPT_DEL_ALWAYS | 不管mutex 等待列表上是否有等待的任务都直接删除mutex。 |
返回错误类型
错误类型 | 功能 |
---|---|
OS_ERR_TASK_WAITING | 参数opt 是OS_OPT_DEL_NO_PEND,但是mutex 等待列表上有等待的任务。 |
OS_ERR_STATE_INVALID | 在还原拥有mutex 任务优先级的时候是根据任务的状态来的,如果检测到任务的状态超出范围即返回这个错误。 |
OS_ERR_DEL_ISR | 从中断中调用删除函数。 |
使用实例:
OS_SEM mutex;; //声明互斥量
OS_ERR err;
/* 删除互斥量mutex*/
OSMutexDel ((OS_MUTEX *)&mutex, //指向互斥量的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err); //返回错误类型
获取互斥量函数OSMutexPend()
互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。函数OSMutexPend 用来等待或者获取mutex,当mutex 被占用的时候就等待,如果没有在等待就直接获取,下面介绍下这个函数的用法和源码。
void OSMutexPend (OS_MUTEX *p_mutex, //互斥信号量指针
OS_TICK timeout, //超时时间(节拍)
OS_OPT opt, //选项
CPU_TS *p_ts, //时间戳
OS_ERR *p_err) //返回错误类型
参数
参数 | 功能 |
---|---|
p_mutex | 指向mutex 变量指针。 |
timeout | 如果任务一开始获取不到信号量等待的节拍数。0 表示无限期地等待。 |
opt | 选项可分为以下两种。 |
p_ts | 指向等待的信号量被删除,等待被强制停止,等待超时等情况时的时间戳的指针。这个参数输入空指针表示不想获取时间戳。 |
p_err | 指向返回错误类型的指针,主要有以下几种类型。(只包含部分) |
OPT配置
选项 | 功能 |
---|---|
OS_OPT_PEND_BLOCKING | 一开始获取不到信号量就阻塞任务,阻塞的时候根据参数timeout 决定。 |
OS_OPT_PEND_NON_BLOCKING | 一开始获取不到信号量就退出函数,继续运行任务。 |
错误类型
错误类型 | 含义 |
---|---|
OS_ERR_MUTEX_OWNER | 信号量已经被占用。 |
OS_ERR_PEND_ABORT | 返回的时候不是因为获得mutex,而是被强制解除等待状态。 |
OS_ERR_PEND_ISR | 从中断中调用等待函数。 |
OS_ERR_PEND_WOULD_BLOCK | 没有获取到mutex,输入的参数选项是OS_OPT_PEND_NON_BLOCKING 的时候。 |
OS_ERR_SCHED_LOCKED | 调度器被锁住了。 |
OS_ERR_TIMEOUT | 等待mutex 超时了 |
使用实例
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); //返回错误类
释放互斥量函数OSMutexPost()
互斥量的释放只能在任务中,不允许在中断中释放互斥量。
void OSMutexPost (OS_MUTEX *p_mutex, //互斥信号量指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
参数
参数 | 功能 |
---|---|
p_mutex | 指向Mutex 变量的指针。 |
opt | 提交Mutex 时的选项,可以为以下两种。 |
p_err | 指向返回错误类型指针,主要有以下几种类型。(只包含部分) |
OPT选项
选项 | 功能 |
---|---|
OS_OPT_POST_NO_SCHED | 发布mutex 后不要进行调度。 |
OS_OPT_POST_NONE | 这个是默认的选项,发布mutex 后进行任务调度。 |
错误类型
错误 | 功能 |
---|---|
OS_ERR_MUTEX_NESTING | Mutex 被发布后,仍然处于嵌套中,即前面提到的元素OwnerNestingCtr 还不为0,Mutex 还不能为其他任务占有。 |
OS_ERR_MUTEX_NOT_OWNER | 只有拥有mutex 的任务才可以释放这个mutex。当在一个不是拥有mutex 的任务中释放mutex 的时候,返回这个错误。 |
OS_ERR_OBJ_PTR_NULL | 参数p_mutex 是空指针。 |
OS_ERR_OBJ_TYPE | 参数p_mutex 指向的内核变量类型不是mutex。 |
OS_ERR_POST_ISR | ISR 不是任务,不可能拥有一个mutex,所以也不能在中断中提交mutex。 |
使用实例
1 OS_MUTEX mutex; //声明互斥量
2
3 OS_ERR err;
4
5 OSMutexPend ((OS_MUTEX *)&mutex, //申请互斥量 mutex
6 (OS_TICK )0, //无期限等待
7 (OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到互斥量就堵塞任务
8 (CPU_TS *)0, //不想获得时间戳
9 (OS_ERR *)&err); //返回错误类
强制解除等待OSMutexPendAbort
函数OSMutexPendAbort 用来强制解除等待mutex,
OS_OBJ_QTY OSMutexPendAbort (OS_MUTEX *p_mutex, //互斥信号量指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
参数
参数 | 功能 |
---|---|
p_mutex | 指向mutex 变量的指针。 |
opt | 可以有以下几种选项。 |
p_err | 指向返回错误类型的指针,主要有以下几种类型。 |
OPT功能
选项 | 功能 |
---|---|
OS_OPT_PEND_ABORT_1 | 只是强制解除等待mutex 的最高任务。 |
OS_OPT_PEND_ABORT_ALL | 将等待mutex 的所有任务都强制解除等待状态。 |
错误类型
错误类型 | 功能 |
---|---|
OS_ERR_NONE | 没有错误。 |
OS_ERR_OBJ_PTR_NULL | 参数p_mutex 是个空指针。 |
OS_ERR_OBJ_TYPE | 参数p_mutex 指向的变量指向的内核变量类型不是mutex。 |
OS_ERR_OPT_INVALID | 参数opt 不符合要求。 |
OS_ERR_PEND_ABORT_ISR | 企图在中断中强制解除等待mutex。 |
OS_ERR_PEND_ABORT_NONE | 没有任务在等待。 |
返回值
解除等待任务状态的个数。
所有函数对比阅读后发现mutex 跟多值信号量的主要区别是:1.mutex 操作的时候需
要进行优先级继承,优先级继承不仅仅是比较下优先级然后对其进行修改,还要对任务所
在的就绪列表或者等待列表,因为这些表是根据优先级进行排列的,在删除mutex 的时候
也要将任务的优先级还原回来。2.mutex 只能被一个任务占用。
三、优先级反转解决原理
优先级继承的本质操作:如果有比拥有mutex 更高优先级的任务准备等待获取mutex,那么就要将拥有mutex 的任务优先级提高到跟准备等待mutex 的任务类似。优先级改变需要修改内核相关内容,主要是对各种列表进行修改,因为通过前面的解析我们知道,就绪列表等都是根据优先级来排列的。当任务插在就绪列表的情况,首先将任务脱离就绪列表,然后更改优先级后重新插入就绪列表,这样子就既实现了优先级继承,又更新了优先级在就绪列表中的位置。
四、使用实例
创建三个优先级的任务,任务一优先级低,而任务二中等,任务三优先级最高,在任务一获得信号量的时候,我们看一看,任务二能不能再抢占任务一,导致优先级反转
创建互斥量
/* 创建互斥信号量 mutex */
OSMutexCreate ((OS_MUTEX *)&TestMutex, //指向信号量变量的指针
(CPU_CHAR *)"Mutex For Test", //信号量的名字
(OS_ERR *)&err); //错误类型
创建3个子任务
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);
任务一
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); //返回错误类型
macLED1_TOGGLE ();
OSTimeDlyHMSM (0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
}
任务二
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) {
printf("AppTaskLed2 Running\n");
macLED2_TOGGLE ();
OSTimeDlyHMSM (0,0,0,200,OS_OPT_TIME_PERIODIC,&err);
}
任务三
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); //返回错误类型
macLED3_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);
}
编译运行,实验现象如下
可以看到任务一获取到释放的时间内任务二没有抢占,优先级反转没用出现