<深入浅出> schedule函数核心

 

asmlinkage 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();
 rq = cpu_rq(cpu);
 rcu_sched_qs(cpu);
 prev = rq->curr;  //为什么要根据当前cpu id来取rq,然后是rq->curr ? 为什么不直接current?
 switch_count = &prev->nivcsw; //默认为非主动调度计数(抢占)

 release_kernel_lock(prev);
need_resched_nonpreemptible:

 schedule_debug(prev);

 if (sched_feat(HRTICK))
  hrtick_clear(rq);

 spin_lock_irq(&rq->lock);
 update_rq_clock(rq);
 clear_tsk_need_resched(prev);

 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) { 


//当前进程不是出于运行态,并且不是被抢占。不能只检查抢占计数,因为可能某个进程(如网卡轮询)直接
//调用了schedule,如果不判断prev->stat就可能误deactivate_task
//进程为R状态,到达这里,有两种可能,一种是主动schedule,另外一种是被抢占。被抢占
//有两种情况,一种是时间片到点,一种是时间片没到点。
//时间片到点后,主要是置当前进程的need_resched标志,接下来在时钟中断结束后,会preempt_schedule_irq抢占调度
          

                                             
  if (unlikely(signal_pending_state(prev->state, prev)))
   prev->state = TASK_RUNNING;
  else
   deactivate_task(rq, prev, 1); //将当前进程从队列里摘除,task->on_rq设置为0, 例如:

set_current_state(TASK_INTERRUPTIBLE);schedule()
  switch_count = &prev->nvcsw;  //如果不是被抢占的,就累加主动切换次数
 }

 pre_schedule(rq, prev);

 if (unlikely(!rq->nr_running))
  idle_balance(cpu, rq);

 put_prev_task(rq, prev); //CFS,if (prev->on_rq) { __enqueue_entity();}


//CFS,如果进程之前是被抢占的(或者是主动调度schedule),则将此进程入运行队列红黑树,这里隐含当前

进程并不在红黑树运行队列里
//RT,if (p->se.on_rq) enqueue_pushable_task(rq, p); 如果是被抢占的(或者是主动schedule),则入

pushable队列。
 next = pick_next_task(rq);


// CFS,pick_next_task_fair,从红黑树里,选出vtime最小的那个进程,调用set_next_entity将其出队。
这里隐含的意思是,当前正在运行的进程,不在红黑树cpu运行队列中,但是他的on_rq参数为1;
RT,pick_next_task_rt,取出合适的进程后,dequeue_pushable_task,
从pushable队列里取出来,实际上,对于RT进程,put和pick并不操作运行队列
//对于FIFO和RR的区别,在scheduler_tick中通过curr->sched_class->task_tick进入到task_tick_rt的处理


如果是非RR的进程则直接返回,否则递减时间片,如果时间片耗完,则需要将当前进程放到运行队列的末尾,
这个时候才操作运行队列(FIFO和RR进程,是否位于同一个plist队列?),时间片到点,会重新移动当前进

程requeue_task_rt,进程会被加到队列尾,接下来set_tsk_need_resched触发调度,进程被抢占进入

schedule

 if (likely(prev != next)) {
  sched_info_switch(prev, next);
  perf_event_task_sched_out(prev, next, cpu);

  rq->nr_switches++;
  rq->curr = next;
  ++*switch_count;

  context_switch(rq, prev, next); /* unlocks the rq */
  /*
   * the context switch might have flipped the stack from under
   * us, hence refresh the local variables.
   */
  cpu = smp_processor_id();
  rq = cpu_rq(cpu);
 } else
  spin_unlock_irq(&rq->lock);

 post_schedule(rq);

 if (unlikely(reacquire_kernel_lock(current) < 0))
  goto need_resched_nonpreemptible;

 preempt_enable_no_resched();
 if (need_resched())
  goto need_resched;
}

if (!(prev->state & TASK_RUNNING_MUTEX) &&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;
}

