2006 年内核峰会接受了将实时相关组件合并到主线的计划,随后作为实时补丁集(PREEMPT_RT)的一部分,rt_mutex在 Linux 2.6.18 版本中被合并到内核主线。
rt_mutex和普通的mutex相比,主要的特征是支持优先级继承,是为了解决优先级反转引入的。rt_mutex 运用优先级继承机制(见 rt_mutex_adjust_prio_chain)来避免优先级反转,这种机制同样适用于睡眠自旋锁(sleeping spinlocks)和读写锁(rwlocks)。
想要理解rt_mutex的作用,首先需要了解优先级反转相关的知识,这样才能明白它 是如何影响系统实时性能的,以及为什么优先级继承可以解决这个问题。
实时系统中的优先级反转问题
一、定义
在实时操作系统(RTOS)中,优先级反转是指高优先级任务被低优先级任务间接阻塞,使得高优先级任务不能按照其优先级及时执行,而低优先级任务却能优先运行的异常现象。这种现象违背了 RTOS 正常的基于优先级的调度机制。
二、产生原因
- 资源共享与互斥访问:当多个任务需要共享某些资源时,为保证数据一致性和资源的正确使用,这些资源往往通过互斥机制(如互斥锁)来进行访问控制。例如,一个打印机资源在同一时间只能被一个任务使用,当一个任务获取了打印机的控制权(锁),其他任务就必须等待该任务释放锁后才能使用打印机。
- 优先级调度规则:RTOS 通常按照任务优先级来调度任务执行,高优先级任务会优先于低优先级任务运行。然而,当低优先级任务获取了高优先级任务所需的共享资源时,情况就变得复杂。
- 具体场景示例:假设有三个任务:高优先级任务 (H)、中优先级任务 (M) 和低优先级任务 (L),存在一个共享资源 (R)。低优先级任务 (L) 首先获取了共享资源 (R) 并开始使用。此时,高优先级任务 (H) 就绪,它试图获取资源 (R),但由于资源 (R) 被 (L) 占用, (H) 只能等待 (L) 释放资源 (R)。在 (L) 使用资源 (R) 的过程中,中优先级任务 (M) 就绪,由于 (M) 的优先级高于 (L),根据调度规则, (M) 抢占 (L) 的执行权开始运行。这样,高优先级任务 (H) 虽然优先级最高,但因为等待低优先级任务 (L) 占用的资源,反而被中优先级任务 (M) 间接阻塞,导致优先级反转。
资源 R低优先级任务 L中优先级任务 M高优先级任务 H资源 R低优先级任务 L中优先级任务 M高优先级任务 H请求并获取资源 R请求资源 R资源正被 L 使用,等待抢占 L 的执行权执行任务等待资源 R,无法执行等待 M 释放执行权释放执行权释放资源 R资源 R 可用获取资源 R执行任务
三、带来的影响
- 任务响应延迟:高优先级任务通常被设计为处理紧急或关键的事务,如实时数据采集、安全相关的控制等。优先级反转会导致这些高优先级任务不能及时执行,从而延迟对重要事件的响应,可能影响整个系统的实时性和稳定性。
- 系统性能下降:如果优先级反转频繁发生,会使得系统整体性能下降,因为高优先级任务不能高效执行,可能导致其他依赖高优先级任务结果的任务也受到影响,形成连锁反应,降低系统的整体吞吐量。
四、解决方法
优先级继承:
- 原理:当高优先级任务因等待低优先级任务占用的资源而阻塞时,低优先级任务的优先级会临时提升到与高优先级任务相同。这样,低优先级任务就可以尽快完成对共享资源的操作并释放资源,减少高优先级任务的阻塞时间。当低优先级任务释放资源后,其优先级恢复到原来的水平。
- 示例:在前面提到的场景中,当 (H) 等待 (L) 占用的资源 (R) 时,系统将 (L) 的优先级提升到与 (H) 相同。此时, (M) 无法抢占 (L), (L) 可以尽快完成对资源 (R) 的使用并释放它,然后 (H) 就能获取资源 (R) 并执行,避免了高优先级任务 (H) 被长时间阻塞。
资源 R低优先级任务 L中优先级任务 M高优先级任务 H资源 R低优先级任务 L中优先级任务 M高优先级任务 H请求并获取资源 R请求资源 R资源正被 L 使用,等待优先级提升至 H 的优先级尝试抢占 L 的执行权拒绝,当前优先级与 H 相同继续执行并释放资源 R资源 R 可用获取资源 R执行任务优先级恢复原级别
优先级天花板:
- 原理:为每个共享资源设定一个优先级天花板,该优先级天花板是所有可能使用该资源的任务中优先级最高的那个。当一个任务获取资源时,它的优先级会被临时提升到该资源的优先级天花板,直到释放资源。通过这种方式,在任务获取资源期间,其他中等优先级任务无法抢占,从而避免优先级反转。
- 示例:若资源 (R) 的优先级天花板是高优先级任务 (H) 的优先级,当 (L) 获取资源 (R) 时, (L) 的优先级会被提升到 (H) 的优先级。这样,在 (L) 使用资源 (R) 的过程中, (M) 无法抢占 (L),有效地避免了优先级反转问题。
资源 R低优先级任务 L中优先级任务 M高优先级任务 H资源 R低优先级任务 L中优先级任务 M高优先级任务 H设置优先级天花板为 H 的优先级请求并获取资源 R,L 优先级提升至天花板(H 的优先级)请求资源 R资源正被 L 使用,等待尝试抢占 L 的执行权拒绝,当前优先级与 H 相同继续执行并释放资源 R资源 R 可用获取资源 R执行任务优先级恢复原级别
rt_mutex的作用及使用
在Linux内核中,rt_mutex(实时互斥锁)是为满足实时应用对低延迟和避免优先级反转的需求而设计的同步原语。实时应用要求对事件的响应具有确定性,而传统的内核互斥锁在某些情况下可能导致优先级反转等问题,影响实时性,rt_mutex则致力于解决这些问题。
特性
- 优先级继承:这是
rt_mutex的核心特性之一。当高优先级任务尝试获取被低优先级任务持有的rt_mutex时,低优先级任务的优先级会临时提升到与高优先级任务相同。这样做的目的是让持有锁的低优先级任务尽快执行完毕并释放锁,从而减少高优先级任务的等待时间,避免优先级反转。例如,假设有高优先级任务H、中优先级任务M和低优先级任务L,L持有rt_mutex,H试图获取该锁,此时L的优先级将提升到H的优先级,M就无法抢占L,L可以尽快释放锁给H。 - 死锁检测:
rt_mutex具备死锁检测机制。内核通过跟踪任务对rt_mutex的获取和释放情况,以及任务之间的等待关系,能够检测到潜在的死锁情况。一旦检测到死锁,内核可以采取相应的措施,如打印死锁相关信息,帮助开发者定位和解决问题。 - 快速路径优化:为了提高性能,
rt_mutex实现了快速路径。在常见情况下,即没有竞争的情况下,获取和释放锁的操作可以在非常低的开销下完成。这有助于减少同步操作对系统性能的影响,满足实时系统对高效性的要求。
使用方法
- 初始化:在使用
rt_mutex之前,需要对其进行初始化。可以使用rt_mutex_init函数,示例如下:
#include <linux/rtmutex.h> struct rt_mutex my_rt_mutex; static int __init my_module_init(void) { rt_mutex_init(&my_rt_mutex); // 其他初始化代码 return 0; }- 获取锁:任务通过调用
rt_mutex_lock函数来获取rt_mutex。如果锁当前可用,任务将立即获取锁并继续执行;如果锁被其他任务持有,任务会被加入等待队列,并可能触发优先级继承机制。
rt_mutex_lock(&my_rt_mutex); // 临界区代码,访问共享资源 rt_mutex_unlock(&my_rt_mutex);- 释放锁:任务使用完共享资源后,调用
rt_mutex_unlock函数释放rt_mutex。如果有其他任务在等待该锁,内核会唤醒等待队列中的最高优先级任务。
rt_mutex_unlock(&my_rt_mutex);注意事项
- 锁的粒度:选择合适的锁粒度非常重要。如果锁的粒度太粗,会导致多个任务长时间等待,降低系统并发性能;如果锁的粒度太细,又会增加锁的管理开销和死锁风险。开发者需要根据具体的应用场景和共享资源的使用模式来权衡锁的粒度。
- 嵌套使用:虽然
rt_mutex支持一定程度的嵌套获取,但过度嵌套会增加代码复杂度和死锁风险。在编写代码时,应