Chinaunix首页 | 论坛 | 博客
  • 博客访问: 732514
  • 博文数量: 79
  • 博客积分: 2671
  • 博客等级: 少校
  • 技术积分: 1247
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-02 15:26
个人简介

宅男

文章分类

全部博文(79)

文章存档

2017年(11)

2016年(12)

2015年(6)

2012年(10)

2011年(33)

2010年(7)

分类: Android平台

2017-07-06 13:18:07

我们其实都知道linux最著名的CFS调度器,但是CFS过于复杂。所以我想从RT scheduler来一步一步的揭开调度器的面纱。

其实linux内部有五种类型的scheduler class,可以认为是调度器等级,从stop开始、deadlinereal timefair最后是idle。这些class的优先级从高到低。最低的就是idle
每一个scheduler class都有一个next指针用来指向比它低一等级的class。

可以看一段pick_next_task的代码来了解一下吧。

  1. for_each_class(class) {

  2.                 p = class->pick_next_task(rq, prev, cookie);

  3.                 if (p) {

  4.                         if (unlikely(p == RETRY_TASK))

  5.                                 goto again;

  6.                         return p;

  7.                 }

  8.         }

  9.         BUG(); /* the idle class will always have a runnable task */

其实我们接触的比较多的是real time fairScheduler的作用大体分为两部分:

(1)       CPU上,选择合适的task运行。

(2)       为新创建或者唤醒的task选择合适的CPU

当然第二点对于单核CPU不适用。Scheduler class的函数特别多,从后续的讲解中一步步的来了解每一个函数的用途。

主要从三个函数入手

2713         .pick_next_task         = pick_next_task_rt,

2714         .put_prev_task          = put_prev_task_rt,

2715

2716 #ifdef CONFIG_SMP

2717         .select_task_rq         = select_task_rq_rt,

其中pick_next_task用于选择即将运行的task,而select_task_rq用于为进程选择合适的CPU

再次首先说明一下,在RT中,没有所谓的时间片(其实fair也没有了)的概念。都是高优先级的进程抢占低优先级的task,直到其主动放弃CPU使用权为止。而且RT task的优先级比正常的fair task要高。所以一般要求RT task运行时间尽可能的短。

RT task的优先级在0 99之间(包括099)。

