A realtime preemption overview(2005-08-10/Paul McKenney)
实时抢占补丁概观
Yang Honggang<eagle.rtlinux@gmail.com>
ref: http://lwn.net/Articles/146861/
----------------------------------------
////PREEMPT_RT的思想
PREEMPT_RT补丁的核心是最小化(Linux)内核中不可抢占部分的代码,同时又将
为支持抢占性必须要修改的代码量最小化。
临界区、中断处理函数、关中断等代码序列通常是进行抢占改进的。
PREEMPT_RT补丁利用Linux内核的SMP特性来进行可抢占改进,这样就避免了对
重写整个Linux代码。
从某种程度上讲,我们可以简单地认为抢占是在系统中添加了一个新的CPU,
然后利用通常的锁机制来对抢占任务进行同步。
注意不要对上面的叙述死扣,比如在每次抢占时PREEMPT_RT并没有产生
CPU热插拔事件。关键是底层SMP环境必须提供容忍自由抢占的机制。
下面的章节给出来PREEMPT_RT的思想是怎样实施的。
////PREEMPT_RT特性
1. 临界区可抢占
2. 中断处理函数可抢占
3. "关中断"代码序列可抢占
4. 内核中的spinlock和semaphore支持优先级继承
5. 延迟操作
6. 降低延迟的措施
下面分别介绍:
/// 1. 临界区可抢占
在PREEMPT-RT中通常的spinlock(spinlock_t和rwlock_t)、RCU“读部分”的临界区
(rcu_read_lock()和rcu_read_unlock())都是可抢占的。
Semaphore临界区也是可以抢占的(没有打PREEMPT_RT补丁普通内核也是这样的)。
该可抢占性意味着当获取 spinlock时也可以阻塞,反过来,当关闭中断或者抢占时,
不应该去申请spinlock。这也意味着spinlock_t中使用的spin_lock_irqsave()没有
关闭硬件中断。
测试#1: 在普通内核中怎样支持semaphore临界区抢占?
那么在中断或者抢占关闭的条件下需要申请锁时该如何去做?可以使用raw_spinlock_t
而不是spinlock_t。在raw_spinlock_t中会调用spin_lock()。
PREEMPT_RT中引入了一系列的宏让spin_lock()表现的像C++中的重载一样。当在raw_spinlock_t
中调用时,它表现为传统的spinlock,当在spinlock_t中调用时,它的临界区又可被抢占。
例如,多种_irq原语(如spin_lock_irqsave())用在raw_spinlock_t中时,会关闭硬件中断。
但是,用在spinlock_t中时却不会关闭硬件中断。然而,对于raw_spinlock_t(以及相应的rwlock_t
、raw_rwlock_t)的使用不遵循该规则。在调度器、平台相关的代码和RCU等很少的底层部分才
需要这些raw lock,其他地方用不到。
因为临界区可以被抢占,那么不能指望给定的临界区仅在一个固定的CPU上执行。由于抢占的原因,
它可能会迁移到另一个不同的CPU上运行。这样,当在临界区中使用per-CPU变量时,就需要
单独的处理可能发生的抢占带来的问题。因为spinlock_t和rwlock_t不会处理这些事情。
可行的方法有:
1. 通过get_cpu_var()、preempt_disable()或者关闭硬件中断等显式地关闭抢占。
2. 使用per-CPU锁保护per-CPU变量。一种方法是使用DEFINE_PER_CPU_LOCKED(),之后会更多
的介绍。
因为现在spin_lock()可以睡眠,那么需要增加额外的任务状态。看下Ingo Molnar提供的
代码片段:
spin_lock(&mylock1);
current->state = TASK_UNINTERRUPTIBLE;
spin_lock(&mylock2); // [*]
blah();
spin_unlock(&mylock2);
spin_unlock(&mylock1);
由于[*]处的spin_lock可能会睡眠,这样就会破坏current->state的值。这对blah()来说是不应该
发生的。因此,引入了TASK_RUNNING_MUTEX位来告知调度器在调度前对之前的current->state值进行
保护。虽然这么做有些怪怪的,但是这么做确实可以在代码修改量最小的前提下支持临界区抢占。
并且,这么做使得同样的代码可以在PREEMPT_RT, PREEMPT和non-PREEMPT配置下工作。