linux内核的优先级继承协议(pip)和μC/OSII 2.52内核的优先级置顶协议(pcp)

在实时系统中,不确定的阻塞是尽力要避免的,因为这可能影响到任务的实时性,在常规的系统中,如果一个低优先级的任务占有了一个独占式资源,那么高优先级任务请求该资源时必然要阻塞,这是可以的,因为系统相信这个低优先级的任务总会释放该资源的,但是在实时系统中,即使再相信资源总会被释放,实时的要求也 不允许任务被长时间的阻塞,更糟糕的是不确定时长的阻塞,那么就有必要让低优先级任务尽快完成任务释放资源,在实施中可以用到两种协议来做到这一点:1. 优先级继承协议(pip);2.优先级置顶协议(pcp)。关于什么是优先级反转我就不多说了,这个概念在任何操作系统都有解释,可以嘲笑一下 windows nt关于优先级反转的解决办法,极其老土。
在linux内核中的/kernel/rtmutex.c源文件实现了pip。所谓的pip就是当一个高优先级进程请求一个已经被低优先级进程占有的资源时,此低优先级进程继承高优先级进程的优先级。我们先来熟悉一下数据结构,每个资源都表示为一个

struct rt_mutex {

spinlock_t wait_lock; //操作该结构的自旋锁

struct plist_head wait_list; //该资源的等待队列,队列按照任务优先级排队,表示等待此资源的进程们

struct task_struct *owner; //资源的当前拥有者

};

wait_list中的每一个元素都是一个rt_mutex_waiter,从字面上理解就是一个等待者

struct rt_mutex_waiter {

struct plist_node list_entry; //排入rt_mutex中的wait_list的链表节点

struct plist_node pi_list_entry; //排入另一个队列的链表节点,一会分析这“另一个队列”

struct task_struct *task; //参与等待的实体,一个进程

struct rt_mutex *lock; //等待拥有的资源,一个rt_mutex

};

如 果说一个进程只能拥有一个资源,那么rt_mutex_waiter里面根本不需要pi_list_entry,但是这个限制未免也太苛刻了,因此,为了使得进程占有资源拥有最大自由度,上面提到的“另一个队列”就出现了。考虑一个进程拥有多个资源,那么该进程的当前优先级应该为该进程拥有的所有资源的等待队列中最高优先级进程的优先级,因此在task_struct中就有一个队列pi_waiters来链接该进程拥有的所有资源的等待队列的最高优先级的 rt_mutex_waiter,而链入此队列的节点就是rt_mutex_waiter中的pi_list_entry了。下面分析代码:

static int __sched rt_mutex_slowlock(struct rt_mutex *lock, int state,

struct hrtimer_sleeper *timeout,

int detect_deadlock)

