内核抢占和schedule()函数的分析

来源:互联网 发布:有没有淘宝内部优惠券 编辑:程序博客网 时间:2024/05/01 02:01

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 &current_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

0 0
原创粉丝点击