if条件里的prev->state,代表当前进程没有处于running态,
!(preempt_count() & PREEMPT_ACTIVE表示当前进程不是被抢占,
就把当前进程从队列里取出来 deactivate_task
这段代码的意思是,如果有类似这么一些语句:
set_current_state(TASK_INTERRUPTIBLE);
schedule();
那么将会把当前进程从运行队列里删除

接下来是
put_prev_task(rq, prev);

如果是cfs,则为put_prev_task_fair, 这个函数主要就是更新当前进程的虚拟运行时间等,如果此进程之前

就在运行队列上,就重新把它插入队列,这种情况什么时候发生?时间片到期的进程,也需要更新
if (prev->on_rq) {
   __enqueue_entity(cfs_rq, prev);
}

 

proc的调度相关字段解释 2.6.32内核

 

CONFIG_SCHED_DEBUG (需要配置CONFIG_SCHEDSTAT才能显示更多信息)

 /proc/pid/sched  显示信息解释如下:

ata/2 (111, #threads: 1)
---------------------------------------------------------
se.exec_start                      :          1159.482823    //此进程最近被调度到的开始执行时刻 ns
se.vruntime                        :            -1.044234    //虚拟运行时间,cfs调度用
se.sum_exec_runtime                :             0.000000    //累计运行时间
se.avg_overlap                     :             0.000000   
se.avg_wakeup                      :             3.617187    //当前进程最近一次调用try_to_wake_up为止的单次执行时间
se.avg_running                     :             0.000000   //平均单次执行时间
//以下开始需要同时配置CONFIG_SCHEDSTATS才能显示
se.wait_start                      :             0.000000  //最近一次当前进程被入队的时刻 cfs使用
se.sleep_start                     :          1159.482823 //此进程最近一次被从队列里取出,并被置S状态的时刻
se.block_start                     :             0.000000 //此进程最近一次被从队列里取出,并被置D状态的时刻
se.sleep_max                       :             0.000000 //最长处于S状态时间
se.block_max                       :             0.482651 //最长处于D状态时间
se.exec_max                        :             0.000000 //最长单次执行时间
se.slice_max                       :             0.000000 //曾经获得时间片的最长时间 (当cpu load过高时开始计算)
se.wait_max                        :             0.000000 //最长在就绪队列里的等待时间
se.wait_sum                        :             0.000000 //累计在就绪队列里的等待时间
se.wait_count                      :                    2 //累计等待次数 (被出队的次数) cfs使用,当被选中做下一个待运行进程时(set_next_entity),等待结束
se.iowait_sum                      :             0.000000 //io等待时间
se.iowait_count                    :                    0 //io等待次数  io_schedule调用次数
sched_info.bkl_count               :                    0 //此进程大内核锁调用次数
se.nr_migrations                   :                    2 //需要迁移当前进程到其他cpu时累加此字段
se.nr_migrations_cold              :                    0  //2.6.32 代码里赋值都是0
se.nr_failed_migrations_affine     :                    0   //进程设置了cpu亲和,进程迁移时检查失败的次数
se.nr_failed_migrations_running    :                    0    //当前进程出入R,不运行迁移的次数
se.nr_failed_migrations_hot        :                    0   //当前进程因为是cache hot导致迁移失败的次数
se.nr_forced_migrations            :                    0    //在当前进程cache hot下,由于负载均衡尝试多次失败,强行进行迁移的次数
se.nr_forced2_migrations           :                    1     //在当前进程是cache hot下,设置此进程进行迁移的次数
se.nr_wakeups                      :                    1  // 被唤醒的累计次数
se.nr_wakeups_sync                 :                    0  // 同步唤醒次数,即a唤醒b,a立刻睡眠,b被唤醒的次数 /* waker goes to sleep after wakup */
se.nr_wakeups_migrate              :                    0  //被唤醒得到调度的当前cpu,不是之前睡眠的cpu的次数
se.nr_wakeups_local                :                    0 //被本地唤醒的次数
se.nr_wakeups_remote               :                    1 //其他唤醒累计次数
se.nr_wakeups_affine               :                    0 //考虑了任务的cache亲和性的唤醒次数
se.nr_wakeups_affine_attempts      :                    0 //尝试进行考虑了任务的cache亲和性的唤醒次数
se.nr_wakeups_passive              :                    0 //2.6.32 代码里赋值都是0
se.nr_wakeups_idle                 :                    0 //2.6.32 代码里赋值都是0
avg_atom                           :             0.000000 //本进程平均切换耗时
avg_per_cpu                        :             0.000000 //如果本进程曾经被推或者拉到其他cpu上,则计算每个cpu上的平均耗时
nr_switches                        :                    2 //主动切换和被动切换的累计次数
nr_voluntary_switches              :                    2  //主动切换次数
nr_involuntary_switches            :                    0  //被动切换次数
se.load.weight                     :                 1024  //权重,和负载均衡有关
policy                             :                    0  //进程属性,0为非实时
                                                       #define SCHED_NORMAL  0
                                                       #define SCHED_FIFO  1
                                                       #define SCHED_RR  2
                                                       #define SCHED_BATCH  3
                                                       #define SCHED_IDLE  5
prio                               :                  120  //优先级
clock-delta                        :                  165  //测试用

 


/proc/sched_debug

此为cpu15运行队列的调度信息:

cpu#15, 1595.968 MHz
  .nr_running                    : 0                           //运行队列里处于就绪态进程的数目
  .load                          : 0                          //当前cpu负载
  .nr_switches                   : 120820818                  //切换累计次数
  .nr_load_updates               : 674238115                  //cpu权重更新的次数,和负载均衡有关
  .nr_uninterruptible            : -30                       
  .next_balance                  : 8012.054504               //下次执行定时负载均衡的时间
  .curr->pid                     : 0                         //当前运行进程
  .clock                         : 3717386659.081997          //当前cpu运行队列时刻
  .cpu_load[0]                   : 0                          //以下为cpu历史负载数组
  .cpu_load[1]                   : 0
  .cpu_load[2]                   : 0
  .cpu_load[3]                   : 0
  .cpu_load[4]                   : 0

   //以下开始需要同时配置CONFIG_SCHEDSTATS才记录
  .yld_count                     : 8236                      //调yield的次数
  .sched_switch                  : 0                       
  .sched_count                   : 122061248                //调用schedule的次数,>=nr_switches
  .sched_goidle                  : 30689623                  //切换到idle进程的次数
  .avg_idle                      : 1000000                   //cpu处于idle的平均时间
  .ttwu_count                    : 46238267                 //此cpu try_to_wake_up唤醒进程的次数
  .ttwu_local                    : 30988951                 //本地唤醒的次数,即进程睡眠前所在cpu为当前cpu
  .bkl_count                     : 2090                     //此cpu上大内核锁调用次数

cfs_rq[15]:/
  .exec_clock                    : 545099081.498339           //非实时线程得到调度的累计时间
  .MIN_vruntime                  : 0.000001                   //当前cfs 运行队列里虚拟时间最小的那个进程的vtime
  .min_vruntime                  : 770437019.732761           //当前cfs 运行队列虚拟时间下限,当处于rb树最左边进程运行虚拟时间,减去此值大于一个阈值后抢占当前进程
  .max_vruntime                  : 0.000001                   // 2.6.32里没有找到这个字段
  .spread                        : 0.000000                  
  .spread0                       : 407973445.279093            //cpu0上的min_vruntime与当前cpu的min_vruntime差值
  .nr_running                    : 0                          //当前cpu非实时进程在就绪队列中的个数
  .load                          : 0                          //非实时进程负载
  .nr_spread_over                : 6811                        //当前cpu中,长时间非实时进程由一个进程占有的次数
                                                               s64 d = se->vruntime - cfs_rq->min_vruntime;
                                                               if (d > 3*sysctl_sched_latency)
                                                         schedstat_inc(cfs_rq, nr_spread_over);

  .shares                        : 0                          
 
rt_rq[15]:/
  .rt_nr_running                 : 0                                 //处于就绪队列的实时进程个数
  .rt_throttled                  : 0                                  //是否限制实时进程的带宽运行时间,实时进程是否需要让出cpu
  .rt_time                       : 0.000000                          //做实时进程带宽分析用
  .rt_runtime                    : 950.000000                          //做实时进程带宽分析用

 

 

另外,还有一个:
/proc/schedstat
字段详细解释文档在内核目录的:
documentation/scheduler/sched-stats.txt
和负载均衡关系大些

你可能感兴趣的:(<深入浅出> schedule函数核心)