{

struct rt_mutex_waiter waiter;

int ret = 0;

debug_rt_mutex_init_waiter(&waiter);

waiter.task = NULL;

spin_lock(&lock->wait_lock);

/* Try to acquire the lock again: */

if (try_to_take_rt_mutex(lock)) {

spin_unlock(&lock->wait_lock);

return 0;

}

set_current_state(state);

/* Setup the timer, when timeout != NULL */

if (unlikely(timeout))

hrtimer_start(&timeout->timer, timeout->timer.expires,

HRTIMER_ABS);

for (;;) {

if (try_to_take_rt_mutex(lock))//尝试得到锁,办法:1.直接得到;2.偷。

break;

if (unlikely(state == TASK_INTERRUPTIBLE)) {

/* Signal pending? */

if (signal_pending(current))

ret = -EINTR;

if (timeout && !timeout->task)

ret = -ETIMEDOUT;

if (ret)

break;

}

//进入下面的if的条件:1.首次进入;2.锁被偷走

if (!waiter.task) {

ret = task_blocks_on_rt_mutex(lock, &waiter,

detect_deadlock);

/*

* If we got woken up by the owner then start loop

* all over without going into schedule to try

* to get the lock now:

*/

if (unlikely(!waiter.task))

continue;

if (unlikely(ret))

break;

}

spin_unlock(&lock->wait_lock);

debug_rt_mutex_print_deadlock(&waiter);

if (waiter.task)

schedule_rt_mutex(lock);//即便我们从此处被唤醒,也不一定能得到锁,因为被唤醒的时候lock的owner只是设置为pending状态,见wakeup_next_waiter,而在那个函数中,又在唤醒前将此waiter的task字段清空,于是当循环到达上面的if (!waiter.task)时,又将进入if内部,之所以循环到if上面执行

try_to_take_rt_mutex的时候没有成功,是因为刚刚设置为pending的锁本来要让被唤醒的进程拥有的,但是还没有正式拥有前,锁被“偷”走了,于是再次进入if语句,一切重新开始。

spin_lock(&lock->wait_lock);

set_current_state(state);

}

set_current_state(TASK_RUNNING);

if (unlikely(waiter.task))

remove_waiter(lock, &waiter);

/*

* try_to_take_rt_mutex() sets the waiter bit

* unconditionally. We might have to fix that up.

*/

fixup_rt_mutex_waiters(lock);

spin_unlock(&lock->wait_lock);

/* Remove pending timer: */

if (unlikely(timeout))

hrtimer_cancel(&timeout->timer);

/*

* Readjust priority, when we did not get the lock. We might

* have been the pending owner and boosted. Since we did not

* take the lock, the PI boost has to go.

*/

if (unlikely(ret))

rt_mutex_adjust_prio(current);

debug_rt_mutex_free_waiter(&waiter);

return ret;

}

之所以给了盗贼以偷锁的机会,使因为在wakeup_next_waiter函数中,以下语句:
1.pendowner = waiter->task;
2.waiter->task = NULL;
3.rt_mutex_set_owner(lock, pendowner, RT_MUTEX_OWNER_PENDING);
注意语句3并没有将锁真正给了pendowner,而只是预定一下,设为pending状态,至于能否真正得到还要等这个pendowner被唤醒之后循环到rt_mutex_slowlock中的for(;;)中 的try_to_take_rt_mutex语句,然后参加竞争,偷或者按常规得到。wakeup_next_waiter函数除了上面的3条语句外还有 一个逻辑,就是更新新被唤醒的waiter的pi_waiters链表,当然一旦这个候选者本应该得到的锁被偷走,这一切就白费了,这个候选者想得到锁必 须从头再来。至于偷锁的过程我就不说了,自己看吧,很短的代码。

static int task_blocks_on_rt_mutex(struct rt_mutex *lock,

struct rt_mutex_waiter *waiter,

int detect_deadlock)// 此函数在请求资源没有成功的时候被调用,主要目的就是:1.初始化一个waiter,然后链入lock的wait_list 队列;2.如果这个 waiter是此lock的最高优先级的waiter,那么还要将此waiter链入lock拥有者的pi_waiters队列(别忘 了,task_struct中的pi_waiters就是链接此task_struct拥有资源的众多wait_list中最高优先级 waiter 的);3.如果2被执行,那么还要调整lock拥有者的优先级;4.如果lock拥有者优先级被调整可能引起链式反应,因为lock拥有者 可能在请求别的资源,那么就有可能连锁地调整一系列进程的优先级。

