内核抢占补丁在2.5系列中就已被打上,接下来在2.6中也会打。这将显著地降低用户交互式应用程式、多媒体应用程式等类似应用程式的延迟。这一特性对实时系统和嵌入式系统来说特别有用。
2.5的内核抢占模块的工作由 Robert Love 完成。在先前的内核版本中(包括2.4内核),不允许抢占以内核模式运行的任务(包括通过系统调用进入内核模式的用户任务),直到他们自己主动释放 CPU。
在内核2.6中,内核是可抢占的。一个内核任务能被抢占,为的是让重要的用户应用程式能继续运行。这样做最主要的优势在于,能极大地增强系统的用户交互性,用户将会觉得鼠标点击和击键的事件得到了更快速的响应。
当然,不是所有的内核代码段都能被抢占。能锁定内核代码的关键部分,不允许抢占。锁定能确保每个 CPU 的数据结构和状态始终受到保护而不被抢占。
以下的代码片断显示了每个 CPU 的数据结构问题(在SMP系统中):
清单 1. 存在内核抢占问题的代码
int arr[NR_CPUS];
arr[smp_processor_id()] = i;
/* kernel preemption could happen here */
j = arr[smp_processor_id()] /* i and j are not equal as
smp_processor_id() may not be the same */
在这种情形下,如果在特定点发生了内核抢占,任务将会由于重新调度而被分配到其他处理器??smp_processor_id() 将返回一个不同的值。
这种情形应该通过锁定来进行保护。
FPU 模式是另外一种CPU应该被保护起来不被抢占的情形。当内核在执行浮点指令时,FPU 状态不被保存。如果这时发生了抢占,由于重新调度,FPU 状态就会和抢占前完全不同。所以 FPU 代码必须始终被锁定,以防止内核抢占。
锁定能这样来实现,在关键部分禁止抢占,在之后再激活抢占。以下是在2.6内核中禁止和激活抢占的定义:
- preempt_enable() -- 抢占计数器减1
- preempt_disable() -- 抢占计数器加1
- get_cpu() -- 先后调用 preempt_disable() 和 smp_processor_id()
- put_cpu() -- 重新激活preemption()
使用这些定义,清单 1能重写成这样:
清单 2. 使用防抢占锁的代码
int cpu, arr[NR_CPUS];
arr[get_cpu()] = i; /* disable preemption */
j = arr[smp_processor_id()];
/* do some critical stuff here */
put_cpu() /* re-enable preemption */
注意 preempt_disable()/enable()调用是能嵌套的。也就是说,preempt_disable() 能被调用 n 次,只有当第 n 次 preempt_enable() 被调用后,抢占才被重新激活。
当使用自旋锁时,抢占是被隐式地禁止的。例如,一个 spin_lock_irqsave() 调用会隐式地通过调用 preempt_disable() 来防止抢占;spin_unlock_irqrestroe() 通过调用 preempt_enable() 来重新激活抢占。