uCOSIII系统学习笔记之互斥信号量

基本概念

互斥信号量(mnutex,简称互斥量)本质也是一种信号量,没有数据传递功能,是一种特殊的多值信号量。和普通的多值信号量不同的是,它支持互斥量所有权递归访问以及防止优先级翻转(优先级继承)的特性,用于实现对临界资源的独占式处理。
先看看互斥量的结构体类型定义

struct  os_mutex {   /* Mutual Exclusion Semaphore*/
  /* ------------------ GENERIC  MEMBERS ------------------ */
    OS_OBJ_TYPE          Type;  /* Should be set to OS_OBJ_TYPE_MUTEX                     */
    CPU_CHAR            *NamePtr;  /* Pointer to Mutex Name (NUL terminated ASCII)           */
    OS_PEND_LIST         PendList;  /* List of tasks waiting on mutex                         */
#if OS_CFG_DBG_EN > 0u
    OS_MUTEX            *DbgPrevPtr;
    OS_MUTEX            *DbgNextPtr;
    CPU_CHAR            *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
    OS_TCB              *OwnerTCBPtr;//指向  mutex 占有者 的TCB
    OS_PRIO              OwnerOriginalPrio;// mutex 占有者 的优先级
    OS_NESTING_CTR       OwnerNestingCtr; /* Mutex is available when the counter is 0   */
    CPU_TS               TS;
};

在该结构体的定义中,最关键的是SPECIFIC MEMBERS中的OwnerNestingCtr。从定义的名称上讲,它记录的是互斥量拥有者(以下简称“mutex占有者”)的对mutex的嵌套计数值,代表着mutex占有者对mutex的占有程度。互斥量的两个特性互斥量所有权递归访问均是围绕该变量展开。
任意时刻互斥量的状态只有两种,开锁状态或闭锁状态。
开锁状态即mutex占有者对该互斥量的占有程度被清空(OwnerNestingCtr==0),可以被其他申请该信号量的任务(以下简称mutex申请者)使用。
闭锁即mutex占有者对该互斥量的占有程度没有被清空(OwnerNestingCtr>0),不能被其他mutex申请者使用。

基本特性

  1. 互斥量所有权
    互斥量所有权指的是mutex占有者对互斥量的所有权。只有mutex占有者才能发布提交 该互斥量,其他任务无法提交和发布。(不像多值信号量,只要任务 ”用完了“就可以直接post信号量)

  2. 递归访问
    递归访问是针对mutex占有者来说的。mutex占有者可以不断地pend 互斥量。每pend 一次,其对互斥量的占有程度会上升一级,即OwnerNestingCtr++。

  3. 防止优先级翻转
    优先级反转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。先了解一下优先级翻转的场景:
     优先级翻转示意图
    (1) 任务H 和任务M 处于挂起状态,等待某一事件的发生,任务L 正在运行。
    (2) 某一时刻任务L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
    (3) 任务L 获得信号量并开始使用该共享资源。
    (4) 由于任务H 优先级高,它等待的事件发生后便剥夺了任务L 的CPU 使用权。
    (5) 任务H 开始运行。
    (6) 任务H 运行过程中也要使用任务L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务H只能进入挂起状态,等待任务L 释放该信号量
    (7) 任务L 继续运行。
    (8) 由于任务M 的优先级高于任务L,当任务M 等待的事件发生后,任务M 剥夺了任务L的CPU 使用权
    (9) 任务M 处理该处理的事。
    (10) 任务M 执行完毕后,将CPU 使用权归还给任务L。
    (11) 任务L 继续运行。
    (12) 最终任务L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优
    先级的任务在等待这个信号量,故内核做任务切换。
    (13) 任务H 得到该信号量并接着运行
    从以上分析过程看出,在(6)时,任务H 就进入挂起状态,等待 任务L post信号量。而在(8)中,任务M获得CPU使用权,开始运行。可是,高优先级任务H却被晾在那,直到任务L post 信号量为止,任务M反而提前运行结束。概括的说,高优先级任务因为信号量被低优先级任务霸占而进入pend状态,导致被中优先级任务抢了先,与我们刚开始赋予任务优先级的初衷相违背。
    互斥量给出的解决手段就是把 暂时(只是暂时,后期会有优先级的恢复,OwnerOriginalPrio就是原先优先级的记录变量)把 任务L的优先级提升到与任务H的优先级一致(相当于任务L继承了任务H的优先级,在某些教材称为优先级继承)。这样一来,在(8)和(9)中,任务M的优先级因为是最低,故不会“截”了任务H对CPU的使用权。
    从需求的角度来讲,优先级翻转的常见场景就是多任务申请互斥资源的时候,这时候用互斥量管理互斥资源当然得加上预防优先级反转的的措施。
    (图片和注解部分摘自正点原子uCosIII开发手册)

