Linux操作系统内核抢占补丁的基本原理

CPU在内核中运行时并不是处处不可抢占的 , 内核中存在一些空隙 , 在这时进行抢占是安全的 , 内核抢占补丁的基本原理就是将SMP可并行的代码段看成是可以进行内核抢占的区域 。
2.4内核正好细化了多CPU下的内核线程同步机构 , 对不可并行的指令块用spinlock和rwlock作了细致的表示 , 该补丁的实现可谓水到渠成 。具体的方法就是在进程的任务结构上增加一个preempt_count变量作为内核抢占锁 , 它随着spinlock和rwlock一起加锁和解锁 。当preempt_count为0时表示可以进行内核调度 。内核调度器的入口为preempt_schedule() , 它将当前进程标记为TASK_PREEMPTED状态再调用schedule() , 在TASK_PREEMPTED状态 , schedule()不会将进程从运行队列中删除 。
下面是内核抢占补丁的主要代码示意:
【Linux操作系统内核抢占补丁的基本原理】
arch/i386/kernel/entry.S:
preempt_count = 4 # 将task_struct中的flags用作preempt_count,flags被移到了别
的位置
ret_from_exception: # 从异常返回
#ifdef CONFIG_SMP
GET_CURRENT(離)
movl processor(離),陎
shll $CONFIG_X86_L1_CACHE_SHIFT,陎
movl SYMBOL_NAME(irq_stat)(,陎),靫 # softirq_active
testl SYMBOL_NAME(irq_stat) 4(,陎),靫 # softirq_mask
#else
movl SYMBOL_NAME(irq_stat),靫 # softirq_active
testl SYMBOL_NAME(irq_stat) 4,靫 # softirq_mask
#endif
jne handle_softirq
#ifdef CONFIG_PREEMPT
cli
incl preempt_count(離) # 异常的入口没有禁止内核调度的指令,与ret_from_intr
匹配一下
#endif
ENTRY(ret_from_intr) # 硬件中断的返回
GET_CURRENT(離)
#ifdef CONFIG_PREEMPT
cli
decl preempt_count(離) # 恢复内核抢占标志
#endif
movl EFLAGS(%esp),陎 # mix EFLAGS and CS
movb CS(%esp),%al
testl $(VM_MASK | 3),陎 # return to VM86 mode or non-supervisor?
jne ret_with_reschedule
#ifdef CONFIG_PREEMPT
cmpl $0,preempt_count(離)
jnz restore_all # 如果preempt_count非零则表示禁止内核抢占
cmpl $0,need_resched(離)
jz restore_all #
movl SYMBOL_NAME(irq_stat) irq_stat_local_bh_count CPU_INDX,靫
addl SYMBOL_NAME(irq_stat) irq_stat_local_irq_count CPU_INDX,靫
jnz restore_all
incl preempt_count(離)
sti
call SYMBOL_NAME(preempt_schedule)
jmp ret_from_intr # 新进程返回,返回ret_from_intr恢复抢占标志后再返回
#else
jmp restore_all
#endif
ALIGN
handle_softirq:
#ifdef CONFIG_PREEMPT
cli
GET_CURRENT(離)
incl preempt_count(離)
sti
#endif
call SYMBOL_NAME(do_softirq)
jmp ret_from_intr
ALIGN
reschedule:
call SYMBOL_NAME(schedule) # test
jmp ret_from_sys_call
include/asm/hw_irq.h:
...
#ifdef CONFIG_PREEMPT
#define BUMP_CONTEX_SWITCH_LOCK
GET_CURRENT
"incl 4(離)nt"
#else
#define BUMP_CONTEX_SWITCH_LOCK
#endif
#define SAVE_ALL硬件中断保护入口现场
"cldnt"
"pushl %esnt"
"pushl %dsnt"
"pushl 陎nt"
"pushl 雙nt"
"pushl 韎nt"
"pushl %esint"
"pushl 韝nt"
"pushl 靫nt"
"pushl 離nt"
"movl $" STR(__KERNEL_DS) ",韝nt"
"movl 韝,%dsnt"
"movl 韝,%esnt"
BUMP_CONTEX_SWITCH_LOCK # 硬件中断的入口禁止内核抢占
include/linux/spinlock.h:
#ifdef CONFIG_PREEMPT
#define switch_lock_count() current->preempt_count
#define in_ctx_sw_off() (switch_lock_count().counter) 判断当前进程的抢占计数
是否非零
#define atomic_ptr_in_ctx_sw_off() (&switch_lock_count())
#define ctx_sw_off()禁止内核抢占
do {
atomic_inc(atomic_ptr_in_ctx_sw_off());当前进程的内核抢占计数增1
} while (0)
#define ctx_sw_on_no_preempt()允许内核抢占
do {
atomic_dec(atomic_ptr_in_ctx_sw_off());当前进程的内核抢占计数减1
} while (0)
#define ctx_sw_on()允许并完成内核抢占
do {
if (atomic_dec_and_test(atomic_ptr_in_ctx_sw_off()) &&
current->need_resched)
preempt_schedule();

推荐阅读