本文主要梳理优先级继承的基本原理,优先级继承主要是用来解决优先级反转,可以简单理解为
一个进程在当前进程拥有的锁上阻塞时,会继承另一个进程的优先级。举个例子说明,进程A、
B、C,其中A优先级最高,B次之,C最小,那么当 A 在 C 拥有的锁上阻塞时,C 将继承 A 的优先
级。因此,如果 B 变为可运行状态,它将不会抢占 C,因为此时 C 具有 A 的高优先级。一旦 C 释
放锁,它将失去继承的优先级,然后 A 可以继续使用 C 拥有的资源。
在linux源码实现中,位置:kernel/locking/rtmutex.c,主要涉及到两个重要步骤:
1、优先级调整;
2、PI(优先级继承)链遍历
优先级调整
该部分实现任务调整的函数分别是rt_mutex_adjust_prio和rt_mutex_setprio,其中
rt_mutex_setprio在rt_mutex_adjust_prio中被调用,rt_mutex_setprio的实现则实在
kernel/sched/core.c中。
static void rt_mutex_adjust_prio(struct task_struct *p) { struct task_struct *pi_task = NULL; lockdep_assert_held(&p->pi_lock); // 确保 pi_lock 被持有 // 检查是否有其他任务在等待该实时互斥锁 if (task_has_pi_waiters(p)) pi_task = task_top_pi_waiter(p)->task; // 获取优先级继承任务 // 调整优先级 rt_mutex_setprio(p, pi_task); }
rt_mutex_adjust_prio检查任务的优先级以及等待任务拥有的所有互斥锁中最高优先级的进
程。然后调用rt_mutex_setprio来将任务的优先级调整为新的优先级。
总的来说rt_mutex_adjust_prio可以增加或降低任务的优先级。如果更高优先级的进程刚好在任务
拥有的互斥锁上阻塞,rt_mutex_adjust_prio将增加/提升任务的优先级。但是,如果由于某种原因
更高优先级的任务离开了互斥锁(超时或信号),则该函数将降低任务的优先级。
void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) { int prio, oldprio, queued, running, queue_flag = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK; const struct sched_class *prev_class; struct rq_flags rf; struct rq *rq; trace_android_rvh_rtmutex_prepare_setprio(p, pi_task);//记录优先级变化 /* XXX used to be waiter->prio, not waiter->task->prio */ prio = __rt_effective_prio(pi_task, p->normal_prio);//获取到任务的优先级 if (p->pi_top_task == pi_task && prio == p->prio && !dl_prio(prio)) return; rq = __task_rq_lock(p, &rf); update_rq_clock(rq); p->pi_top_task = pi_task; if (prio == p->prio && !dl_prio(prio))//如果新的优先级和旧的优先级相同 goto out_unlock; if (unlikely(p == rq->idle)) { //如果是空闲任务 WARN_ON(p != rq->curr); WARN_ON(p->pi_blocked_on); goto out_unlock; } trace_sched_pi_setprio(p, pi_task); oldprio = p->prio; if (oldprio == prio) queue_flag &= ~DEQUEUE_MOVE; prev_class = p->sched_class; queued = task_on_rq_queued(p); running = task_current(rq, p); if (queued) dequeue_task(rq, p, queue_flag); if (running) put_prev_task(rq, p); if (dl_prio(prio)) { if (!dl_prio(p->normal_prio) || (pi_task && dl_prio(pi_task->prio) && dl_entity_preempt(&pi_task->dl, &p->dl))) { p->dl.pi_se = pi_task->dl.pi_se; queue_flag |= ENQUEUE_REPLENISH; } else { p->dl.pi_se = &p->dl; } } else if (rt_prio(prio)) { if (dl_prio(oldprio)) p->dl.pi_se = &p->dl; if (oldprio < prio) queue_flag |= ENQUEUE_HEAD; } else { if (dl_prio(oldprio)) p->dl.pi_se = &p->dl; if (rt_prio(oldprio)) p->rt.timeout = 0; } __setscheduler_prio(p, prio); if (queued) enqueue_task(rq, p, queue_flag); if (running) set_next_task(rq, p); check_class_changed(rq, p, prev_class, oldprio); out_unlock: /* Avoid rq from going away on us: */ preempt_disable(); rq_unpin_lock(rq, &rf); __balance_callbacks(rq); raw_spin_unlock(&rq->lock); preempt_enable(); }
rt_mutex_setprio函数通过允许高优先级任务抢占持有它们所需资源的低优先级任务,维护系
统的响应性,即执行优先级继承和实时调度的原则。
PI(优先级继承)链遍历
PI链遍历由函数rt_mutex_adjust_prio_chain实现,该函数源码较长,本文不进行贴出,对主
要步骤进程阐述:
1、深度检查:
使用变量 depth 跟踪锁的深度。如果超过最大深度(max_lock_depth),则报告死锁并返回错误代码 -EDEADLK。
2、自旋锁保护:
使用自旋锁 (raw_spin_lock_irq) 保护对任务结构体 task 的访问,确保在调整优先级时不会被其他任务打断。
3、等待者检查:
检查当前任务在互斥锁上的阻塞状态 pi_blocked_on。
验证原始等待者 orig_waiter 是否为实时真实等待者,并确保其约束条件满足。
4、锁的重试:
尝试获取锁(raw_spin_trylock),如果失败,则释放自旋锁并让 CPU 空闲,然后重试。
5、处理优先级调整:
如果当前持有者和 next_lock 一致,则判断是否需要返回死锁或进行重排。
如果不需要进行重排,则获取持有者的任务并继续处理。
6、优先级的入队与出队:
调整优先级队列。如果当前任务是锁的顶层等待者,进行相应的优先级调整;如果不是,则进行入队和出队操作。
7、锁的状态更新:
更新当前任务状态以处理后续的锁请求。
最后,清理任务结构体,释放锁,并返回处理结果。