应用场景

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态。互斥量
更适合于:

可能会引起优先级翻转的情况。
 任务可能会多次获取互斥量的情况下,这样可以避免同一任务多次递归持有而造成死锁的问题。

关键代码解析

  1. 等待互斥量
void  OSMutexPend (OS_MUTEX  *p_mutex,
                   OS_TICK    timeout,
                   OS_OPT     opt,
                   CPU_TS    *p_ts,
                   OS_ERR    *p_err)
{
    OS_PEND_DATA  pend_data;
    OS_TCB       *p_tcb;
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif
/*中断调用检测 ,Not allowed to call from an ISR   */
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) {   
       *p_err = OS_ERR_PEND_ISR;
        return;
    }
#endif
//输入参数检测
#if OS_CFG_ARG_CHK_EN > 0u
    if (p_mutex == (OS_MUTEX *)0) {   /* Validate arguments  */
       *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
    switch (opt) {  /* Validate 'opt'  */
        case OS_OPT_PEND_BLOCKING:
        case OS_OPT_PEND_NON_BLOCKING:
             break;

        default:
            *p_err = OS_ERR_OPT_INVALID;
             return;
    }
#endif
//内核对象类型检测
#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
    if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) {   /* Make sure mutex was created  */
       *p_err = OS_ERR_OBJ_TYPE;
        return;
    }
