Chinaunix首页 | 论坛 | 博客
  • 博客访问: 572634
  • 博文数量: 168
  • 博客积分: 62
  • 博客等级: 民兵
  • 技术积分: 442
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-30 11:45
文章分类

全部博文(168)

文章存档

2016年(2)

2015年(19)

2014年(98)

2013年(22)

2012年(6)

2011年(21)

分类: C/C++

2013-10-27 13:18:04

    在sched_features.h中定义了一些调度的特性


  1. /*
  2. * Prefer to schedule the task we woke last (assuming it failed
  3. * wakeup-preemption), since its likely going to consume data we
  4. * touched, increases cache locality.
  5. */
  6. SCHED_FEAT(NEXT_BUDDY, 0)

  7. /*
  8. * Prefer to schedule the task that ran last (when we did
  9. * wake-preempt) as that likely will touch the same data, increases
  10. * cache locality.
  11. */
  12. SCHED_FEAT(LAST_BUDDY, 1)


    从注释中可以看出 NEXT_BUDDY表示在cfs选择next sched_entity的时候会优先选择最后一个唤醒的sched_entity,而 LAST_BUDDY表示在cfs选择next sched_entity的时候会优先选择最后一个执行唤醒操作的那个sched_entity,这两种调度策略都有助于提高cpu cache的命中率,因为在切换不同任务得越频繁,会导致cpu cache因为进程改变而频繁缓存数据失效。所以尽量不切换到其他的任务会使得cache实效的更慢一些,有利于提高系统性能,可以看出默认NEXT_BUDDY是不开启的。

内核代码中next buddy last buddy对应cfs_rq 中的nextlast指针。

    看下代码这个指针的赋值,check_preempt_wakeup函数在wakeup操作的最后一部,wakeup包括了fork的最后一部也是调用了这个函数。

  1. static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
  2. {
  3.         struct task_struct *curr = rq->curr;
  4.         struct sched_entity *se = &curr->se, *pse = &p->se//pse就是需要被唤醒的sched_entity,se是当前的sched_entity
  5.         struct cfs_rq *cfs_rq = task_cfs_rq(curr);
  6.         int scale = cfs_rq->nr_running >= sched_nr_latency; //内核中有选择,当running的进程大于等于 sched_nr_latency的时候才会涉及到next buddy和last buddy,2.6.35.13中sched_nr_latency默认是3

  7.         if (unlikely(rt_prio(p->prio)))
  8.                 goto preempt;

  9.         if (unlikely(p->sched_class != &fair_sched_class))
  10.                 return;

  11.         if (unlikely(se == pse))
  12.                 return;

  13.                                                                          对于这个WF_FORK标志可以看下 https://patchwork.kernel.org/patch/47930/
  14.         if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK))
  15.                 set_next_buddy(pse);                                //如果running 大于等于sched_nr_latency,就设置next指针,表示最后被唤醒的sched_entity

  16.         /*
  17.          * We can come here with TIF_NEED_RESCHED already set from new task
  18.          * wake up path.
  19.          */
  20.         if (test_tsk_need_resched(curr))
  21.                 return;

  22.         /*
  23.          * Batch and idle tasks do not preempt (their preemption is driven by
  24.          * the tick):
  25.          */
  26.         if (unlikely(p->policy != SCHED_NORMAL))
  27.                 return;

  28.         /* Idle tasks are by definition preempted by everybody. */
  29.         if (unlikely(curr->policy == SCHED_IDLE))
  30.                 goto preempt;

  31.         if (!sched_feat(WAKEUP_PREEMPT))
  32.                 return;

  33.         update_curr(cfs_rq);
  34.         find_matching_se(&se, &pse);
  35.         BUG_ON(!pse);
  36.         if (wakeup_preempt_entity(se, pse) == 1)
  37.                 goto preempt;

  38.         return;

  39. preempt:                                            //可以抢占,标记resched
  40.         resched_task(curr);
  41.         /*
  42.          * Only set the backward buddy when the current task is still
  43.          * on the rq. This can happen when a wakeup gets interleaved
  44.          * with schedule on the ->pre_schedule() or idle_balance()
  45.          * point, either of which can * drop the rq lock.
  46.          *
  47.          * Also, during early boot the idle thread is in the fair class,
  48.          * for obvious reasons its a bad idea to schedule back to it.
  49.          */
  50.         if (unlikely(!se->on_rq || curr == rq->idle))
  51.                 return;
  52.    
  53.         if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se))//当前进程标记last buddy,设置cfs_rq的last指针
  54.                 set_last_buddy(se);
  55.    
  56. }

    从代码中可以看出,在wakeup进程的时候会去check是否当前的curr需要resched,这时会设置cfs_rqlastnext指针,last表示最后一个调用唤醒操作的进程,next表示最后一个被唤醒的进程。这两个指针会在pick next sched_entity的时候被用到,优先选择这些sched_entity。

    看下选择next sched_entity时候的代码


  1. static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)
  2. {
  3.         struct sched_entity *se = __pick_next_entity(cfs_rq);               //这里确实是选择vruntime最小的那个sched_entity
  4.         struct sched_entity *left = se;

  5.         if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) //判断left和next的vruntime的差距是否小于sysctl_sched_wakeup_granularity 
  6.                 se = cfs_rq->next;

  7.         /*
  8.          * Prefer last buddy, try to return the CPU to a preempted task.
  9.          */
  10.         if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)  //判断left和last的vruntime的差距是否小于sysctl_sched_wakeup_granularity 
  11.                 se = cfs_rq->last;

  12.         clear_buddies(cfs_rq, se);                   //用过一次任何一个next或者last,都需要清除掉这个指针,以免影响到下次pick next sched_entity

  13.         return se;
  14. }

    这里wakeup_preempt_entity函数,fork进程在cfs中的处理过程中的最后说的很清楚了,把那个图贴过来说明一下问题


    可以看到如果S3leftcurrnext或者last,可以函数wakeup_preempt_entity肯定返回1,那么就说明nextlast指针的vruntimeleft差距过大,这个时候没有必要选择这个last或者next指针,如果next或者lastS2S1,那么vruntimeleft差距并不大,并没有超过sysctl_sched_wakeup_granularity ,那么这个next或者last就可以被优先选择,而代替了left

    而清除这两个指针的时机有这么几个:

  1. sched_tick的时候,如果一个进程的运行时间超过理论时间(这个时间是根据loadcfs_rqload,平均分割sysctl_sched_latency的时间),那么如果next或者last指针指向这个正在运行的进程,需要清除这个指针,使得pick sched_entity不会因为next或者last指针再次选择到这个sched_entity
  2. 当一个sched_entity dequeue出运行队列,那么如果有next或者last指针指向这个sched_entity,那么需要删除这个next或者last指针。
  3. 刚才说的那种case,如果nextlast指针在pick的时候被使用了一次,那么这次用完了指针,需要清除相应的指针,避免使用过的nextlast指针影响到下次pick
  4. 当进程yield操作的时候,进程主动放弃了调度机会,那么如果nextlast指针指向了这个sched_entity,那么需要清除相应指针。

总结:

    cfs_rqlastnext指针,last表示最后一个执行wakeupsched_entity,next表示最后一个被wakeupsched_entity。他们在进程wakeup的时候会赋值,在picksched_entity的时候,会优先选择这些last或者next指针的sched_entity,有利于提高缓存的命中率。




阅读(690) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~