【Ucos-III源码分析】——互斥信号量(互斥锁)

一、互斥信号量理解

互斥信号量的出现就是为了解决优先级反转问题(因为等待信号量和系统任务调度导致优先级低的任务先运行),这里的互斥信号量可以看作是计数值最大只能为1的信号量,谁获取到了这个信号量,谁才可以发布。这就表明了这个互斥信号量可以解决我们的优先级反反转问题。

 

二、创建

OSMutexCreate ((OS_MUTEX *)&MyMutex,
			   (CPU_CHAR *)"mymutex",
			   (OS_ERR   *)&err);

可以看到,我们的互斥信号量创建,是并不会赋予初值的。默认就是1,仔细看一下这个函数内部。

void  OSMutexCreate (OS_MUTEX    *p_mutex,
                     CPU_CHAR    *p_name,
                     OS_ERR      *p_err)
{
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == DEF_TRUE) {
       *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
        return;
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) {              /* Not allowed to be called from an ISR                   */
        *p_err = OS_ERR_CREATE_ISR;
        return;
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u
    if (p_mutex == (OS_MUTEX *)0) {                         /* Validate 'p_mutex'                                     */
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
#endif

    CPU_CRITICAL_ENTER();
    p_mutex->Type              =  OS_OBJ_TYPE_MUTEX;        /* Mark the data structure as a mutex                     */
    p_mutex->NamePtr           =  p_name;
    p_mutex->OwnerTCBPtr       = (OS_TCB       *)0;
    p_mutex->OwnerNestingCtr   = (OS_NESTING_CTR)0;         /* Mutex is available                                     */
    p_mutex->TS                = (CPU_TS        )0;
    p_mutex->OwnerOriginalPrio =  OS_CFG_PRIO_MAX;
    OS_PendListInit(&p_mutex->PendList);                    /* Initialize the waiting list                            */

#if OS_CFG_DBG_EN > 0u
    OS_MutexDbgListAdd(p_mutex);
#endif
    OSMutexQty++;

    CPU_CRITICAL_EXIT();
    *p_err = OS_ERR_NONE;
}

可以看到,这个函数实现可以说是比信号量要稍微简单一点。除了默认的参数检查,就直接是对一个mutex的赋值。这里需要注意这个OwnerTCBptr这个就是保存谁获取到了这个mutex,前面也提到了,谁获取谁才能发布。下面就是一个OS_Pendlist这个依旧是对于所有等待这个mutex的任务惊醒一个排序,也就是根据优先级的一个双向链表。这里面有分阻塞等待和非阻塞等待的。这里非阻塞等待的就会多添加进一个ticklist里面。用来配合这个超时等待的时间匹配。

这里还需要了解一个事情:例如当前任务A的优先级比任务B高,但是B在A前面获取到了这个mutex,但是此时A也想要获取mutex,此时怎么办。这时候就需要用到我们的mutex对任务B的一个优先级进行提升。让他不被A抢占走(因为ucos是抢占式内核,谁优先级高谁就可以打断优先级低的任务)。

这个等待列表的初始化也是非常的简单,就是三个数字,管理双线链表的头尾指针。和所有在等待列表的一个数量。


三、获取等待mutex

OSMutexPend ( (OS_MUTEX *)&MyMutex,
			  (OS_TICK  )0,
			  (OS_OPT   )OS_OPT_PEND_BLOCKING,
			  (CPU_TS  *)0,
			  (OS_ERR  *)&err);

这里几个参数的作用就不过多提了,请参考我的另外一篇文章中有详细的用法介绍。

因为这里面源码过多,我就简单直接描述里面源码的作用

(1)、照例参数检查

#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_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) {
        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

(2)、判断你是否需要获取时间的这个时间戳,需要就把当前相对时间设为0后续用

    if (p_ts != (CPU_TS *)0) {
       *p_ts  = (CPU_TS  )0;                                /* Initialize the returned timestamp                      */
    }

(3)、判断当前的这个mutex是否被占用了

        1、如果没有,那么直接就是把你当前调用mutex的这个任务设置为当前任务,获取时间,再就是把当前任务的优先级也都保存到mutex中。

    if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) {    /* Resource available?                                    */
        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;
    }

        2、如果当前任务已经拥有了mutex,但是又在获取了一次,那么mutex的嵌套层数+1,并且这个嵌套层数也只能由拥有mutex的任务来解用(就是发布)