#endif
 / * Initialize the returned timestamp    */
    if (p_ts != (CPU_TS *)0) {
       *p_ts  = (CPU_TS  )0;  
    }

    CPU_CRITICAL_ENTER();
    if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) {    
          /* Resource available? 若==0 则互斥信号量可用  =	*/
        p_mutex->OwnerTCBPtr       =  OSTCBCurPtr;          /* Yes, caller may proceed                                */
        p_mutex->OwnerOriginalPrio =  OSTCBCurPtr->Prio;
        p_mutex->OwnerNestingCtr   = (OS_NESTING_CTR)1;
        if (p_ts != (CPU_TS *)0) {
           *p_ts  = p_mutex->TS;
        }
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_NONE;
        return;
    }
  /************************互斥信号量暂时不可用******************************/
    if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) {    /* See if current task is already the owner of the mutex 
         若请求mutex的任务仍然是mutex占有者 占有者嵌套次数+1*/
        p_mutex->OwnerNestingCtr++;
        if (p_ts != (CPU_TS *)0) {
           *p_ts  = p_mutex->TS;
        }
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_MUTEX_OWNER;                         /* Indicate that current task already owns the mutex      */
        return;
    }

		//是否进行等待
    if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) {    /* Caller wants to block if not available?                */
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_PEND_WOULD_BLOCK;     /* No 不等待  直接返回错误  */
        return;
    } else {     //等待 mutex 若调度器上锁   退出
        if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {    /* Can't pend when the scheduler is locked ???*/
            CPU_CRITICAL_EXIT();
           *p_err = OS_ERR_SCHED_LOCKED;
            return;
        }
    }
    //等待mutex中 处理
    OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();  /* Lock the scheduler/re-enable interrupts锁调度器但开中断的形式保护临界区  */
    p_tcb = p_mutex->OwnerTCBPtr;                           /* Point to the TCB of the Mutex owner得到mutex占有任者 */
    if (p_tcb->Prio < OSTCBCurPtr->Prio) { /* See if mutex owner has a lower priority than current
  若mutex占有者优先级<mutex申请者的优先级  则mutex占有者的优先级需提高至与等待任务等同   改变优先级-修改在各种根据优先级大小排列的列表的位置*/
        switch (p_tcb->TaskState) {  //  更改mutex任务优先级根据mutex占有者的状态进行分类处理
					//如果是就绪状态
            case OS_TASK_STATE_RDY:
                 OS_RdyListRemove(p_tcb);                   /* Remove from ready list at current priority             */
                 p_tcb->Prio = OSTCBCurPtr->Prio;           /* Raise owner's priority                                 */
                 OS_PrioInsert(p_tcb->Prio);
                 OS_RdyListInsertHead(p_tcb);               /* Insert in ready list at new priority                   */
                 break;
            //如果是延时和挂起状态  只需改变优先级  列表位置以后处理
            case OS_TASK_STATE_DLY:
            case OS_TASK_STATE_DLY_SUSPENDED:
            case OS_TASK_STATE_SUSPENDED:
                 p_tcb->Prio = OSTCBCurPtr->Prio;           /* Only need to raise the owner's priority                */
                 break;
            //各种等待状态  优先级改变+改变等待列表中的位置
            case OS_TASK_STATE_PEND:                        /* Change the position of the task in the wait list       */
            case OS_TASK_STATE_PEND_TIMEOUT:
            case OS_TASK_STATE_PEND_SUSPENDED:
            case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
                 OS_PendListChangePrio(p_tcb,
                                       OSTCBCurPtr->Prio);
                 break;

            default:
                 OS_CRITICAL_EXIT();
                *p_err = OS_ERR_STATE_INVALID;
                 return;
        }
    }

    OS_Pend(&pend_data,   /* Block task pending on Mutex  将任务脱离就绪列表 插入等待列表  */
            (OS_PEND_OBJ *)((void *)p_mutex),
             OS_TASK_PEND_ON_MUTEX,
             timeout);

    OS_CRITICAL_EXIT_NO_SCHED();

    OSSched();                                              /* Find the next highest priority task ready to run       */
  
	//任务解除等待状态  重新获得CPU使用权
    CPU_CRITICAL_ENTER();
    switch (OSTCBCurPtr->PendStatus) {
        case OS_STATUS_PEND_OK:                             /* We got the mutex                                       */
             if (p_ts != (CPU_TS *)0) {
                *p_ts  = OSTCBCurPtr->TS;
             }
            *p_err = OS_ERR_NONE;
             break;

        case OS_STATUS_PEND_ABORT:                          /* Indicate that we aborted                               */
             if (p_ts != (CPU_TS *)0) {
                *p_ts  = OSTCBCurPtr->TS;
             }
            *p_err = OS_ERR_PEND_ABORT;
             break;

        case OS_STATUS_PEND_TIMEOUT:                        /* Indicate that we didn't get mutex within timeout       */
             if (p_ts != (CPU_TS *)0) {
                *p_ts  = (CPU_TS  )0;
             }
            *p_err = OS_ERR_TIMEOUT;
             break;

        case OS_STATUS_PEND_DEL:                            /* Indicate that object pended on has been deleted        */
             if (p_ts != (CPU_TS *)0) {
                *p_ts  = OSTCBCurPtr->TS;
             }
            *p_err = OS_ERR_OBJ_DEL;
             break;

        default:
            *p_err = OS_ERR_STATUS_INVALID;
             break;
    }
    CPU_CRITICAL_EXIT();
}

