Chinaunix首页 | 论坛 | 博客
  • 博客访问: 569335
  • 博文数量: 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:11

原文地址:fork进程在CFS的处理过程 作者:djjsindy

    在进程fork子进程的过程中,可以想到的内核中的大致过程就是fork出的子进程,根据load选择出一个的rq,然后把task的状态置为running,把子进程插入到这个rq中,同时更新vruntime,最后根据vruntime确定是否要check preempt(标记TIF_NEED_RESCHED),尝试调度这个新fork出的进程。

    在fork进程的copy_process函数中会调用sched_fork函数,会设置taskstate设置为TASK_RUNNING,调用cfstask_fork_fair函数,这个函数的做的事情:

  1. 因为这里需要用到父进程的vruntime,子进程的vruntime以父进程为基础,所以这里需要更新currvruntime到最新,调用update_curr函数。
  2. 初始化子进程的vruntime为父进程vruntime
  3. 根据当前子进程的loadcfs_rq中的从load平分sysctl_sched_latency,计算子进程的vruntime


    sysctl_sched_latency就是一个sched_entity的调度的最大延时,在时钟中断函数tick中,会判断这轮运行的时间(计算这个依赖于sum_exec_runtimeprev_sum_exec_runtime之差),如果sched_entity运行超过了时间段,那么一定会标记resched,这个细节后面blog会讲到。

代码:

    

  1. static void task_fork_fair(struct task_struct *p){
  2.         struct cfs_rq *cfs_rq = task_cfs_rq(current);
  3.         struct sched_entity *se = &p->se, *curr = cfs_rq->curr;
  4.         int this_cpu = smp_processor_id();
  5.         struct rq *rq = this_rq(); //获得当前cpu 的rq
  6.         unsigned long flags;

  7.         raw_spin_lock_irqsave(&rq->lock, flags);

  8.         if (unlikely(task_cpu(p) != this_cpu)) {
  9.             rcu_read_lock();
  10.             __set_task_cpu(p, this_cpu);
  11.             rcu_read_unlock();
  12.         }

  13.         update_curr(cfs_rq); //根据时间片的deltatime和load更新curr的vruntime和cfs的min_vruntime

  14.         if (curr)
  15.             se->vruntime = curr->vruntime; //初始化子进程的vruntime
  16.         place_entity(cfs_rq, se, 1); //根据sched_entity的load和cfs_rq中的load平分sysctl_sched_latency最大延时
  17.                                                                //如果设置了 sysctl_sched_child_runs_first,那么需要让子进程的vruntime最小
  18.         if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) {
  19.             swap(curr->vruntime, se->vruntime);
  20.             resched_task(rq->curr);
  21.         }

  22.         se->vruntime -= cfs_rq->min_vruntime; //这里需要说明下
  23.         raw_spin_unlock_irqrestore(&rq->lock, flags);
  24.     }


    这里为何 se->vruntime要减去 cfs_rq->min_vruntime,查了一下,写到

To prevent boost or penalty in the new cfs_rq caused by delta min_vruntime between the two cfs_rqs, we skip vruntime adjustment.

    可以看到内核中的注释说为了避免sched_entity在换cfs_rq的时候,导致了继承了原来cfs_rqmin_vruntime,因为不同的cfs_rqmin_vruntime不相同,可以让在计算vruntime的时候先减去当前cfs_rqvruntime,在enqueue到其他cfs_rq的时候在加上那个cfs_rqmin_vruntime

    可以看到这个减去cfs_rqmin_vruntime的时机:

  1. task_fork的时候,因为fork的最后会去挑选loadcfs_rqenqueue子进程task,所以在执行task_fork_fair的时候当前cfs_rq不一定是最后task enqueue的那个queue
  2. dequeue_entity的时候,表示当前sched_entityrunqueue,有可能移动到其他的runqueue中,所以这个时候需要减去min_vruntime

    在enqueue_entity的时候需要把当前sched_entityvruntime加上当前cfs_rqmin_vruntime,do_fork的最后会把当前fork出的子线程放到load最低的cfs_rq中,然后check preempt

主要逻辑:    

  1.     cpu = select_task_rq(rq, p, SD_BALANCE_FORK, 0); //按照load,挑选出load最低的cpu
  2.     set_task_cpu(p, cpu);
  3.     p->state = TASK_RUNNING;                        //更新状态
  4.     task_rq_unlock(rq, &flags);
  5.     rq = task_rq_lock(p, &flags);
  6.     activate_task(rq, p, 0);                         //sched_entity进入相应的cfs_rq运行队列
  7.     trace_sched_wakeup_new(p, 1);
  8.     check_preempt_curr(rq, p, WF_FORK);              //检测当前正在执行的进程是否可以被抢占


active_task的逻辑就是把task enqueue进相关的运行队列

  1. static void activate_task(struct rq *rq, struct task_struct *p, int flags){
  2.         if (task_contributes_to_load(p))
  3.             rq->nr_uninterruptible--;
  4.         enqueue_task(rq, p, flags);
  5.         inc_nr_running(rq);
  6.     }

    check_preempt_curr函数主要是看当前执行的进程是否可以被抢占(标记TIF_NEED_RESCHED),来运行新的子进程,check_preempt_curr函数判断是否可以resched,主要逻辑在wakeup_preempt_entity函数中。


  1. static int wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se){
  2.     s64 gran, vdiff = curr->vruntime - se->vruntime;
  3.     if (vdiff <= 0)
  4.         return -1;
  5.     gran = wakeup_gran(curr, se);
  6.     if (vdiff > gran)
  7.         return 1;
  8.     return 0;
  9. }


 
代码中的注释很好的说明了wakeup_gran函数的作用
                                             

    这里利用到cfs中的一个调度参数sysctl_sched_wakeup_granularity,这个参数表示wakeup时最小粒度,决定当前sched_entity是否被抢占的时候,如果有sched_entityvruntimecurrvruntime大至少sysctl_sched_wakeup_granularity,那么这个时候需要标记重新调度(TIF_NEED_RESCHED),如果差距不足sysctl_sched_wakeup_granularity就不需要抢占,如果没有这个参数,只比较vruntime,会导致抢占的太频繁,这样系统的进程切换也会很频繁,性能也不会很高。如果不抢占会导致,有的sched_entity被调度到需要一定的延时,所以定一个临界值(sysctl_sched_wakeup_granularity)是很有必要的。


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