进程调度(二)

来源:互联网 发布:java静态变量默认值 编辑:程序博客网 时间:2024/04/29 19:51

紧接上一文!!!!

​3:进程选择

在CFS调度里面,当需要选择下一个进程的时候,将会选择最小的vruntime的进程。这个其实就是CFS调度的算法的核心。

CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小的vruntime值的进程。在Linux中,红黑树是一个子平衡的二叉搜索树。

下面我们就来看一下如何挑选下一个vruntime最小的进程。

1):挑选下一个任务

根据红黑树的原理,假设vruntime就是节点的键值,那么如果要寻找最小的vruntime的进程,就是从树的根节点开始,一直向左寻找,一直找到树的最左边的节点即为vruntime最小的节点。

下面我们来看一下如何进行选择下一个任务的函数__pick_next_entity()。定义在kernel/sched_fair.c中:

static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq){    struct rb_node *left = cfs_rq->rb_leftmost;    if (!left)        return NULL;    return rb_entry(left, struct sched_entity, run_node);}

看了这段代码,很明显,并没有进行寻找,而是直接获取了运行队列中的rb_leftmost成员,其实,这就是linux中的一个用空间省时间的一个做法。他先将最小vruntime的节点保存下来,保存成rb_leftmost成员,当使用的时候,直接拿来使用就可以,但是当每次vruntime更新的时候,都会重现最一下选择,选择出最小的vruntime的节点保存到该成员中。如果找不到最小的vruntime的进程,那么CFS调度其将会选择idle任务运行。

2):向树中加入进程

当进程变为可运行状态或者是通过fork()调用第一次创建进程的时候,CFS就需要将进程加入到rbtree中。现在我们来看一下如何将进程加入到rbtree中,在函数enqueue_entity中被实现。

static voidenqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags){    /*     * Update the normalized vruntime before updating min_vruntime     * through callig update_curr().     *     * 通过调用update_curr()函数,在更新min_vruntime之前来更新     * 规范化的vruntime     */    if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATE))        se->vruntime += cfs_rq->min_vruntime;    /*     * Update run-time statistics of the 'current'.     *     * 更新'当前任务'的运行时间统计数据     */    update_curr(cfs_rq);    account_entity_enqueue(cfs_rq, se);    if (flags & ENQUEUE_WAKEUP) {        place_entity(cfs_rq, se, 0);        enqueue_sleeper(cfs_rq, se);    }    update_stats_enqueue(cfs_rq, se);    check_spread(cfs_rq, se);    if (se != cfs_rq->curr)        __enqueue_entity(cfs_rq, se);}

该函数更新可运行时间和其他的一些统计数据,然后,调用函数__enqueue_entity()函数向rbtree中插入节点,将数据真正插入到rbtree中。
下面我们看一下函数__enqueue_entity()。

/* * Enqueue an entity into the rb-tree: * 向rbtree中添加一个实体 */static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se){    struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;    struct rb_node *parent = NULL;    struct sched_entity *entry;    s64 key = entity_key(cfs_rq, se);    int leftmost = 1;    /*     * Find the right place in the rbtree:     *     * 在rbtree中寻找合适的位置     */    while (*link) {        parent = *link;        entry = rb_entry(parent, struct sched_entity, run_node);        /*         * We dont care about collisions. Nodes with         * the same key stay together.         *         * 我们不关心冲突,含有相同键值的节点可以呆在一起。         */        if (key < entity_key(cfs_rq, entry)) {            link = &parent->rb_left;        } else {            link = &parent->rb_right;            leftmost = 0;        }    }    /*     * Maintain a cache of leftmost tree entries (it is frequently     * used):     *     * 维护一个缓存,存放最左叶子节点(他会经常被使用)     */    if (leftmost)        cfs_rq->rb_leftmost = &se->run_node;    rb_link_node(&se->run_node, parent, link);    rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);}

如果对这些代码不太理解的话,也不用太担心,当我们后面学习了红黑树的时候,这些问题就都理解了。这里对节点的插入就不详细讲解,后面会专门讲解红黑树。

