1.线程描述符:
struct thread_info {
struct task_struct *task;
struct exec_domain *exec_domain;
__u32 flags;
__u32 status;
__u32 cpu;
int saved_preempt_count;
...
};
重要字段解释:
flags: 该字段中有一个位是用于设置内核是否应该被重新调度的标志–need_resched,set_tsk_need_resched()和clear_tsk_need_resched()函数实际上就是对该字段的某一位进行操作(第31位 ?)
saved_preempt_count: 内核此时是否可以进行抢占的标志,为0表示当前进程不持有锁,可以被抢占.大于0则表示此时持有锁,不能被抢占
preempt_counter 字段是32位的, 除了抢占计数器之外还包括其他标志位, 只要 preempt_counter 整体不为0, 就不能进行内核抢占, 这个设计一下子简化了对众多不能抢占的情况的检测:
Bit 0-7: 抢占计数器, 表示显式禁用内核抢占的次数
Bit 8-15: 软中断计数器, 记录可延迟函数被禁用的次数
Bit 16-27: 硬中断计数器, 表示中断处理程序的嵌套数, irq_enter()递增它, irq_exit()递减它
Bit 28: PREEMPT_ACTIVE 标志, 内核抢占的标志
2.schedule()函数被调度的时机:
1.内核从系统调用或中断处理程序返回时:
1.内核要返回到用户空间时:
如果当前进程的thread_info结构中的need_resched标志被设置,那么就可以调用schedule(),因为内核返回用
户空间时,它知道自己是安全的,因为既然它可以继续去执行当前进程,那么它当然可以再去选择一个新的进程去执行
2.内核要返回内核空间时:
如果当前进程的thread_info结构体中的need_resched标志被设置而且saved_preempt_count为0时, 通过调用preempt_schedule_irq()函数来间接调用schedule()函数:
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); //设置thread_info->saved_preempt_count变量的第28位, 即PREEMPT_ACTIVE标志
local_irq_enable();
schedule(); //调度
local_irq_disable();
sub_preempt_count(PREEMPT_ACTIVE);
barrier();
} while (need_resched());
}
此时thread_info->saved_preempt_count中的PREEMPT_COUNT位被设置了, 表示schedule()函数的调用不是进程自己主动调用的,而是被抢占了
3.schedule函数 部分代码:
static void __sched __schedule(void)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
struct rq *rq;
int cpu;
need_resched:
preempt_disable(); //禁止抢占
cpu = smp_processor_id(); //获得cpu号
rq = cpu_rq(cpu); //获得cpu上的运行队列
rcu_note_context_switch(cpu);
prev = rq->curr; //获得运行队列上的当前运行进程
schedule_debug(prev);
if (sched_feat(HRTICK))
hrtick_clear(rq);
smp_mb__before_spinlock();
raw_spin_lock_irq(&rq->lock); //关闭本地中断, 并获取本地运行队列的自旋锁
switch_count = &prev->nivcsw;
//1.当进程主动调用schedule函数时, prev->state不为TASK_RUNNING(0),且preempt_count和PREEMPT_ACTIVE都为0, 那么if语句条件满足
//2.当前运行进程是被抢占的, schedule函数是通过preempt_schedule_irq函数间接调用的, 则PREEMPT_ACTIVE会被设置, 则if语句条件不满足. 则被抢占的进程的on_rq还是为1, 会被后面的put_prev_task函数加入红黑树的, 不会失去重新调度的机会
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
//当前进程的状态为TASK_INTERRUPTIBLE而且该进程有未决的信号未处理, 则if语句条件满足
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
deactivate_task(rq, prev, DEQUEUE_SLEEP); //好像此处没做什么事
prev->on_rq = 0; //on_rq为0, 表示该进程此时已经不处于运行状态了, 所以后面的put_prev_task函数不会将其加入红黑树
}
switch_count = &prev->nvcsw;
}
pre_schedule(rq, prev);
if (unlikely(!rq->nr_running)) //如果当前运行队列为空, 则从其他cpu上的运行队列拿来一些进程
idle_balance(cpu, rq);
put_prev_task(rq, prev); //将当前进程加入红黑树中
next = pick_next_task(rq); //挑选出下一个要运行的进程, 并将其设置为当前进程
clear_tsk_need_resched(prev); //清除prev进程的重新调度标志
clear_preempt_need_resched(); //清除需要抢占标志
rq->skip_clock_update = 0;
if (likely(prev != next)) { //选出的进程不和之前的进程一样, 则进行上下文切换
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next); // 上下文切换
cpu = smp_processor_id();
rq = cpu_rq(cpu);
} else
raw_spin_unlock_irq(&rq->lock);
post_schedule(rq);
sched_preempt_enable_no_resched(); //打开内核抢占
//如果新选出的进程也有重新调度标志, 则继续调度
if (need_resched())
goto need_resched;
}
4.PREEMPT_ACTIVE位的作用:
考虑这样的代码:
set_current_state(TASK_UNINTERRUPTBLE);
add_wait_queue(&wait_queue_head, &wait_queue);
if(condition)
break;
schedule();
finish_wait(&wq, &__wait);
如果在set_current_state()函数执行之后,产生了中断,这时如果没有设置PREEMPT_ACTIVE位的话,将会是这样一个执行情况:
if (prev->state && !preempt_count()) { //preempt_count直接返回的是preempt_count的值, 在设置了PREEMPT_ACTIVE位的情况下, preempt_count()的结果也不为0
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
deactivate_task(rq, prev, DEQUEUE_SLEEP);
}
prev->state的状态是TASK_UNINTERUPTIBLE, !preempt_count()的结果也为1, 此时第一个if语句条件满足. 此时进程的状态为TASK_UNINTERRUPTIBLE, 第二个if语句条件不满足, 执行else语句, 此时该进程不会加入红黑树. 而在中断之前, 进程只是把自己的状态设为TASK_UNINTERRUPTIBLE, 还没有将自己加入等待队列. 这样进程不在运行队列中, 同时当等待队列条件满足时, 也不会被该等待队列给唤醒, 因为进程还没有加入到等待队列中, 所以此时进程就再也没有机会运行了
如果设置了PREEMPT_ACTIVE位的情况下, 上述代码将会是这样的执行情况.
内核通过调用preempt_schedule_irq()函数将saved_preempt_count的PREEMPT_ACTIVE位设置为1,此时上述的第一个if语句的条件就不会满足,因为!(preempt_count() & PREEMPT_ACTIVE)的值为0. 所以进程会被加入红黑树, 这样进程就还有被重新调度的机会
总的来说,PREEMPT_ACTIVE位的使用, 就是为了表示此时schedule()函数的调用是否是被进程自己主动调用的还是被内核抢占的. 如果是进程主动调用schedule()函数的, 那么当当前进程的状态不是TASK_RUNNING时, 内核不会把当前进程加入红黑树中. 如果是内核抢占的情况,则会把当前进程加入红黑树.
5.几个与抢占有关的函数:
preempt_disable(): 抢占计数加1, 禁止内核抢占
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define preempt_count_inc() preempt_count_add(1)
#define preempt_count_add(val) __preempt_count_add(val)
static __always_inline void __preempt_count_add(int val)
{
*preempt_count_ptr() += val;
}
static __always_inline int *preempt_count_ptr(void)
{
return ¤t_thread_info()->preempt_count;
}
preempt_enable(): 抢占计数减1, 并判断是否可以重新调度
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \ //将抢占计数减一并检查是否可以重新调度
__preempt_schedule(); \ //如果可以则重新调度, 即抢占计数为0且TIF_NEED_RESCHED标志也已经被设置
} while (0)
#define preempt_count_dec_and_test() __preempt_count_dec_and_test()
static __always_inline bool __preempt_count_dec_and_test(void)
{
return !--*preempt_count_ptr() && tif_need_resched();
}
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
#define test_thread_flag(flag) \
test_ti_thread_flag(current_thread_info(), flag)
static inline int test_ti_thread_flag(struct thread_info *ti, int flag)
{
return test_bit(flag, (unsigned long *)&ti->flags); //测试thread_inof->flags的TIF_NEED_RESCHED位
}
#define __preempt_schedule() preempt_schedule()
asmlinkage void __sched notrace preempt_schedule(void)
{
if (likely(!preemptible()))
return;
do {
__preempt_count_add(PREEMPT_ACTIVE); //设置抢占标志位
__schedule(); //调度
__preempt_count_sub(PREEMPT_ACTIVE);
barrier();
} while (need_resched());
}
从上述两个函数的源代码来看, 抢占计数和调度标志都是对当前进程的thread_info->preempt_count和thread_info->flags变量进行操作的
本文引述自:
http://www.cnblogs.com/openix/archive/2013/03/09/2952041.html
http://blog.chinaunix.net/uid-12461657-id-3353217.html
http://edsionte.com/techblog/archives/3819