[TOC]

UCOS-Ⅲ:互斥量

一、互斥量基本概念

我在之前的博客中分享了有关UCOS-Ⅲ信号量的知识,使我们对信号量有了一个初步的认识,这一节主要分享UCOS-Ⅲ互斥量有关的知识,互斥量的本质和信号量相似,都是用于资源的访问限制,但信号量是多值信号量,允许多个任务获取同一个资源运行,在面对临界资源时可以通过设置二值信号量的方式来进行互斥访问,但在上一章讲到,二值信号量的引入会导致优先级反转的发生,高优先级被低优先级抢占,在本身就强调优先级顺序的UCOS里面,这是一件非常危险的情况,所以UCOS引入了互斥量这个概念来作为二值信号量的修补,用于临界资源的访问,任意时刻互斥量的状态只有两种,开锁或着闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权,不允许其他任务获取。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁

互斥信号量结构体

20210315222617

成员 作用
OwnerTCBPtr 这个元素记录了唯一拥有mutex 的任务控制块指针。
OwnerOrignalPrio 在任务准备等待mutex,即调用OSMutexPend 的时候,系统会调整拥有mutex 的任务的优先级,当任务发布信号量的时候,要还原拥有mutex 的任务的优先级。这个变量就保存了优先级改变之前的优先级,以便还原时候的操作。
OwnerNestingCtr 任务不仅仅可以占用mutex,还可以多层嵌套,即是当某一得到了Mutex 的使用权,还可以继续使用OSMutexPend 函数进行获取信号,每使用一次OSMutexPend,这个元素就会加1,如果其他任务要获取这个mutex,需要拥有这个mutex 的任务多次调用OSMutexPost 直到OwnerNestingCtr 为0 才可以。可以说OwnerNestingCtr 数值越大,表示任务对整个mutex 拥有程度越高。

二、调用API

创建函数OSMutexCreate()

函数OSMutexCreate 用来创建mutex

1
2
3
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调用成功都会返回,不多赘述

使用实例:

1
2
3
4
5
OS_MUTEX mutex; //声明互斥量
/* 创建互斥量 mutex */
OSMutexCreate ((OS_MUTEX *)&mutex, //指向互斥量变量的指针
(CPU_CHAR *)"Mutex For Test", //互斥量的名字
(OS_ERR *)&err); //错误类型

删除互斥量函数OSMutexDel()

OSSemDel()用于删除一个互斥量,互斥量删除函数是根据互斥量结构(互斥量句柄)直接删除的,删除之后这个互斥量的所有信息都会被系统清空,而且不能再次使用这个互斥量了,但是需要注意的是,如果某个互斥量没有被定义,那也是无法被删除的,如果有任务阻塞在该互斥量上,那么尽量不要删除该互斥量。想要使用互斥量删除函数就必须将OS_CFG_MUTEX_DEL_EN 宏定义配置为1,

1
2
3
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 从中断中调用删除函数。

使用实例:

1
2
3
4
5
6
7
8
OS_SEM mutex;; //声明互斥量

OS_ERR err;

/* 删除互斥量mutex*/
OSMutexDel ((OS_MUTEX *)&mutex, //指向互斥量的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err); //返回错误类型

获取互斥量函数OSMutexPend()

互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。函数OSMutexPend 用来等待或者获取mutex,当mutex 被占用的时候就等待,如果没有在等待就直接获取,下面介绍下这个函数的用法和源码。

1
2
3
4
5
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 超时了

使用实例

1
2
3
4
5
6
7
8
9
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()

互斥量的释放只能在任务中,不允许在中断中释放互斥量。

1
2
3
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
2
3
4
5
6
7
8
9
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,

1
2
3
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 的任务类似。优先级改变需要修改内核相关内容,主要是对各种列表进行修改,因为通过前面的解析我们知道,就绪列表等都是根据优先级来排列的。当任务插在就绪列表的情况,首先将任务脱离就绪列表,然后更改优先级后重新插入就绪列表,这样子就既实现了优先级继承,又更新了优先级在就绪列表中的位置。

四、使用实例

创建三个优先级的任务,任务一优先级低,而任务二中等,任务三优先级最高,在任务一获得信号量的时候,我们看一看,任务二能不能再抢占任务一,导致优先级反转

创建互斥量

1
2
3
4
/* 创建互斥信号量 mutex */
OSMutexCreate ((OS_MUTEX *)&TestMutex, //指向信号量变量的指针
(CPU_CHAR *)"Mutex For Test", //信号量的名字
(OS_ERR *)&err); //错误类型

创建3个子任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);

任务一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  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);
}

任务二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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);
}


任务三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  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);

}

编译运行,实验现象如下

20210319134000

可以看到任务一获取到释放的时间内任务二没有抢占,优先级反转没用出现

wechat