进程管理之schedule --> pick_next_task()

来源:互联网 发布:任意显示号码软件 编辑:程序博客网 时间:2024/06/07 23:06
原网址:http://blog.csdn.net/sunnybeike/article/details/6918586

[cpp] view plaincopy
  1. /* 
  2.  * Pick up the highest-prio task: 
  3.  */  
  4. static inline struct task_struct *  
  5. pick_next_task(struct rq *rq)  
  6. {  
  7.     const struct sched_class *class;  
  8.     struct task_struct *p;  
  9.   
  10.     /* 
  11.      * Optimization: we know that if all tasks are in 
  12.      * the fair class we can call that function directly: 
  13.      */  
  14.     if (likely(rq->nr_running == rq->cfs.nr_running)) { //如果nr_running==cfs.nr_running,则说明当前rq队列中是没有rt任务的,  
  15.                                                             //rt任务不在cfs队列中,它的优先级的设置和cfs不一样。  
  16.         p = fair_sched_class.pick_next_task(rq); //在cfs队列中挑选即将被切换进去的进程,核心函数,我们下文会详细讲解。  
  17.         if (likely(p))  
  18.             return p;  
  19.     }  
  20.   
  21.     for_each_class(class) {   
  22.         p = class->pick_next_task(rq);  
  23.         if (p)  
  24.             return p;  
  25.     }  
  26.   
  27.     BUG(); /* the idle class will always have a runnable task */  
  28. }  

当CPU中没有rt任务时,那么从cfs队列中挑选合适的进程以待切换进CPU,因为是cfs调度类,所以最终调度的函数是pick_next_task_fair:

[cpp] view plaincopy
  1. static struct task_struct *pick_next_task_fair(struct rq *rq)  
  2. {  
  3.     struct task_struct *p;  
  4.     struct cfs_rq *cfs_rq = &rq->cfs;  
  5.     struct sched_entity *se;  
  6.   
  7.     if (!cfs_rq->nr_running)   
  8.         return NULL;  
  9.   
  10.     do {  
  11.         se = pick_next_entity(cfs_rq); //挑选下一个调度实体,详解见下文  
  12.         set_next_entity(cfs_rq, se); //  
  13.         cfs_rq = group_cfs_rq(se);   
  14.     } while (cfs_rq);  <span style="color:#FF0000;">//如果被调度的进程仍属于当前组,那么选取下一个可能被调度的任务,以保证组间调度的公平性???</span>  
  15.   
  16.     p = task_of(se);  
  17.     hrtick_start_fair(rq, p);  
  18.   
  19.     return p;  
  20. }  
来看一下pick_next_entity:

[cpp] view plaincopy
  1. /* 
  2.  * Pick the next process, keeping these things in mind, in this order: 
  3.  * 1) keep things fair between processes/task groups                    1. 首先要确保任务组之间的公平,这也是设置组的原因之一; 
  4.  * 2) pick the "next" process, since someone really wants that to run   2. 其次,挑选下一个合适的(优先级比较高的)进程,因为它确实需要马上运行; 
  5.  * 3) pick the "last" process, for cache locality                       3. 如果没有找到条件2中的进程,那么为了保持良好的局部性,则选中上一次执行的进程; 
  6.  * 4) do not run the "skip" process, if something else is available     4. 只要有任务存在,就不要让CPU空转,也就是让CPU运行idle进程。 
  7.  */  
  8. static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)  
  9. {  
  10.     struct sched_entity *se = __pick_first_entity(cfs_rq); //摘取红黑树最左边的进程  
  11.     struct sched_entity *left = se;  
  12.   
  13.     /* 
  14.      * Avoid running the skip buddy, if running something else can 
  15.      * be done without getting too unfair. 
  16.      */  
  17.     if (cfs_rq->skip == se) { //如果是被取到的进程自动放弃运行权利  
  18.         /* 
  19.          *cfs_rq队列中的*skip指向暂时不需要被调度执行的进程,这样的进程一般通过sched_yield() 
  20.          *(在CFS中是通过yield_task_fair)放弃执行权的,同时在sched_yield设置skip之前,总是将上一个被设置为skip的进程清除掉,以防止放弃 
  21.          *运行权利的进程永远得不到调度。 
  22.          */  
  23.                 struct sched_entity *second = __pick_next_entity(se); //摘取红黑树上第二左的进程节点  
  24.         if (second && wakeup_preempt_entity(second, left) < 1) //left应该抢占second么?这个函数的具体实现会在下文详述。  
  25.             se = second; //left不会抢占second,则se = second.  
  26.     }  
  27.   
  28.     /* 
  29.      * Prefer last buddy, try to return the CPU to a preempted task. 
  30.      */  
  31.     if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)  
  32.         //如果上一次执行的进程尚在cfs_rq队列中,并且left不能抢占它,这里我们应该能够想到为什么内核线程的active_mm要借用  
  33.         //上一个进程的active_mm。这样的话上一个activ_mm就不用从tlb中清洗掉,而下一次调度的时候有可能重新调度到该进程,增加了tlb的命中率。  
  34.                 se = cfs_rq->last;   
  35.   
  36.     /* 
  37.      * Someone really wants this to run. If it's not unfair, run it. 
  38.      */  
  39.     if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)//如果cfs_rq中指向下一个即将被调度进来的进程的指针不为空,并且left不能抢占它   
  40.         se = cfs_rq->next;  
  41.   
  42.     clear_buddies(cfs_rq, se); //清空last, next, skip指针  
  43.   
  44.     return se;  
  45. }  