3):从树中删除进程

当进程堵塞或者是终止的时候,CFS需要将进程从rbtree中删除,下面我们来看一下CFS是如何删除进程的。

static voiddequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int sleep){    /*     * Update run-time statistics of the 'current'.     *     * 更新‘当前进程’的运行时间统计     */    update_curr(cfs_rq);    update_stats_dequeue(cfs_rq, se);    if (sleep) {#ifdef CONFIG_SCHEDSTATS        if (entity_is_task(se)) {            struct task_struct *tsk = task_of(se);            if (tsk->state & TASK_INTERRUPTIBLE)                se->sleep_start = rq_of(cfs_rq)->clock;            if (tsk->state & TASK_UNINTERRUPTIBLE)                se->block_start = rq_of(cfs_rq)->clock;        }#endif    }    //清楚占有的内存    clear_buddies(cfs_rq, se);    if (se != cfs_rq->curr)        __dequeue_entity(cfs_rq, se);    account_entity_dequeue(cfs_rq, se);    update_min_vruntime(cfs_rq);    /*     * Normalize the entity after updating the min_vruntime because the     * update can refer to the ->curr item and we need to reflect this     * movement in our normalized position.     *     * 当更新min_vruntime之后,规范化调度器实体,因为更新可以指向"->curr"     * 项,我们需要在规范化的位置来反映这个变化     */    if (!sleep)        se->vruntime -= cfs_rq->min_vruntime;}

根据代码,实际上是调用__dequeue_entity()函数来删除rbtree中的实体。

下面我们看一下代码:

static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se){    if (cfs_rq->rb_leftmost == &se->run_node) {        struct rb_node *next_node;        next_node = rb_next(&se->run_node);        cfs_rq->rb_leftmost = next_node;    }    rb_erase(&se->run_node, &cfs_rq->tasks_timeline);}

具体的对于rbtree的操作我们后面具体详解。

从红黑树中删除节点比较容易,因为rbtree实现了rb_erase()函数,可以完成所有工作。该函数剩下的工作就是更新rb_leftmost缓存,如果删除的是最左节点,则需要遍历树,找到下一个最左节点。

4:调度器入口

调度器的主要入口是函数schedule(),定义在文件kernel/sched.h文件中。他是内核其他部分用于调用进程调度器的入口:选择哪个进程可以运行,何时将其投入运行。该函数唯一重要的事情就是调用pick_next_task()函数,该函数会以优先级为序,从高到低,依次检查每一个调度器类,并且从最高优先级调度器类中选择最高优先级的进程。

下面我们看一下这个函数:

/* * Pick up the highest-prio task: * 选择最高优先级进程 */static inline struct task_struct *pick_next_task(struct rq *rq){    const struct sched_class *class;    struct task_struct *p;    /*     * Optimization: we know that if all tasks are in     * the fair class we can call that function directly:     *     * 优化:我们知道,如果所有的进程都在公平调度器类中的话,     * 我们就可以直接调用那个函数     */    if (likely(rq->nr_running == rq->cfs.nr_running)) {        p = fair_sched_class.pick_next_task(rq);        if (likely(p))            return p;    }    class = sched_class_highest;    for ( ; ; ) {        p = class->pick_next_task(rq);        if (p)            return p;        /*         * Will never be NULL as the idle class always         * returns a non-NULL p:         *         * 永远不会为NULL,因为idle类总会返回非NULL的p         */        class = class->next;    }}

该函数开始部分的优化比较有意思。因为CFS是普通进程的调度类,而系统的绝大部分进程是普通进程,所以这里使用该技巧来加速一下下一个CFS提供的进程。但是前提是所有的可运行进程都是CFS类的。

该函数的主要部分是for循环,从最高的优先级类开始,遍历了每一个调度类。每一个调度类都实现了pick_next_task()函数,他会返回下一个可运行进程或者是没有时返回NULL,我们会从第一个返回非NULL的类中选择下一个可运行进程。

1 0
原创粉丝点击