一、互斥信号量理解
互斥信号量的出现就是为了解决优先级反转问题(因为等待信号量和系统任务调度导致优先级低的任务先运行),这里的互斥信号量可以看作是计数值最大只能为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);