使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓线程优先级翻转,即当一个高优先级线程试图通过某种互斥IPC 对象机制访问共享资源时,如果该IPC 对象已被一低优先级的线程所持有,而这个低优先级线程在运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。
假设三个任务准备执行,A,B,C,优先级依次是A>B>C;
第一:C处于运行状态,获得CPU正在执行,同时占有了A需要的临界资源;
第二:A进入就绪状态,因为优先级比C高,所以A获得CPU,A转为运行状态;C进入就绪状态;
第三:A执行过程中需要使用临界资源,而这个资源又被等待中的C占有的,但是C又无法获取CPU资源,
于是A进入阻塞状态,C回到运行状态;
第四:此时B进入就绪状态,因为优先级比C高,B获得CPU,进入运行状态;C又回到就绪状态,
还是无法释放A需要的临界资源,所以就只能等到B执行完成后C才可以继续运行,然后A才会拿到临界资源继续运行;
优先级翻转会造成高优先级线程的实时性得不到保证。
假设:如果这时又出现B2,B3等任务,他们的优先级都大于C,但比A低,那么就会出现高优先级任务的A不能执行,反而低优先级的B,B2,B3等任务可以执行的奇怪现象,而C永远无法获取CPU使用权,而这就是优先反转。
在RT-Thread 中,通过互斥量的优先级继承算法,可以有效地解决优先级翻转问题。
值得注意的是,我们在设计多线程进应该避免长时间的让线程处理临界区,使用临界资源。
原理就是
提高某个占有某种共享资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,
从而得到更快的执行然后释放共享资源,而当这个低优先级线程释放该资源时,优先级重新回到初始设定值。
/*
* 互斥量使用例程
*
* 这个例子将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否
* 被调整到等待线程优先级中的最高优先级。
*
* 线程 1,2,3 的优先级从高到低分别被创建,
* 线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 的优先级应该
* 被提升为和线程 2 的优先级相同。线程 1 用于检查线程 3 的优先级是否被提升
* 为与线程 2的优先级相同。
*/
#include
/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;
static rt_mutex_t mutex = RT_NULL;
#define THREAD_PRIORITY 10
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
/* 先让低优先级线程运行 */
rt_thread_mdelay(100);
/* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */
/* 检查 rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]); 与 thread3 的优先级情况 */
if (tid2->current_priority != tid3->current_priority)
{
/* 优先级不相同,测试失败 */
rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
rt_kprintf("test failed.\n");
return;
}
else
{
rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
rt_kprintf("test OK.\n");
}
}
/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
rt_err_t result;
rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
/* 先让低优先级线程运行 */
rt_thread_mdelay(50);
/*
* 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升
* 到 thread2 相同的优先级
*/
result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
if (result == RT_EOK)
{
/* 释放互斥锁 */
rt_mutex_release(mutex);
}
}
/* 线程 3 入口 */
static void thread3_entry(void *parameter)
{
rt_tick_t tick;
rt_err_t result;
rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
if (result != RT_EOK)
{
rt_kprintf("thread3 take a mutex, failed.\n");
}
/* 做一个长时间的循环,500ms */
tick = rt_tick_get();
while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ;
rt_mutex_release(mutex);
}
int pri_inversion(void)
{
/* 创建互斥锁 */
mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO);
if (mutex == RT_NULL)
{
rt_kprintf("create dynamic mutex failed.\n");
return -1;
}
/* 创建线程 1 */
tid1 = rt_thread_create("thread1",
thread1_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY - 1, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
/* 创建线程 2 */
tid2 = rt_thread_create("thread2",
thread2_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
/* 创建线程 3 */
tid3 = rt_thread_create("thread3",
thread3_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY + 1, THREAD_TIMESLICE);
if (tid3 != RT_NULL)
rt_thread_startup(tid3);
return 0;
}
解释
首先:优先级:线程1 > 线程2 > 线程3
第二:线程1优先级高,所以先获取CPU资源,
第三:当线程2 比线程3优先级高所以线程2先运行。
第四:线程1和线程2都在延时处于阻塞状态,因此线程3获取了CPU资源,开始运行。
第五:当线程2延时结束达到运行条件开始运行时,系统自动检测到线程2 试图互斥量,而线程3优先级低则 将互斥量上锁了。
按照优先级线程2会抢走线程3的CPU资源,让线程3释放CPU资源
第六:但是在RT-Thread中是有防止优先级反转算法,因此系统自动将线程3 的优先级提高到与线程2 相同等级,
以防止中间优先级线程2插入,最大限度满足高优先级线程在互斥量释放之后第一时间运行。
当发现高优先级的任务因为低优先级任务占用资源而阻塞时,就将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。
还有一种做法就是谁先拿到谁执行。
/**
* 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\n",
thread->name, mutex->value, mutex->hold));
/* reset thread error */
thread->error = RT_EOK;
/**********************************************************
判断锁的拥有者是否是当前线程 ,如果是当前线程,则意味着重复加锁, RT-Thread是允许重复加锁的,
只要用户确保加锁和解锁操作是配对的即可,
和rt_hw_interrupt_disable类似,可重复调用,用户需要确保成对出现即可。
**********************************************************/
if (mutex->owner == thread)
{
/* it's the same thread */
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 suspend list 锁不可用,进入阻塞逻辑 */
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
thread->name));
/*
change the owner thread priority of mutex 改变当前拥有者的优先级
接下来这行代码就是优先级继承的代码实现
*/
if (thread->current_priority < mutex->owner->current_priority)
{
/**********************************************************
change the owner thread priority
首先判断当前线程的优先级和锁拥有者线程的优先级,如果当前线程优先级高,
则有可能发生优先级反转,内核就需要采用优先级继承的方式来解决此问题。
通过rt_thread_control将锁拥有者线程的优先级提升到与当前线程同样的优先级
**********************************************************/
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 of thread:%s\n",
thread->name));
/* reset the timeout of thread timer and start 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
执行调度,锁拥有者线程将会继续被CPU调度执行,继续拥有CPU资源
***********************************************************/
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();
}
}
}
}
/* enable interrupt */
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;
/* parameter check */
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\n",
thread->name, mutex->value, mutex->hold));
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
/* mutex only can be released by owner 一个锁只能被加锁者释放 */
if (thread != mutex->owner)
{
thread->error = -RT_ERROR;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return -RT_ERROR;
}
/* decrease hold */
mutex->hold --;
/* if no hold 计数器为0,则锁被彻底释放,即不再有线程占用该锁。 */
if (mutex->hold == 0)
{
/* change the owner 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));
}
/* wakeup suspended thread */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
/**********************************************************
get suspended thread
如果有被挂起到该锁的线程,则设置标志位need_schedule,重新调度。
如果当前线程为最高优先级,那么当锁释放后需要继续运行,这时是不能重新调度的,
如果当前线程为低优先级,且阻塞了高优先级线程,那么锁释放后就需要立即进行调度,
RT-Thread将以上判断逻辑放在了函数rt_schedule中实现,后续解释
**********************************************************/
thread = rt_list_entry(mutex->parent.suspend_thread.next,
struct rt_thread,
tlist);
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s\n",
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;
}