代码挺复杂,文字表达清晰一点
当然,其中一些细节后期有需要再补上。
2. 请求互斥量

void  OSMutexPost (OS_MUTEX  *p_mutex,    //提交/发布互斥信号量
                   OS_OPT     opt,
                   OS_ERR    *p_err)
{
    OS_PEND_LIST  *p_pend_list;
    OS_TCB        *p_tcb;
    CPU_TS         ts;
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL    //安全性检测
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u              //中断调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) {              /* Not allowed to call from an ISR                        */
       *p_err = OS_ERR_POST_ISR;
        return;
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u   //输入参数p_mutex和opt 检测
    if (p_mutex == (OS_MUTEX *)0) {                         /* Validate 'p_mutex'                                     */
       *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
    switch (opt) {         /* Validate 'opt'                                         */
        case OS_OPT_POST_NONE:
        case OS_OPT_POST_NO_SCHED:
             break;

        default:
            *p_err =  OS_ERR_OPT_INVALID;
             return;
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u                  //内核对象检测
    if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) {               /* Make sure mutex was created                            */
       *p_err = OS_ERR_OBJ_TYPE;
        return;
    }
#endif

    CPU_CRITICAL_ENTER();
    if (OSTCBCurPtr != p_mutex->OwnerTCBPtr) {              /* Make sure the mutex owner is releasing the mutex       */
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_MUTEX_NOT_OWNER;
        return;
    }

    OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
    ts          = OS_TS_GET();                              /* Get timestamp                                          */
    p_mutex->TS = ts;
    p_mutex->OwnerNestingCtr--;                             /* Decrement owner's nesting counter  占有者嵌套次数减一  */
    if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) {     /* Are we done with all nestings?嵌套次数大于0 不可提交*/
        OS_CRITICAL_EXIT();                                 /* No                                                     */
       *p_err = OS_ERR_MUTEX_NESTING;
        return;
    }

    p_pend_list = &p_mutex->PendList;
    if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) {         /* Any task waiting on mutex?没有任务等待该信号量*/
        p_mutex->OwnerTCBPtr     = (OS_TCB       *)0;       /* No                                                     */
        p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0;
        OS_CRITICAL_EXIT();
       *p_err = OS_ERR_NONE;
        return;
    }
                                                            /* Yes  等待列表中存在任务                      */
		/*检查任务的优先级是否跟之前保存的优先级是否相同 若不一样,优先级发生反转,占有任务的优先级需还原到原来的优先级
		  注意还原优先级需要从就需列表中移除 还原后再插入*/
    if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio) {
        OS_RdyListRemove(OSTCBCurPtr);
        OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio;     /* Lower owner's priority back to its original one        */
        OS_PrioInsert(OSTCBCurPtr->Prio);
        OS_RdyListInsertTail(OSTCBCurPtr);                  /* Insert owner in ready list at new priority             */
        OSPrioCur         = OSTCBCurPtr->Prio;
    }
      /* Get TCB from head of pend list 等待列表的最高优先级任务获得mutex注意等待列表中的任务均按照优先级大小排列  */
    p_tcb                      = p_pend_list->HeadPtr->TCBPtr;
    p_mutex->OwnerTCBPtr       = p_tcb;                     /* Give mutex to new owner                                */
    p_mutex->OwnerOriginalPrio = p_tcb->Prio;
    p_mutex->OwnerNestingCtr   = (OS_NESTING_CTR)1;
 /* Post to mutex实际上就是把获得mutex的任务从等待列表中移除,插入就绪列表,相关任务状态更新     */
    OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
            (OS_TCB      *)p_tcb,
            (void        *)0,
            (OS_MSG_SIZE  )0,
            (CPU_TS       )ts);

    OS_CRITICAL_EXIT_NO_SCHED();//开调度器

    if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) {
        OSSched();    /* Run the scheduler                                      */
    }

   *p_err = OS_ERR_NONE;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值