我们首先要关注的是 pick_next_entity中调用的第一个函数__pick_first_entity:

[cpp] view plaincopy
  1. static struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)  
  2. {  
  3.     struct rb_node *left = cfs_rq->rb_leftmost;  //挑选cfs队列红黑树中最左的元素  
  4.   
  5.     if (!left)  
  6.         return NULL;  
  7.   
  8.     return rb_entry(left, struct sched_entity, run_node);  
  9. }  

                                   现在,有必要对CFS调度机制有一个深入的了解了。推荐参考网站:http://blog.csdn.net/peimichael/article/details/5218335或者《独辟蹊径》一书。

接着,来看一下在pick_next_entity中被反复用到的一个函数:

[cpp] view plaincopy
  1. /* 
  2.  * Should 'se' preempt 'curr'. 
  3.  * 
  4.  *             |s1 
  5.  *        |s2 
  6.  *   |s3 
  7.  *         g 
  8.  *      |<--->|c 
  9.  * 
  10.  *  w(c, s1) = -1 
  11.  *  w(c, s2) =  0 
  12.  *  w(c, s3) =  1 
  13.  * 
  14.  */  
  15. static int  
  16. wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)  
  17. {  
  18.     s64 gran, vdiff = curr->vruntime - se->vruntime; //curr和se之间虚拟时钟的差别  
  19.   
  20.     if (vdiff <= 0)     //如果curr的虚拟时钟小于se的虚拟时钟  
  21.         return -1;  //则se不能抢占curr  
  22.   
  23.     gran = wakeup_gran(curr, se); //计算粒度,详述见下文  
  24.     if (vdiff > gran)  //如果vdiff大于粒度,则允许se抢占curr  
  25.         return 1;  
  26.   
  27.     return 0;  
  28. }  

这个函数返回-1表示新进程vruntime大于当前进程,当然不能抢占,返回0表示虽然新进程vruntime比当前进程小,但是没有小到调度粒度,一般也不能抢占。返回1表示新进程vruntime比当前进程小的超过了调度粒度,可以抢占。
调度粒度是什么概念呢?进程调度的时候每次都简单选择vruntime最小的进程调度,其实也不完全是这样。
假设进程A和B的vruntime很接近,那么A先运行了一个tick,vruntime比B大了,B又运行一个tick,vruntime又比A大了,又切换到A,这样就会在AB间频繁切换,对性能影响很大,因此如果当前进程的时间没有用完,就只有当有进程的vruntime比当前进程小超过调度粒度时,才能进行进程切换。
函数上面注释中那个图就是这个意思,我们看下:
横坐标表示vruntime,s1 s2 s3分别表示新进程,c表示当前进程,g表示调度粒度。
s3肯定能抢占c;而s1不可能抢占c。
s2虽然vruntime比c小,但是在调度粒度之内,能否抢占要看情况,像现在这种状况就不能抢占。

