UCOS-Ⅲ:互斥量

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_CREATEDmutex 变量已经被创建,但是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_NESTINGMutex 被发布后,仍然处于嵌套中,即前面提到的元素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_ISRISR 不是任务,不可能拥有一个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);
      
    }

编译运行,实验现象如下

在这里插入图片描述

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Top嵌入式

投喂个鸡腿

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值