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

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

标签: schedulepreempt-diTIF-NEED-Rthread-infPREEMPT-AC
  523人阅读  评论(0)  收藏  举报
  分类:
校招准备(12) 

1.线程描述符:

struct thread_info {
    struct task_struct  *task;      
    struct exec_domain  *exec_domain;   
    __u32           flags;      
    __u32           status;     
    __u32           cpu;        
    int         saved_preempt_count;
    ...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

重要字段解释: 
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());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

此时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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

4.PREEMPT_ACTIVE位的作用: 
考虑这样的代码:

        set_current_state(TASK_UNINTERRUPTBLE);
        add_wait_queue(&wait_queue_head, &wait_queue);   
        if(condition) 
            break;                                                        
        schedule();                     
        finish_wait(&wq, &__wait);  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果在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); 
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

从上述两个函数的源代码来看, 抢占计数和调度标志都是对当前进程的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

你可能感兴趣的:(C/C++,linux/android)