{

struct task_struct *owner = rt_mutex_owner(lock);

struct rt_mutex_waiter *top_waiter = waiter;

unsigned long flags;

int chain_walk = 0, res;

spin_lock_irqsave(&current->pi_lock, flags);

__rt_mutex_adjust_prio(current);

waiter->task = current;

waiter->lock = lock;

plist_node_init(&waiter->list_entry, current->prio);

plist_node_init(&waiter->pi_list_entry, current->prio);

/* Get the top priority waiter on the lock */

if (rt_mutex_has_waiters(lock))

top_waiter = rt_mutex_top_waiter(lock);

plist_add(&waiter->list_entry, &lock->wait_list); //将新的waiter链入lock的wait_list

current->pi_blocked_on = waiter; //至此,新的waiter初始化完毕

spin_unlock_irqrestore(&current->pi_lock, flags);

if (waiter == rt_mutex_top_waiter(lock)) { // 如果这个新waiter是lock的wait_list的最高优先级waiter,那么就要把此waiter链入lock拥有者的pi_waiters,在链入新waiter之前要删去老的waiter,因为老的waiter已经不是lock拥有者所拥有的rt_mutex之一的wait_list的最高 优先级waiter了。

spin_lock_irqsave(&owner->pi_lock, flags);

plist_del(&top_waiter->pi_list_entry, &owner->pi_waiters);

plist_add(&waiter->pi_list_entry, &owner->pi_waiters);

__rt_mutex_adjust_prio(owner);//调整lock拥有者的优先级,pip在此体现

if (owner->pi_blocked_on)

chain_walk = 1;

spin_unlock_irqrestore(&owner->pi_lock, flags);

}

else if (debug_rt_mutex_detect_deadlock(waiter, detect_deadlock))

chain_walk = 1;

if (!chain_walk)

return 0;

get_task_struct(owner);

spin_unlock(&lock->wait_lock);

res = rt_mutex_adjust_prio_chain(owner, detect_deadlock, lock, waiter, current);//处理连锁反应,在owner也在请求资源的情况下,那个owner请求的资源的拥有者的优先级也要调整,因为owner的优先级可能已经被调整,而owner可能就是owner请求资源的wait_list中的最高优先级waiter。

spin_lock(&lock->wait_lock);

return res;

}

一 旦调整了占有资源的低优先级进程优先级,那么就要处理一条链了,因为这个资源的占有者虽然在这次请求中以占有者的身份出现,它本身还可能是别的资源的请求者,它的优先级改变了,如果说它正在请求一个资源r,而它又是r的wait_list的最高优先级的waiter,那么r的拥有者的优先级相应也要调整, 这就是一个连锁反应,相应的,r的拥有者也可能导致相同的连锁反应...
最后就以这个连锁反应的处理和优先级继承的过程来结束linux内核的pip机制进而转向uc/os ii内核的pcp机制,为了简单起见,我省去了死锁检测

static int rt_mutex_adjust_prio_chain(struct task_struct *task,

int deadlock_detect,

struct rt_mutex *orig_lock,

struct rt_mutex_waiter *orig_waiter,

struct task_struct *top_task)

{

struct rt_mutex *lock;

struct rt_mutex_waiter *waiter, *top_waiter = orig_waiter;

int detect_deadlock, ret = 0, depth = 0;

unsigned long flags;

detect_deadlock = debug_rt_mutex_detect_deadlock(orig_waiter, deadlock_detect);

again:

...

retry:

spin_lock_irqsave(&task->pi_lock, flags);

waiter = task->pi_blocked_on;//waiter为task的等待入口点

if (!waiter || !waiter->task)

goto out_unlock_pi;

if (top_waiter && (!task_has_pi_waiters(task) ||

top_waiter != task_top_pi_waiter(task)))

goto out_unlock_pi;

if (!detect_deadlock && waiter->list_entry.prio == task->prio)//没有调整,进而没有引起连锁反应,直接返回。

goto out_unlock_pi;

lock = waiter->lock;

if (!spin_trylock(&lock->wait_lock)) {

spin_unlock_irqrestore(&task->pi_lock, flags);

cpu_relax();

goto retry;

}

...

top_waiter = rt_mutex_top_waiter(lock);//得到重新排队前的老的最高优先级的waiter

//以下将waiter重新排队,因为task的prio可能已经变化了

plist_del(&waiter->list_entry, &lock->wait_list);

waiter->list_entry.prio = task->prio;

plist_add(&waiter->list_entry, &lock->wait_list);

/* Release the task */

spin_unlock_irqrestore(&task->pi_lock, flags);

put_task_struct(task);

//得到task请求资源的拥有者,并且赋值给task,这时的焦点就是task请求资源的拥有者了。

task = rt_mutex_owner(lock);

get_task_struct(task);

spin_lock_irqsave(&task->pi_lock, flags);

if (waiter == rt_mutex_top_waiter(lock)) {//如果waiter现在成了最高优先级的waiter,那么就应该调整task优先级了。

/* Boost the owner */

plist_del(&top_waiter->pi_list_entry, &task->pi_waiters);

waiter->pi_list_entry.prio = waiter->list_entry.prio;

plist_add(&waiter->pi_list_entry, &task->pi_waiters);

__rt_mutex_adjust_prio(task);

} else if (top_waiter == waiter) { //如果waiter曾经是最高优先级的waiter现在却不再是了,那么也要调整task的优先级

/* Deboost the owner */

plist_del(&waiter->pi_list_entry, &task->pi_waiters);

waiter = rt_mutex_top_waiter(lock);

waiter->pi_list_entry.prio = waiter->list_entry.prio;

plist_add(&waiter->pi_list_entry, &task->pi_waiters);

__rt_mutex_adjust_prio(task);

}

spin_unlock_irqrestore(&task->pi_lock, flags);

top_waiter = rt_mutex_top_waiter(lock); //准备进行下一轮。

spin_unlock(&lock->wait_lock);

if (!detect_deadlock && waiter != top_waiter)

goto out_put_task;

goto again;

out_unlock_pi:

spin_unlock_irqrestore(&task->pi_lock, flags);

out_put_task:

put_task_struct(task);

return ret;

}