if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) {              /* See if current task is already the owner of the mutex  */
        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;
    }

        3、如果你当前的任务想获取mutex,但是mutex已经被其他任务拥有了,并且你的任务是非阻塞,那么就直接退出,相当于你想拿,但是别人拿走了,你又不等,那就直接退出。

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;
    } 

        4、 3、如果你当前的任务想获取mutex,但是mutex已经被其他任务拥有了,并且你的任务是阻塞,那么此时就需要做两件事 1判断优先级,消除优先级反转问题。2把任务加入等待列表。

if (p_tcb->Prio > OSTCBCurPtr->Prio) {                  /* See if mutex owner has a lower priority than current   */
        switch (p_tcb->TaskState) {
            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;
        }
    }

这里就是你的任务需要修改优先级得问题,又分为几个类型。

  •  任务是就绪态

这里就是你就绪态的任务在就绪列表中是按照任务优先级来排序的,那么就需要先把任务从就绪态移除出去,然后修改任务优先级,然后再重新插入,对应第一堆case


  •  任务是挂起,延时状态

这里就是第二堆case,这里的意思就是,你任务已经被主动挂起了,此时任务是连接在时钟基准列表当中的,而时钟基准列表是根据你任务需要延时的时间来排序的,和你的优先级没有关系,所以可以直接改


  • 任务处于等待列表

这屋里就是任务如果处于等待列表中,第三堆case,这里就需要调用一个等待列表修改任务优先级的一个专用函数,来进行修改。这里面实现就是找到任务等待的等待列表,然后改优先级,然后就是先把你从等待列表里删了再重新按新的优先级插入。

在后面就是把你当前调用mutex的任务挂起到等待列表中

OS_Pend(&pend_data,                                     /* Block task pending on Mutex                            */
            (OS_PEND_OBJ *)((void *)p_mutex),
             OS_TASK_PEND_ON_MUTEX,
             timeout);

在后面就是根据一个状态值,来返回一个err的以一个状态。

四、发布mutex

1、利用post发布mutex,这里面要注意一个参数,这个参数里面opt要注意一下,这里是问你是否想进行任务调度。

 2、查看这个mutex里面是存的那个tcb(谁占用谁存这个tcb),不是就不行,如果是的话那么在获取一个时间并且mutex的嵌套层数-1,后面就接着判断你的嵌套层数是否为0了,不为0相当于当时占用这个mutex的用了多个pend,套了很多层。

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?                         */
        OS_CRITICAL_EXIT();                                 /* No                                                     */
        *p_err = OS_ERR_MUTEX_NESTING;
        return;
    }

2、判断你的mutex等待列表里面是否有任务

 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;
    }

3、这里后续的就是拥有mutex的这个任务此时调用post来发布mutex,这时候就要看你的当前任务和这个之前拥有mutex的任务(这俩是同一个),他俩现在的优先级是不是一样的,如果不一样那么肯定是因为当时占用mutex的任务优先级比较低,后续又来了一个比他优先级高的任务也想占用mutex,原来任务被修改了更高的优先级来保证不发生优先级反转问题。这时候表示拥有mutex的任务优先级被改了,所以要把任务原来的优先级改回去,任务原来的优先级保存在mutex的obj中,中心复制就行了,就是先移除这个准备列表,改了优先级再加入。然后就是去mutex的等待列表中看优先级最高的等待的任务,将他设置为mutex拥有者,并且将他的优先级又保存在mutex当中。设置mutex为1。

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                         */
    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;

4、调用os_post把任务从这个等待列表中移除

OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
            (OS_TCB      *)p_tcb,
            (void        *)0,
            (OS_MSG_SIZE  )0,
            (CPU_TS       )ts);

五、OSMutexPendAbort取消等待

