PREEMPT_ACTIVE
thread_info中的preempt_count域设置当前进程是否可被抢占,但是我们还得注意下内核中会用到preempt_count() & PREEMPT_ACTIVE,这就是判断preempt_count 的PREEMPT_ACTIVE是否被置位,preempt_count的PREEMPT_ACTIVE位只有在内核抢占中才会被置位。
__irq_svc: svc_entry #ifdef CONFIG_PREEMPT get_thread_info tsk ldr r8, [tsk, #TI_PREEMPT] @ get preempt count add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] #endif irq_handler #ifdef CONFIG_PREEMPT str r8, [tsk, #TI_PREEMPT] @ restore preempt count ldr r0, [tsk, #TI_FLAGS] @ get flags teq r8, #0 @ if preempt count != 0 movne r0, #0 @ force flags to 0 tst r0, #_TIF_NEED_RESCHED blne svc_preempt #endif ldr r4, [sp, #S_PSR] @ irqs are already disabled #ifdef CONFIG_TRACE_IRQFLAGS tst r4, #PSR_I_BIT bleq trace_hardirqs_on #endif svc_exit r4 @ return from exception UNWIND(.fnend ) ENDPROC(__irq_svc) .ltorg #ifdef CONFIG_PREEMPT svc_preempt: mov r8, lr 1: bl preempt_schedule_irq @ irq en/disable is done inside ldr r0, [tsk, #TI_FLAGS] @ get new tasks TI_FLAGS tst r0, #_TIF_NEED_RESCHED moveq pc, r8 @ go again b 1b #endif
考虑下:
preempt_count为0,本进程可被抢占,中断处理后发现有优先级高的进程,因此,thread_info中的flags字段的TIF_NEED_RESCHED位被置位,
所以调度器重新选择进程运行,进入preempt_schedule_irq函数
asmlinkage void __sched preempt_schedule_irq(void) { struct thread_info *ti = current_thread_info(); /* Catch callers which need to be fixed */ BUG_ON(ti->preempt_count || !irqs_disabled()); do { add_preempt_count(PREEMPT_ACTIVE); local_irq_enable(); schedule(); local_irq_disable(); sub_preempt_count(PREEMPT_ACTIVE); /* * Check again in case we missed a preemption opportunity * between schedule and now. */ barrier(); } while (need_resched()); }
此处会置位preempt_cout的PREEMPT_ACTIVE位,重点看下schedule函数中的如下几行
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) { if (unlikely(signal_pending_state(prev->state, prev))) prev->state = TASK_RUNNING; else deactivate_task(rq, prev, 1); switch_count = &prev->nvcsw; }
若state为running,不会出问题,但是如果state不是running就可能会出问题,试想:
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
第一种情形:prepare_to_wait()中如果只设置了task_struct的state后立即发生中断,中断返回时判断该进程可以被抢占,如果我们没有将当前进程的thread_info:preempt_count中置位PREEMPT_ACTIVE,则当前进程将被剔除运行队列,这下问题来了:我们还没有将当前进程挂到等待队列,那么该进程将不可能再次运行。因此如果在内核态发生中断,且当前进程判断可以被抢占,则先调用preempt_schedule(),在preempt_schedule中会将thread_info:preempt_count加上PREEMPT_ACTIVE,而后在调用schedule(),这样即使当前进程还没有被挂入等待队列,也不会被剔除,仍然可以正常运行。
第二种情形:prepare_to_wait()中已挂入等待队列,顺利运行schedule(),那么当前进程将会被剔除运行队列,但是当条件满足时可以被唤醒。
第三种情形:A进程在等待condition满足,B进程某时设置condition,并唤醒等待队列上的A,A此时再次运行到prepare_to_wait(),此时发生中断,如果我们没有判定是否由于内核抢占而进行schedule调用(即判定PREEMPT_ACTIVE位),则由于prev->state非零(非running),则当前进程会被剔除运行队列,而由于此后可能再也没有唤醒该进程的其它进程,该进程将永久的不到运行。
keep in mind :进程状态和其所在的队列没有关系,设置进程状态和抢占总是有可能有间隙的