本篇解读互斥量(互斥锁),互斥量顾名思义是任务间对资源进行独占而进行一种互斥保护的手段。由于互斥量与信号量类似,只是互斥量的值只能是0或者1,故又称为二值信号量。互斥量在IPC概念中属于任务同步机制的一种。本篇内容将不再深入分析与上篇信号量相同的源码,着重带来互斥量中独有的特性。详细目录如下:
1.优先级反转
2.优先级继承
3.互斥量创建与获取
4.互斥量释放
5.典型的死锁案例
本篇内容源码基于3.1.x LTS
优先级反转
优先级继承
当出现优先级反转后,这是不太合理的现象,所以就有人想出办法来避免这种现象。其中就有一种称为优先级继承的方法出现了。优先级继承是在当A任务挂起在C任务持有的互斥量上时,通过对比C任务的优先级,如果持有者的优先级比自己低,则将持有者任务的优先级提升到自己的优先级,后续所有想获取此互斥量的任务都是如此。所以当A任务发现C任务优先级比自己低,就把C任务的优先级提升到与自己相同,此时A任务依然挂起在互斥量M上,但是C任务的优先级此时提升了与A相同,比B任务高,所以C任务立马又接着运行,并最终释放掉互斥量,并恢复自身的原始优先级。此时A任务就立马得以运行,这期间就没有被B任务抢先运行。
互斥量创建和获取
/**
*Mutual exclusion (mutex) structure
*/
struct rt_mutex
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of mutex */
rt_uint8_t original_priority; /**< priority of last thread hold the mutex */
rt_uint8_t hold; /**< numbers of thread hold the mutex*/
structrt_thread *owner; /**< current owner of mutex */
};
typedef struct rt_mutex *rt_mutex_t;
/**
*This function will create a mutex from system resource
*
*@param name the name of mutex
*@param flag the flag of mutex
*
*@return the created mutex, RT_NULL on error happen
*
*@see rt_mutex_init
*/
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
{
struct rt_mutex *mutex;
RT_DEBUG_NOT_IN_INTERRUPT;
/* allocateobject */
mutex = (rt_mutex_t)rt_object_allocate(RT_Object_Class_Mutex, name);
if (mutex == RT_NULL)
return mutex;
/* init ipcobject */
rt_ipc_object_init(&(mutex->parent));
mutex->value = 1;
mutex->owner = RT_NULL;
mutex->original_priority = 0xFF;
mutex->hold = 0;
/* set flag */
mutex->parent.parent.flag = flag;
return mutex;
}
关于静态创建的方法和信号量类似,这里就不再分析。rt_mutex_create用于创建一个动态的rt_mutex_t对象,参数与信号量创建是一样的。除了初始值互斥量里是1,并多了几个字段没有什么大不一样。Owner用来保存当前正在使用互斥量的任务TCB,original_priority用来记录任务原始优先级用于释放互斥量的时候恢复,hold用来支持同一个人多次递归持有互斥量。下面来看看获取互斥锁的的代码:
/**
*This function will take a mutex, if the mutex is unavailable, the
*thread shall wait for a specified time.
*
*@param mutex the mutex object
*@param time the waiting time
*
*@return the error code
*/
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
register rt_base_t temp;
struct rt_thread *thread;
/* this function must not be used in interrupt even if time = 0 */
RT_DEBUG_IN_THREAD_CONTEXT;
/* parameter check */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
/* get current thread */
thread = rt_thread_self();
/* disable interrupt */
temp = rt_hw_interrupt_disable();
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_take: current thread %s, mutex value: %d, hold: %d",
thread->name, mutex->value, mutex->hold));
/* reset thread error*/
thread->error = RT_EOK;
if (mutex->owner == thread)
{
/* it's the samethread */
mutex->hold++;
}
else
{
__again:
/* The value of mutex is 1 in initial status. Therefore, if the
* value is great than 0, it indicates the mutex is avaible.
*/
if (mutex->value>0)
{
/* mutex is available */
mutex->value--;
/* set mutex owner and original priority */
mutex->owner = thread;
mutex->original_priority = thread->current_priority;
mutex->hold++;
}
else
{
/* no waiting, return with timeout */
if (time == 0)
{
/* set error as timeout */
thread->error = -RT_ETIMEOUT;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
else
{
/* mutex is unavailable, push to suspendlist */
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s",
thread->name));
/* change the owner thread priority ofmutex */
if (thread->current_priority < mutex->owner->current_priority)
{
/* change the owner thread priority */
rt_thread_control(mutex->owner,
RT_THREAD_CTRL_CHANGE_PRIORITY,
&thread->current_priority);
}
/* suspend current thread */
rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
thread,
mutex->parent.parent.flag);
/* has waiting time, start thread timer */
if (time > 0)
{
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_take: start the timer ofthread:%s",
thread->name));
/* reset the timeout of thread timer andstart it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&time);
rt_timer_start(&(thread->thread_timer));
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* do schedule */
rt_schedule();
if (thread->error != RT_EOK)
{
/* interrupt by signal, try it again */
if (thread->error == -RT_EINTR) goto __again;
/* return error */
return thread->error;
}
else
{
/* the mutex is taken successfully. */
/* disable interrupt */
temp =rt_hw_interrupt_disable();
}
}
}
}
/* enableinterrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mutex->parent.parent)));
return RT_EOK;
}
互斥量释放
持有互斥量的任务必须在退出临界资源的时候将互斥量释放,否则其它任务想获取临界资源将会导致挂起在互斥量上。关于互斥量释放的代码如下:
/**
*This function will release a mutex, if there are threads suspended on mutex,
*it will be waked up.
*
*@param mutex the mutex object
*
*@return the error code
*/
rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
register rt_base_t temp;
struct rt_thread *thread;
rt_bool_t need_schedule;
/* parametercheck */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
need_schedule = RT_FALSE;
/* only thread could release mutex because we need test the ownership */
RT_DEBUG_IN_THREAD_CONTEXT;
/* get current thread */
thread = rt_thread_self();
/* disable interrupt */
temp = rt_hw_interrupt_disable();
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_release:current thread %s, mutex value: %d, hold: %d",
thread->name, mutex->value, mutex->hold));
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
/* mutex onlycan be released by owner */
if (thread != mutex->owner)
{
thread->error = -RT_ERROR;
/* enableinterrupt */
rt_hw_interrupt_enable(temp);
return -RT_ERROR;
}
/* decrease hold*/
mutex->hold--;
/* if no hold */
if (mutex->hold == 0)
{
/* change theowner thread to original priority */
if (mutex->original_priority!=mutex->owner->current_priority)
{
rt_thread_control(mutex->owner,
RT_THREAD_CTRL_CHANGE_PRIORITY,
&(mutex->original_priority));
}
/* wakeupsuspended thread */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
/* get suspended thread */
thread =rt_list_entry(mutex->parent.suspend_thread.next,
struct rt_thread,
tlist);
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s",
thread->name));
/* set new owner and priority */
mutex->owner = thread;
mutex->original_priority = thread->current_priority;
mutex->hold++;
/* resume thread */
rt_ipc_list_resume(&(mutex->parent.suspend_thread));
need_schedule = RT_TRUE;
}
else
{
/* increase value */
mutex->value++;
/* clear owner */
mutex->owner = RT_NULL;
mutex->original_priority = 0xff;
}
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* perform a schedule */
if (need_schedule == RT_TRUE)
rt_schedule();
return RT_EOK;
}
看代码对比信号量的释放发现互斥锁只能被拥有锁的任务释放,不能被其它无关的任务释放这是有别于信号量的。另外同一个任务因为可以支持多次递归取锁,所以仅仅当最后一次释放锁的时候hold为0了才真正释放锁。在最后一次释放锁的时候会将任务的优先级会恢复到原始优先级。同时如果有其它任务挂起在互斥锁的IPC suspend list队列中时也会将从队列头取出一个任务TCB,然后将互斥锁交给它,并恢复这个队列头的任务使其就绪。最后触发调度器进行一次任务调度,函数返回。如果suspend list里没有任务挂起,则对互斥锁的值加一,锁的值从0变1,并清空owner和original_priority,然后函数返回。
现在我们把目光回到恢复队列头任务的代码块处,仔细思考一下。由于RT-Thread中mutex继承自ipc object,所以等待队列支持FIFO和PRIO两种模式,并且在mutex初始化时没有限定队列模式。这块代码处理逻辑是将锁直接交给队列头的任务,如果是PRIO就没问题,但是如果是FIFO模式,则会造成优先级反转。因为FIFO队列头并不是按照优先级排序的,所以队列头的任务有可能优先级较低,队列中其它较高的优先级任务还在挂起等待锁,此时把锁直接交给低优先级任务就会造成优先级反转。在这里不妨提出疑问——在实时操作系统中,能用FIFO队列做IPC等待队列吗?
所以这里应该是一个隐藏的BUG。在此BUG修复之前建议最好还是用PRIO(RT_IPC_FLAG_PRIO)作为初始化flag。
典型的死锁案例
死锁就是多个任务间交叉需要获取其它任务的锁,例如任务A,任务B。A持有锁M1后,想要获取锁M2,而任务B此时获取了锁M2,且又想获取锁M1,这时候A发现M2锁被B占用,从而挂起;B任务此时也发现M1锁被A任务占用,从而也挂起,就这样,A,B都挂起了,彼此永无止境的等待对方释放锁。
END
RT-Thread线上活动
1、【RT-Thread能力认证考试12月——RCEA】经过第一次考试的验证,
能力认证官网链接:https://www.rt-thread.org/page/rac.html(在外部浏览器打开)
立即报名
#题外话# 喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread
你可以添加微信18917005679为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群
RT-Thread
长按二维码,关注我们
点击阅读原文进入Github