pick_next_task用于选择下一个运行的RT task,但是情况在SMP下就稍微复杂了一下。因为我们追求的是整个系统的优先级高的RT task能够优先运行,如果当前进程的一个RT task 放弃CPU使用权的话,假如当前CPU上的下一个RT task的优先级比其余某一个CPU上第二优先级的RT task要低呢?

  1. static struct task_struct *

  2. pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct pin_cookie cookie)

  3. {

  4.         struct task_struct *p;

  5.         struct rt_rq *rt_rq = &rq->rt;

  6.         if (need_pull_rt_task(rq, prev)) {

在migrate里面有两种情况:pull是指当前CPU从别的CPU上拉一些进程过来运行。

Push是指将当前CPU上的一些进程migrate到别的CPU上运行。

  1. /*

  2.                  * This is OK, because current is on_cpu, which avoids it being

  3.                  * picked for load-balance and preemption/IRQs are still

  4.                  * disabled avoiding further scheduler activity on it and we're

  5.                  * being very careful to re-start the picking loop.

  6.                  */

  7.                 lockdep_unpin_lock(&rq->lock, cookie);

  8.                 pull_rt_task(rq);

  9.                 lockdep_repin_lock(&rq->lock, cookie);

  10.                 /*

  11.                  * pull_rt_task() can drop (and re-acquire) rq->lock; this

  12.                  * means a dl or stop task can slip in, in which case we need

  13.                  * to re-start task selection.

  14.                  */

  15.                 if (unlikely((rq->stop && task_on_rq_queued(rq->stop)) ||

  16.                              rq->dl.dl_nr_running)
如果出现了更高优先级class的task,那么就需要重新选择。


                        return RETRY_TASK;

        }

其中有一个need_pull_rt_task的判断。

static inline bool need_pull_rt_task(struct rq *rq, struct task_struct *prev)

{

        /*

         * Try to pull RT tasks here if we lower this rq's prio and cpu is not

         * isolated

         */

        return rq->rt.highest_prio.curr > prev->prio &&

               !cpu_isolated(cpu_of(rq));

}

代码很简单,意思说就是下一个进程优先级要比prev的优先级低的话,那么就需要尝试从别的runqueue上pull task过来。

接着看pull_rt_task函数。

        for_each_cpu(cpu, this_rq->rd->rto_mask) {
root domain是一个全局结构体,用来描述sched domain的一些系统全局的信息。

root domain的rto_mask_mask用于标记那些CPU是否rto(real time overload)。什么是rto呢?就是大于1个实时进程,而且存在可以迁移的TASK。什么是可以迁移的task ?就是tsk_nr_cpus_allowed 大于1.

                if (this_cpu == cpu)

                        continue;

                src_rq = cpu_rq(cpu);

                /*

                 * Don't bother taking the src_rq->lock if the next highest

                 * task is known to be lower-priority than our current task.

                 * This may look racy, but if this value is about to go

                 * logically higher, the src_rq will push this task away.

                 * And if its going logically lower, we do not care

                 */

                if (src_rq->rt.highest_prio.next >=

                    this_rq->rt.highest_prio.curr)

                        continue;

//如果src_rq的下一个RT task的优先级比当前runqueue的优先级要低的话。就跳过src_rq,因为是想pull一个高优先级的RT task过来。

                /*  

                 * We can pull only a task, which is pushable

                 * on its rq, and no others.

                 */

                p = pick_highest_pushable_task(src_rq, this_cpu);

//从src rq上找一个不在running,而且可以跑在this cpu上的进程。

                /*

                 * Do we have an RT task that preempts

                 * the to-be-scheduled task?

                 */

                if (p && (p->prio < this_rq->rt.highest_prio.curr)) {

//如果找到了一个进程,且该进程的优先级比当前进程的优先级要高的话。

                        WARN_ON(p == src_rq->curr);

                        WARN_ON(!task_on_rq_queued(p));

 

                        /*

                         * There's a chance that p is higher in priority

                         * than what's currently running on its cpu.

                         * This is just that p is wakeing up and hasn't

                         * had a chance to schedule. We only pull

                         * p if it is lower in priority than the

                         * current task on the run queue

                         */

                        if (p->prio < src_rq->curr->prio)

                                goto skip;

 

                        resched = true;

 

                        deactivate_task(src_rq, p, 0);

                        set_task_cpu(p, this_cpu);

                        activate_task(this_rq, p, 0);

//将该进程从src rq上dequeue掉,设置新的run CPU,然后重新enqueue到新的队列里面来。

                        /*

                         * We continue with the search, just in

                         * case there's an even higher prio task

                         * in another runqueue. (low likelihood

                         * but possible)

                         */

                }

skip:

                double_unlock_balance(this_rq, src_rq);

        }

//这边好像有点问题,因为for循环会执行好几次,会可能pull 好几个RT task过来?但是由于已经将task enqueue到rt rq了,所以
pick_highest_pushable_task应该会阻止这种情况。

        if (resched)

                resched_curr(this_rq);

//pull了一个新的高优先级的task过来了,需要重新sched

再次回到pick_next_task_rt函数。

        /*

         * We may dequeue prev's rt_rq in put_prev_task().

         * So, we update time before rt_nr_running check.

         */

        if (prev->sched_class == &rt_sched_class)

                update_curr_rt(rq);//在调用put_prev_task之前,调用update_curr_rt更新状态。

        if (!rt_rq->rt_queued)

                return NULL;

        put_prev_task(rq, prev);

        p = _pick_next_task_rt(rq);

        /* The running task is never eligible for pushing */

        dequeue_pushable_task(rq, p);

//将task p从rq的pushable task中删除,因为task p已经准备运行了。

        queue_push_tasks(rq);

//插入一个push task的call back给balance_callback(rq)。没太搞清楚意图?

相对而言put_prev_task_rt就简单的很多了。

        update_curr_rt(rq);

 

        /*  

         * The previous task needs to be made eligible for pushing

         * if it is still active

         */

        if (on_rt_rq(&p->rt) && p->nr_cpus_allowed > 1)

                enqueue_pushable_task(rq, p);

//将进程插入到当前rq的链表中(按优先级排序),更新rq的highest_prio.next

 

接下来是select_task_rq_rt函数。

        if (curr && unlikely(rt_task(curr)) &&

            (curr->nr_cpus_allowed < 2 ||

             curr->prio <= p->prio)) {

//如果当前的task是RT,并且当前进程只能在当前CPU上运行。那么给进程p重新选择一个CPU,否则就在当前CPU上运行。

                int target = find_lowest_rq(p);

 

                /*

                 * Don't bother moving it if the destination CPU is

                 * not running a lower priority task.

                 */

                if (target != -1 &&

                    p->prio < cpu_rq(target)->rt.highest_prio.curr)

                        cpu = target;

        }

接着调用find_lowest_rq.来选择一个优先级最低的CPU。

static int find_lowest_rq(struct task_struct *task)

{

        struct sched_domain *sd;

        struct cpumask *lowest_mask = this_cpu_cpumask_var_ptr(local_cpu_mask);

        int this_cpu = smp_processor_id();

        int cpu      = task_cpu(task);

 

        /* Make sure the mask is initialized first */

        if (unlikely(!lowest_mask))

                return -1;

 

        if (task->nr_cpus_allowed == 1)

//如果task affinity绑定在一个CPU上不能migrate的话,就不需要选择rq了。

                return -1; /* No other targets possible */

 

        if (!cpupri_find(&task_rq(task)->rd->cpupri, task, lowest_mask))

                return -1; /* No targets found */

//这里面最关键的就是cpupri_find函数了,通过该函数查找可以容纳该进程的CPU。

怎么算可以容纳呢?即进程p可以抢占CPU,也就是task的优先级比CPU上的优先级最高的进程都要高。

为此在rd->cpupri中维护了一个数组。

struct cpupri {

        struct cpupri_vec pri_to_cpu[CPUPRI_NR_PRIORITIES];

        int *cpu_to_pri;

};

该数组大小为102,用于维护CPU的优先级最高的task。除了0~99一共100个RT优先级,还有两外两个IDLE 和NORMAL分别用于
在不存在RT task情况下,用于标记runqueue为idle或者fair tasks。
可以从函数cpupri_set以及cpupri_cleanup上看出使用方法。

Cpu_to_pri是一个NR_CPUS的数组,维护着每一个CPU上RT 的最高优先级。

        /*

         * At this point we have built a mask of cpus representing the

         * lowest priority tasks in the system.  Now we want to elect

         * the best one based on our affinity and topology.

         *

         * We prioritize the last cpu that the task executed on since

         * it is most likely cache-hot in that location.

         */

        if (cpumask_test_cpu(cpu, lowest_mask))

                return cpu;

//如果prev cpu满足条件,就是用prev cpu

        if (!cpumask_test_cpu(this_cpu, lowest_mask))

                this_cpu = -1; /* Skip this_cpu opt if not among lowest */

 

        rcu_read_lock();

        for_each_domain(cpu, sd) {

                if (sd->flags & SD_WAKE_AFFINE) {

                        int best_cpu;

 

                        /*

                         * "this_cpu" is cheaper to preempt than a

                         * remote processor.

                         */

                        if (this_cpu != -1 &&

                            cpumask_test_cpu(this_cpu, sched_domain_span(sd))) {

//如果当前CPU满足条件,并且所在sched domain满足SD_WAKE_AFFINE条件的话。使用当前CPU。

                                rcu_read_unlock();

                                return this_cpu;

                        }

 

                        best_cpu = cpumask_first_and(lowest_mask,

                                                     sched_domain_span(sd));

//否则从sched中随意找出第一个满足条件的CPU。

                        if (best_cpu < nr_cpu_ids) {

                                rcu_read_unlock();

                                return best_cpu;

                        }

                }

        }

        rcu_read_unlock();

1619         /*

1620          * And finally, if there were no matches within the domains

1621          * just give the caller *something* to work with from the compatible

1622          * locations.

1623          */

1624         if (this_cpu != -1)

1625                 return this_cpu;

//如果当前CPU满足条件,就是用当前CPU,否则从满足条件的CPU中选择第一个。

1626

1627         cpu = cpumask_any(lowest_mask);

1628         if (cpu < nr_cpu_ids)

1629                 return cpu;

个人认为上面三个是比较重要的函数,下面想看一下tasken/dequeue

RT runqueue维护了一个bitmapper prioritylist

133 struct rt_prio_array {

 134         DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */

 135         struct list_head queue[MAX_RT_PRIO];

 136 };

同时队列还维护着一个pushable_tasks队列,什么叫pushable task ?也就是nr_cpu_allows >=2.也就是可以在2个及以上CPU运行的,即可以migratetask

下面直接开始看代码吧。

static void

enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)

{

        struct sched_rt_entity *rt_se = &p->rt;

 

        if (flags & ENQUEUE_WAKEUP)

                rt_se->timeout = 0;

 

        enqueue_rt_entity(rt_se, flags & ENQUEUE_HEAD);

 

        if (!task_current(rq, p) && p->nr_cpus_allowed > 1)

                enqueue_pushable_task(rq, p);

//如果进程的nr_cpus_allowed > 1,添加到pushable 队列中。

}

 

static void enqueue_rt_entity(struct sched_rt_entity *rt_se, bool head)

{

        struct rq *rq = rq_of_rt_se(rt_se);

 

        dequeue_rt_stack(rt_se);

//这个函数看起来比较奇怪,rt stack ?!为什么呢。因为这个涉及到CGROUP相关的结构,我们的cgroup都是只有parent指针,没有child指针。但是比如添加一个较高优先级的rt task到一个cgroup的话。那么在cgroup的整体的优先级就会被提升,然后在list以及bitmap中的位置就会发生变化。就需要从上到下的来重新一个个插入到相应的list中去。

所以dequeue_rt_stack利用rt entity的back成员建立一个从上到下的层次结构,一边后续利用

        for_each_sched_rt_entity(rt_se)

                __enqueue_rt_entity(rt_se, head);

//因为在上面的dequeue_rt_stack中将每一个层次的rt entity冲列表中删除。这里根据新的优先级重新插入。

        enqueue_top_rt_rq(&rq->rt);

}

 

dequeue_task_rt函数大体跟enqueue_task_rt差不多。

 

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