互斥信号量
互斥信号量的原理和创建


对于解决多任务共享资源冲突的问题,大致有三种解决办法,一种是关中断,这种一般冲突的是中断;第二种是调度锁,缺点和第一种一样,如果资源占用时间太长的话,会影响实时性;第三种是计数信号量,如上图,如果在嵌套的时候,同时使用了信号量并且与该信号量一样,那会被卡死,同样如果任何任务都能notify,那么安全性就会大大降低,对线程管理还说,也会有优先级反转的问题
下图是优先级反转的框图,大致是低优先级占有资源,高优先级意图访问资源,但是中间有中优先级任务,这就导致了优先级最高的任务,反而最后运行




typedef struct _tMutex
{
// 事件控制块
tEvent event;
// 已被锁定的次数
uint32_t lockedCount;
// 拥有者
tTask * owner;
// 拥有者原始的优先级
uint32_t ownerOriginalPrio;
}tMutex;
对于互斥量的构造来说,比较复杂,相比信号量来说,多了owner和ownerOriginalPrio,分别用于确定拥有者和优先级继承的
void tMutexInit (tMutex * mutex)
{
tEventInit(&mutex->event, tEventTypeMutex);
mutex->lockedCount = 0;
mutex->owner = (tTask *)0;
mutex->ownerOriginalPrio = TINYOS_PRO_COUNT;
}
初始化比较简单,对每个构造进行初始化信息即可
互斥信号量的等待和通知




