http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=700000&page=&view=&sb=&o=&vc=1
link: hylpro.cublog.cn
1. 2.6.25.4-rt
下载内核和rt-linux 补丁
http://www.kernel.org/pub/linux/kernel/projects/rt/patch-2.6.25.4-rt6.bz2
http://www.cn.kernel.org/pub/linux/kernel/v2.6/linux-2.6.25.4.tar.bz2
在解压缩后的内核目录,用下面命令把补丁打上:
patch -p1 -i patch-2.6.25.4-rt6
其主要修改的文件是: rcu/rtmutex/sched/lockdep/trace/arch/driver…
2. preempt_rt 实现简介
Ingo Molnar 带领一干人等,维护rt linux patch. 打完补丁后除了raw_spinlock 不容许抢占外,几乎所有内核代码都容许被抢占.主要途径
就是吧内核的大部分spinlock替换成支持PI的rt-mutexes,并把中断和软中断改造成内核线程.PI: priority inheritance,
a)Preemptible critical sections
普通的spinlocks,rwlock_t, 以及RCU read 操作容许抢占.以spin lock为例,打过补丁后spinlock分成两种:spinlock_t和
raw_spinlock_t. raw_spinlock_t就是原来的spinlock, spinlock_t则被替换成rt_mutex.
以下面的spin_lock为例
#define spin_lock(lock) PICK_SPIN_OP(spin_lock, _spin_lock, lock)
#define spin_lock_irq(lock) PICK_SPIN_OP(spin_lock_irq, _spin_lock_irq, lock)
#define spin_lock_bh(lock) PICK_SPIN_OP(spin_lock_bh, _spin_lock_bh, lock)
PICK_SPIN_OP 根据lock的类型选择对应的函数, 如果是raw_spinlock_t就选用前一个函数, 否则用后一个.
下面是spinlock_t选用的函数:
#ifdef CONFIG_PREEMPT_RT
# define _spin_lock(l) rt_spin_lock(l)
# define _spin_lock_nested(l, s) rt_spin_lock_nested(l, s)
# define _spin_lock_bh(l) rt_spin_lock(l)
# define _spin_lock_irq(l) rt_spin_lock(l)
通过上面的代码片断,可以看到, spin_lock_irq/bh 都使用了同一个函数:rt_spin_lock, 深入进去, _spin_lock_irq就根本没有关中断.
void lockfunc rt_spin_lock(spinlock_t lock)
{
spin_acquire(&lock->dep_map, 0, 0, _RETIP);
LOCK_CONTENDED_RT(lock, rt_mutex_trylock, __rt_spin_lock);
}
而raw_spinlock_t则对应于原来的实现.
spin_lock成为可抢占代码后, 某些关键路径如果不能容许使用这种spinlock: 如preemption已经被禁止,或者irq是disable的时候(这些情况下再使用可以被抢占的代码显然不合适: 进程已经明确要求经禁抢占了,还(有可能)强行调度当前进程显然比较操蛋.)
万一这种情况无可避免,就使用raw_spinlock_t来代替spinlock_t即可.(上面分析了,使用raw格式的spin, 保持其原有含义). 不过这种情况实属例外,一般除了底层的scheduler,RCU,体系相关代码用不着如此.
另一个问题是,spinlock_t所保护的代码现在可以抢占了,有可能迁徙到其他cpu去执行,这样per cpu的变量就要注意了:在spinlock_t保护下的per-cpu变量需要明确的禁止抢占,或者使用 DEFINE_PER_CPU_LOCKED()来定义per cpu var,通过get_cpu_var_locked来操作per cpu变量(呵呵,per cpu现在也锁上了).
Ingo Molnar 指出, 如下情况, blah函数会发现当前任务状态变成了running, 这是因为sched函数会唤醒进程的时候把进程状态重新置为running.
spin_lock(&mylock1);
current->state = TASK_UNINTERRUPTIBLE;
spin_lock(&mylock2); //
blah();
spin_unlock(&mylock2);
spin_unlock(&mylock1);
解决的办法是, 在sping lock 加锁睡眠的时候保存任务状态(rt_spin_lock_slowlock),然后由于rtmutex而被唤醒的进程任务状态被置为mutex running(wakeup_next_waiter->wake_up_process_mutex),而slowlock过程根据被唤醒后的 任务的状态决定是恢复保存的状态还是设置成TASK_RUNNING(见rt_spin_lock_slowlock).
b)Preemptible interrupt handlers
在PREEMPT_RT环境下,大部分中断运行在 process context环境, 是进程化了的中断. 可以指定中断处理函数的 SA_NODELAY属性从而使得中断处理函数依然处于interrupt context, 但是实际上只有fpu_irq(浮点), irq0(per cpu的timer中断), irq2, lpptest(中断延迟测定)指定这个属性. 只有per cpu timer属于经常性使用的. software timer (add_timer() and friends) 不运行在hardware interrupt context上而是运行于process context,是可抢占的.
指定SA_NODELAY 会增加很多延迟,要小心对待,不然很容易oops, deadlock.
per-CPU timer interrupt (e.g., scheduler_tick()) 运行于hardware-interrupt context,和process-context code 互斥需要用raw spinlocks (raw_spinlock_t or raw_rwlock_t), 进程端必须用_irq 类型的spin lock.和NO_DELAY硬件中断共享的per-CPU 变量在进程端也要关中断进行保护.
C) Preemptible "interrupt disable" code sequences
这个奇怪的提法是因为PREEMPT_RT的逻辑:针对spinlcok_t的spin_lock_irq并不关中断,在RT环境下,大部分中断作为一个进程来运行.和中断处理打交道的进程需要考虑到中断处理函数可能运行在另外一个cpu上.
spin_lock_irqsave() 和其他相关原语也不必要disable preemption:如果中断处理抢占了一个持有spinlock_t进的程,当这个中断处理函数试图获取这个锁的时候也会马上block,临界区依然得到保护.
local_irq_save也有禁止抢占的作用, 所以用spinlcok_t代替local_irq_save() 可以减少调度的延迟但是会降低SMP的效率.
在RT环境下和SA_NODELAY类型的中断处理函数打交道需要使用raw_spin_lock/raw_xxxx ,但是需要注意raw类型的应该仅仅用在scheduler, architecture-dependent code, RCU 就可以了.
(似乎早期RT patches local_irq_save也不关中断(??),仅仅关闭抢占而已, 新内核lock_irq_save == raw_lock_irq_save,就是多了hard irq trace的功能, 另外加了local_irq_save_rt/nort这些宏)
D) Priority inheritance for in-kernel spinlocks and semaphores
优先级继承适合于Rt kenrel下消除优先级翻转.对于spin_lock锁, 优先级翻转较为容易处理.
但是对于 writer-2-readers 的优先级继承就很有问题: 比如一个rw lock被多个readers read_lock, 然后每个reader都block 到一个rw lock,并且他们试图write_lock这个锁, 然而这个锁被许多reader read_lock住…. 这是个级数增长的过程, 几乎无解. RT kernel 简化这个问题,同一时间只容许一个任务read_hold一个reader-writer lock, 或者semaphore.
另外一个特殊情况是,semaphores 有时做PI反而坏事,比如sem被用于event,而不是锁, 因为你补知道谁将give 锁(sem用做事件时和lock相反:先block上, 这样没办法去PI, 我们只能在block on时才boost. 所以,这种情况下使用compat_semaphore,compat_rw_semaphore 这些变种.
E) Deferred operations
由于spin_lock() 可以sleep, 所以抢占禁止的时候不能调用这些函数,一些情况下这个由延迟处理来解决:
put_task_struct_delayed() 把put_task_struct()个函数放到一个队列中,直到可以调用spin_lock时才执行.
mmdrop_delayed() : 就是mmdrop() 的queue…
TIF_NEED_RESCHED_DELAYED :
这里顺带解释下TIF_NEED_RESCHED: 这个要从调度谈起.
Latency-reduction measures
There are a few changes in PREEMPT_RT whose primary purpose is to reduce scheduling or interrupt latency.
The first such change involves the x86 MMX/SSE hardware. This hardware is handled in the kernel with preemption disabled, and this sometimes means waiting until preceding MMX/SSE instructions complete. Some MMX/SSE instructions are no problem, but others take overly long amounts of time, so PREEMPT_RT refuses to use the slow ones.
The second change applies per-CPU variables to the slab allocator, as an alternative to the previous wanton disabling of interrupts.
参考资料:
http://rt.wiki.kernel.org/index.php/CONFIG_PREEMPT_RT_Patch 简介
http://lwn.net/Articles/146861/ A realtime preemption overview by Paul McKenney
http://www.ibm.com/developerworks/cn/linux/l-real-time-linux/ 实时linux的几种类型