首页 > 代码库 > Linux内核架构读书笔记 - 2.5.4 核心调度器

Linux内核架构读书笔记 - 2.5.4 核心调度器

什么是核心调度器?

  参考前面的博文http://www.cnblogs.com/songbingyu/p/3696414.html

 

  1 周期性调度器

    作用:

    • 管理内核中与整个系统和各个进程的调度相关的统计量
    • 负责当前调度类的周期性调度方法

    kernel/sched.c

 1 /*
 2  * This function gets called by the timer code, with HZ frequency.
 3  * We call it with interrupts disabled.
 4  *
 5  * It also gets called by the fork code, when changing the parent‘s
 6  * timeslices.
 7  */
 8 void scheduler_tick(void)
 9 {
10     int cpu = smp_processor_id();
11     struct rq *rq = cpu_rq(cpu);
12     struct task_struct *curr = rq->curr;
13     u64 next_tick = rq->tick_timestamp + TICK_NSEC;
14 
15     spin_lock(&rq->lock);
16     __update_rq_clock(rq);
17     /*
18      * Let rq->clock advance by at least TICK_NSEC:
19      */
20     if (unlikely(rq->clock < next_tick))
21         rq->clock = next_tick;
22     rq->tick_timestamp = rq->clock;
23     update_cpu_load(rq);
24         ....

    _update_rq_clock 处理就绪队列时钟更新,本质上增加struct rq 的时间戳

    update_cpu_load 负责更新就绪队列的cpu_load[] 数组,本质上相当与将数组中先前存储的负载值向后移动一个位置,将当前就绪队列的负载值计入数组的第一个位置、、、

    kernel/sched.c

1     if (curr != rq->idle) /* FIXME: needed? */
2         curr->sched_class->task_tick(rq, curr);

    task_tick 实现取决与底层的调度器类

    注意: 如果进程应该被重新调度,调度器类会在task_struct中设置TIF_NEED_RESCHED标志,已表示该请求,内核会载接下来的适当时机完成该请求

  2 主调度器

   在内核的许多地方,如果要将CPU 分配给当前活动进程不同的另一个进程,都会直接调用主调度器函数(schedule)

  kernel/sched.c

 1 /*
 2  * schedule() is the main scheduler function.
 3  */
 4 asmlinkage void __sched schedule(void)
 5 {
 6     struct task_struct *prev, *next;
 7     long *switch_count;
 8     struct rq *rq;
 9     int cpu;
10 
11 need_resched:
12     preempt_disable();
13     cpu = smp_processor_id();
14     rq = cpu_rq(cpu);
15     rcu_qsctr_inc(cpu);
16     prev = rq->curr;
17     switch_count = &prev->nivcsw;

  

       类似与周期性调度器,内核也利用该时机来更新就绪队列的时钟,并清楚当前运行进程中的重调度标志TIF_NEED_RESCHED

  kernel/sched.c

1 __update_rq_clock(rq);
2     spin_lock(&rq->lock);
3     clear_tsk_need_resched(prev);

 

  如果当前进程原来处于可中断睡眠状态但现在接受到信号,,那吗它必须再次提升为运行进程,负责相应调度器类的方法使进程停止活动(deactivate_task实质上最终调用sched_class->dequeue_task)

1 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
2         if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
3                 unlikely(signal_pending(prev)))) {
4             prev->state = TASK_RUNNING;
5         } else {
6             deactivate_task(rq, prev, 1);
7         }
8         switch_count = &prev->nvcsw;
9     }

  

  put_prev_task同志调度器类当前进程将要被另一个进程代替,不是从就绪队列移除,而是提供一个时机进行一些统计工作

  pick_next_task 选择下一个进程

1     prev->sched_class->put_prev_task(rq, prev);
2     next = pick_next_task(rq, prev);

  不见得必然选择一个进程,有可能其他进程都在睡眠

 

  kernel/sched.c

1 if (likely(prev != next)) {
2         rq->nr_switches++;
3         rq->curr = next;
4         ++*switch_count;
5 
6         context_switch(rq, prev, next); /* unlocks the rq */

  content_switch 一个接口,供访问特定与体系结构的方法

  

  下面代码检测当前的重调度位是否设置,并跳转

1     if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
2         goto need_resched;

  注意:上述代码可能载两个上下文中执行  无上下文切换,schedule 末尾直接执行,如果执行上下文切换,在新的上下文执行,所以需要current 和 test_thread_flag 找到当前进程

  3 与fork 交互

  使用fork或其变体建立新进程时候,调度器有机会用sched_fork 挂钩到该进程

  再单处理器上,执行三个操作,初始化新进程与调度相关字段,建立数据结构 确定进程的动态优先级

  kernel/sched.c/sched_fork

    /*
     * Make sure we do not leak PI boosting priority to the child:
     */
    p->prio = current->normal_prio;
    if (!rt_prio(p->prio))
        p->sched_class = &fair_sched_class;

   在使用wake_up_new_task 唤醒新的进程,是调度器与创建逻辑交互的第二时机,内核会调用task_new 函数,将进程加入到相应的就绪队列

  

  4 上下文切换

  content_switch 

  kernel/sched.c

 1 /*
 2  * context_switch - switch to the new MM and the new
 3  * thread‘s register state.
 4  */
 5 static inline void
 6 context_switch(struct rq *rq, struct task_struct *prev,
 7            struct task_struct *next)
 8 {
 9     struct mm_struct *mm, *oldmm;
10 
11     prepare_task_switch(rq, prev, next);
12     mm = next->mm;
13     oldmm = prev->active_mm;

 

  上下文切换调用两个特定与体系结构的的函数

    1 switch_mm

    2 switch_to

  kernel/sched.c

1     if (unlikely(!mm)) {
2         next->active_mm = oldmm;
3         atomic_inc(&oldmm->mm_count);
4         enter_lazy_tlb(oldmm, next);
5     } else
6         switch_mm(oldmm, mm, next);

 

  entry_lazy_tlb 通知底层结构体系不许要切换虚拟地址空间的用户部分

  

  如果前一进程是内核进程(prev-》mm 为 null ) ,其 active_mm 必须重置为null

  kernel/sched.c

1     if (unlikely(!prev->mm)) {
2         prev->active_mm = NULL;
3         rq->prev_mm = oldmm;
4     }

  

  最后用switch_to 完成进程切换

  switch_to 之后的代码只有当前进程下一次被选择运行是才会运行

  finish_task_switch 完成一些清理工作

 1     /* Here we just switch the register state and the stack. */
 2     switch_to(prev, next, prev);
 3 
 4     barrier();
 5     /*
 6      * this_rq must be evaluated again because prev may have moved
 7      * CPUs since it called schedule(), thus the ‘rq‘ on its stack
 8      * frame will be invalid.
 9      */
10     finish_task_switch(this_rq(), prev);

  

  •   switch_to 的复杂之处

  finish_task_switch 特点,调度器可能选择了一个新的进程,但是清理则是针对此前的活动进程

  eg

  

  其实上面只是表达一个意思

  

  在新的进程被再次执行时候,获得上一次运行的是那一个进程

 

  还没想明白。。。mark

  •   惰性FPU 模式

  浮点寄存器,除非有程序使用,负责不会保存,此外除非有应用程序需要,负责这些寄存器不会恢复