uint32_t tMutexWait (tMutex * mutex, uint32_t waitTicks)
{
uint32_t status = tTaskEnterCritical();
if (mutex->lockedCount <= 0)
{
// 如果没有锁定,则使用当前任务锁定
mutex->owner = currentTask;
mutex->ownerOriginalPrio = currentTask->prio;
mutex->lockedCount++;
tTaskExitCritical(status);
return tErrorNoError;
}
else
{
// 信号量已经被锁定
if (mutex->owner == currentTask)
{
// 如果是信号量的拥有者再次wait,简单增加计数
mutex->lockedCount++;
tTaskExitCritical(status);
return tErrorNoError;
}
else
{
// 如果是信号量拥有者之外的任务wait,则要检查下是否需要使用
// 优先级继承方式处理
if (currentTask->prio < mutex->owner->prio)
{
tTask * owner = mutex->owner;
// 如果当前任务的优先级比拥有者优先级更高,则使用优先级继承
// 提升原拥有者的优先
if (owner->state == TINYOS_TASK_STATE_RDY)
{
// 任务处于就绪状态时,更改任务在就绪表中的位置
tTaskSchedUnRdy(owner);
owner->prio = currentTask->prio;
tTaskSchedRdy(owner);
}
else
{
// 其它状态,只需要修改优先级
owner->prio = currentTask->prio;
}
}
// 当前任务进入等待队列中
tEventWait(&mutex->event, currentTask, (void *)0, tEventTypeMutex, waitTicks);
tTaskExitCritical(status);
// 执行调度, 切换至其它任务
tTaskSched();
return currentTask->waitEventResult;
}
}
}
uint32_t tMutexNoWaitGet (tMutex * mutex)
{
uint32_t status = tTaskEnterCritical();
if (mutex->lockedCount <= 0)
{
// 如果没有锁定,则使用当前任务锁定
mutex->owner = currentTask;
mutex->ownerOriginalPrio = currentTask->prio;
mutex->lockedCount++;
tTaskExitCritical(status);
return tErrorNoError;
}
else
{
// 信号量已经被锁定
if (mutex->owner == currentTask)
{
// 如果是信号量的拥有者再次wait,简单增加计数
mutex->lockedCount++;
tTaskExitCritical(status);
return tErrorNoError;
}
tTaskExitCritical(status);
return tErrorResourceUnavaliable;
}
}
uint32_t tMutexNotify (tMutex * mutex)
{
uint32_t status = tTaskEnterCritical();
if (mutex->lockedCount <= 0)
{
// 锁定计数为0,信号量未被锁定,直接退出
tTaskExitCritical(status);
return tErrorNoError;
}
if (mutex->owner != currentTask)
{
// 不是拥有者释放,认为是非法
tTaskExitCritical(status);
return tErrorOwner;
}
if (--mutex->lockedCount > 0)
{
// 减1后计数仍不为0, 直接退出,不需要唤醒等待的任务
tTaskExitCritical(status);
return tErrorNoError;
}
// 是否有发生优先级继承
if (mutex->ownerOriginalPrio != mutex->owner->prio)
{
// 有发生优先级继承,恢复拥有者的优先级
if (mutex->owner->state == TINYOS_TASK_STATE_RDY)
{
// 任务处于就绪状态时,更改任务在就绪表中的位置
tTaskSchedUnRdy(mutex->owner);
currentTask->prio = mutex->ownerOriginalPrio;
tTaskSchedRdy(mutex->owner);
}
else
{
// 其它状态,只需要修改优先级
currentTask->prio = mutex->ownerOriginalPrio;
}
}
// 检查是否有任务等待
if (tEventWaitCount(&mutex->event) > 0)
{
// 如果有的话,则直接唤醒位于队列首部(最先等待)的任务
tTask * task = tEventWakeUp(&mutex->event, (void *)0, tErrorNoError);
mutex->owner = task;
mutex->ownerOriginalPrio = task->prio;
mutex->lockedCount++;
// 如果这个任务的优先级更高,就执行调度,切换过去
if (task->prio < currentTask->prio)
{
tTaskSched();
}
}
tTaskExitCritical(status);
return tErrorNoError;
}
tMutexWait中,大致逻辑是先对计数判断,非正即未锁定,那么就使用当前任务锁定,如果已经被锁定了那么就执行下一步判断,也就是当前任务是信号量的拥有者还是其他,是信号量的拥有者的话就简单加加一下,是其它任务的话,就需要当前任务的优先级比拥有者优先级更高,则使用优先级继承,插入等待队列中,这里有一个细节,由于我们修改了拥有者的优先级,那么如果拥有者处于就绪态,我们就需要更改其位置
tMutexNoWaitGet中,与tMutexWait类似,不同的是,tMutexNoWaitGet并没有相关等待队列的功能,当然也不会有优先级继承
tMutexNotify中,前面是一些判断,主要是合法判断,比如计数为0即未锁定,则直接退出,以及只能通过拥有者释放等,后面就是优先级继承的操作,先是恢复优先级,仿照tMutexWait中,处于就绪态就更改位置,方便运行;最后就是取出任务运行
互斥信号量的删除和状态查询
typedef struct _tMutexInfo
{
// 等待的任务数量
uint32_t taskCount;
// 拥有者任务的优先级
uint32_t ownerPrio;
// 继承优先级
uint32_t inheritedPrio;
// 当前信号量的拥有者
tTask * owner;
// 锁定次数
uint32_t lockedCount;
}tMutexInfo;
uint32_t tMutexDestroy (tMutex * mutex)
{
uint32_t count = 0;
uint32_t status = tTaskEnterCritical();
// 信号量是否已经被锁定,未锁定时没有任务等待,不必处理
if (mutex->lockedCount > 0)
{
// 是否有发生优先级继承?如果发生,需要恢复拥有者的原优先级
if (mutex->ownerOriginalPrio != mutex->owner->prio)
{
// 有发生优先级继承,恢复拥有者的优先级
if (mutex->owner->state == TINYOS_TASK_STATE_RDY)
{
// 任务处于就绪状态时,更改任务在就绪表中的位置
tTaskSchedUnRdy(mutex->owner);
mutex->owner->prio = mutex->ownerOriginalPrio;
tTaskSchedRdy(mutex->owner);
}
else
{
// 其它状态,只需要修改优先级
mutex->owner->prio = mutex->ownerOriginalPrio;
}
}
// 然后,清空事件控制块中的任务
count = tEventRemoveAll(&mutex->event, (void *)0, tErrorDel);
// 清空过程中可能有任务就绪,执行一次调度
if (count > 0)
{
tTaskSched();
}
}
tTaskExitCritical(status);
return count;
}
void tMutexGetInfo (tMutex * mutex, tMutexInfo * info)
{
uint32_t status = tTaskEnterCritical();
// 拷贝需要的信息
info->taskCount = tEventWaitCount(&mutex->event);
info->ownerPrio = mutex->ownerOriginalPrio;
if (mutex->owner != (tTask *)0)
{
info->inheritedPrio = mutex->owner->prio;
}
else
{
info->inheritedPrio = TINYOS_PRO_COUNT;
}
info->owner = mutex->owner;
info->lockedCount = mutex->lockedCount;
tTaskExitCritical(status);
}
新增tMutexInfo结构体,在tMutexGetInfo获取想要的信息,例如等待任务数量,原本优先级和继承优先级等
删除函数tMutexDestroy比起其他会更复杂一些,因为删除的内容不止是等待列表,还有互斥量的持有者,也需要考虑优先级继承的问题