(摘自:http://blog.csdn.net/peimichael/article/details/5218335)

在选中了下一个将被调度执行的进程之后,回到pick_next_task_fair中,执行set_next_entity():


[cpp] view plaincopy
  1. static void  
  2. set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)  
  3. {  
  4.     /* 'current' is not kept within the tree. */  
  5.     if (se->on_rq) { //如果se尚在rq队列上  
  6.         /* 
  7.          * Any task has to be enqueued before it get to execute on 
  8.          * a CPU. So account for the time it spent waiting on the 
  9.          * runqueue. 
  10.          */  
  11.         update_stats_wait_end(cfs_rq, se);  
  12.         __dequeue_entity(cfs_rq, se); //将se从rq队列中删除  
  13.                 /* 
  14.                  *static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) 
  15.                  *{ 
  16.                  *    if (cfs_rq->rb_leftmost == &se->run_node) { 
  17.                  *        struct rb_node *next_node; 
  18.  
  19.                  *        next_node = rb_next(&se->run_node); 
  20.                  *        cfs_rq->rb_leftmost = next_node; //挑选下一个leftmost 
  21.                  *    } 
  22.                   
  23.  
  24.                  *     rb_erase(&se->run_node, &cfs_rq->tasks_timeline); //调整rb树 
  25.                  *} 
  26.                  */  
  27.     }  
  28.   
  29.     update_stats_curr_start(cfs_rq, se); //We are picking a new current task.更新sched_entity中的exec_start字段为当前clock_task.  
  30.     cfs_rq->curr = se; //将se设置为curr进程  
  31. #ifdef CONFIG_SCHEDSTATS  
  32.     /* 
  33.      * Track our maximum slice length, if the CPU's load is at 
  34.      * least twice that of our own weight (i.e. dont track it 
  35.      * when there are only lesser-weight tasks around): 
  36.      */  
  37.     if (rq_of(cfs_rq)->load.weight >= 2*se->load.weight) {  
  38.         se->statistics.slice_max = max(se->statistics.slice_max,  
  39.             se->sum_exec_runtime - se->prev_sum_exec_runtime);  
  40.     }  
  41. #endif  
  42.     se->prev_sum_exec_runtime = se->sum_exec_runtime; //更新task上一次投入运行的从时间。  
  43. }  

以上,我们讨论了公平调度的情况,回到pick_next_task中,如果rq队列中进程的数量不等于CFS中进程的数量,那么说明有其它类型的任务需要被优先调度。

[cpp] view plaincopy
  1. for_each_class(class) {   
  2.     p = class->pick_next_task(rq);  
  3.     if (p)  
  4.         return p;  
  5. }  

对于for_each_class:

[cpp] view plaincopy
  1. #define sched_class_highest (&stop_sched_class)  
  2. #define for_each_class(class) \  
  3.    for (class = sched_class_highest; classclass = class->next)  
我们可以得到这样的结论,不同类型的任务被调度的顺序是:

stop_sched_class     ---->     rt_sched_class    ---->    fair_sched_class    ---->    idle_sched_class    ---->    NULL

首先看一下stop_sched_class选取下一个进程的过程:

[cpp] view plaincopy
  1. static struct task_struct *pick_next_task_stop(struct rq *rq)  
  2. {  
  3.     struct task_struct *stop = rq->stop;  //直接将rq队列中的stop指向的进程作为即将被调度进来的进程。  
  4.   
  5.     if (stop && stop->on_rq)  
  6.         return stop;  
  7.   
  8.     return NULL;  
  9. }  

然后,看rt_sched_class挑选下一个进程的过程:

[cpp] view plaincopy
  1. static struct task_struct *pick_next_task_rt(struct rq *rq)  
  2. {  
  3.     struct task_struct *p = _pick_next_task_rt(rq);  
  4.   
  5.     /* The running task is never eligible for pushing */  
  6.     if (p)  
  7.         dequeue_pushable_task(rq, p);  
  8.   
  9. #ifdef CONFIG_SMP  
  10.     /* 
  11.      * We detect this state here so that we can avoid taking the RQ 
  12.      * lock again later if there is no need to push 
  13.      */  
  14.     rq->post_schedule = has_pushable_tasks(rq);  
  15. #endif  
  16.   
  17.     return p;  
  18. }  
其中的关键操作自然是_pick_next_task_rt:

[cpp] view plaincopy
  1. static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,  
  2.                            struct rt_rq *rt_rq)  
  3. {  
  4.     struct rt_prio_array *array = &rt_rq->active;  
  5.     struct sched_rt_entity *next = NULL;  
  6.     struct list_head *queue;  
  7.     int idx;  
  8.   
  9.     idx = sched_find_first_bit(array->bitmap); //找到active->bitmap中第一个置位的比特位,即高优先级。  
  10.     BUG_ON(idx >= MAX_RT_PRIO);  
  11.   
  12.     queue = array->queue + idx;  
  13.     next = list_entry(queue->next, struct sched_rt_entity, run_list);  
  14.   
  15.     return next;  
  16. }  

实时进程的调度用的是和CFS不同的调度方法,不过想比于CFS,它更简单。详细的可以参看《深入Linux内核架构》P117.

到目前为止,我们终于将下一个要执行的进程挑选出来了!


0 0
原创粉丝点击