这个其实意思很简单, 简单说一下就是利用这个mutex找到里面的等待列表,然后逐个移除出去,然后让他们不等了没必要。并且移出去一个就让一个变量+1,最后返回这个变量,表示移出去了多少个。

六、mutex的删除函数

这里也大概说一下,将这个mutex的等待列表中的数量保存下来,这里面有一个全局变量用来保存你全局存了多少个mutex,这里就做了--运算。

这里面还有一个地方就是看你的当前拥有这个mutex的任务和你等待列表里面的任务,

第一:你占用mutex的任务可能存在优先级被提升的情况,此时需要把优先级踢回去。

第二:循环将你的等待列表中的任务都踢出去。

这里面就还涉及到一部分内容,就是你占用mutex的任务当前处于一个什么状态,如果是就绪态,那么你修改优先级就需要先把任务从就绪列表中移除出去,在修改,在添加回来,对于在等待列表中的任务我们通常就不需要那么复杂,因为大多处于的是一个挂起的状态,我们只需要把他从等待列表中移除出去就可以了。

OS_CRITICAL_ENTER();
    p_pend_list = &p_mutex->PendList;
    cnt         = p_pend_list->NbrEntries;
    nbr_tasks   = cnt;
    switch (opt) {
        case OS_OPT_DEL_NO_PEND:                                 /* Delete mutex only if no task waiting              */
             if (nbr_tasks == (OS_OBJ_QTY)0) {
#if OS_CFG_DBG_EN > 0u
                 OS_MutexDbgListRemove(p_mutex);
#endif
                 OSMutexQty--;
                 OS_MutexClr(p_mutex);
                 OS_CRITICAL_EXIT();
                 *p_err = OS_ERR_NONE;
             } else {
                 OS_CRITICAL_EXIT();
                 *p_err = OS_ERR_TASK_WAITING;
             }
             break;

        case OS_OPT_DEL_ALWAYS:                                            /* Always delete the mutex                 */
             p_tcb_owner = p_mutex->OwnerTCBPtr;                           /* Did we had to change the prio of owner? */
             if ((p_tcb_owner       != (OS_TCB *)0) &&
                 (p_tcb_owner->Prio !=  p_mutex->OwnerOriginalPrio)) {
                 switch (p_tcb_owner->TaskState) {                         /* yes                                     */
                     case OS_TASK_STATE_RDY:
                          OS_RdyListRemove(p_tcb_owner);
                          p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;  /* Lower owner's prio back                 */
                          OS_PrioInsert(p_tcb_owner->Prio);
                          OS_RdyListInsertTail(p_tcb_owner);               /* Insert owner in ready list at new prio  */
                          break;

                     case OS_TASK_STATE_DLY:
                     case OS_TASK_STATE_SUSPENDED:
                     case OS_TASK_STATE_DLY_SUSPENDED:
                          p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;  /* Not in any pend list, change the prio   */
                          break;

                     case OS_TASK_STATE_PEND:
                     case OS_TASK_STATE_PEND_TIMEOUT:
                     case OS_TASK_STATE_PEND_SUSPENDED:
                     case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
                          OS_PendListChangePrio(p_tcb_owner,               /* Owner is pending on another object      */
                                                p_mutex->OwnerOriginalPrio);
                          break;

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

             ts = OS_TS_GET();                                             /* Get timestamp                           */
             while (cnt > 0u) {                                            /* Remove all tasks from the pend list     */
                 p_pend_data = p_pend_list->HeadPtr;
                 p_tcb       = p_pend_data->TCBPtr;
                 OS_PendObjDel((OS_PEND_OBJ *)((void *)p_mutex),
                               p_tcb,
                               ts);
                 cnt--;
             }
#if OS_CFG_DBG_EN > 0u
             OS_MutexDbgListRemove(p_mutex);
#endif
             OSMutexQty--;
             OS_MutexClr(p_mutex);
             OS_CRITICAL_EXIT_NO_SCHED();
             OSSched();                                                    /* Find highest priority task ready to run */
             *p_err = OS_ERR_NONE;
             break;

        default:
             OS_CRITICAL_EXIT();
             *p_err = OS_ERR_OPT_INVALID;
             break;
    }
    return (nbr_tasks);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值