看一下一切的结果__rt_mutex_adjust_prio函数:

static void __rt_mutex_adjust_prio(struct task_struct *task)

{

int prio = rt_mutex_getprio(task);

if (task->prio != prio)

rt_mutex_setprio(task, prio);

}

int rt_mutex_getprio(struct task_struct *task)

{

if (likely(!task_has_pi_waiters(task)))//取出task的pi_waiters中的最高优先级waiter的优先级,别忘了pi_waiters是干什么的。

return task->normal_prio;

return min(task_top_pi_waiter(task)->pi_list_entry.prio,

task->normal_prio);

}

以 上就是linux内核的pip协议的全部,我们可以从代码中看到,光是花在调整优先级上的开销就很大,代码量一点也不少,而且处理连锁反应更是要花费很大的开销,因此,pip对于实时性要求比较高的应用来说是不合适的,以上仅从静态代码的角度,如果从运行时的角度分析情况也不是很好,pip并没有消除死锁 隐患,pip是基于贪心算法的,它只保证局部的最优而在全局上没有任何保证。每次请求资源,最坏的情况很糟糕,高优先级的进程几乎每次都要被更低优先级的进程阻塞。有一种更加优化的,非贪心的算法---pcp协议可以免除死锁和过长时间的阻塞,因为它是全局考虑的,而不是局部考虑的,pcp协议将一个优先 级和一个资源永久绑定而不是等到一定条件时才绑定,就像pip协议中那样。一般来说,资源的优先级就是所有要用到此资源的进程的优先级中最高的那个加一个常量,一旦进程请求到了一个资源,那么该进程的优先级马上提升到该资源的优先级,因为这个资源优先级比所有要用到它的进程的优先级都要高,那么在这个得到 资源的进程释放资源之前,别的进程根本没有机会请求资源,因此,一旦进程请求资源,该资源是立马可以申请成功的。uc/osii就是用的这种方式。该协议 的主要思想认为系统中的资源都很宝贵,为了不让请求者等待过长时间,那么保有资源的进程就应该尽快释放资源,为了使其尽快释放资源,系统会给它足够高的优先级以使其高速执行不被打扰,为了配合系统提供的这种机制,我们的策略实现中千万不要在资源保有期间进行睡眠等活动,这样就未免太贪心了,系统给你优先级 使你不被打扰是为了让你尽快释放资源而你却用来睡觉...
linux内核是一个通用的os内核,要照顾到的因素比较多,因此在“效率优先,兼顾公平”的原则下做事,毕竟内核不是为少数应用服务的,而uc/osii就不同了,它是一个专业的嵌入式实时内核,它的应用就少得多了,因此它不需要考虑那么多。

你可能感兴趣的:(linux)