`
hongbochen1223
  • 浏览: 44000 次
文章分类
社区版块
存档分类
最新评论

进程调度(二)

阅读更多

紧接上一文!!!!

​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 void
enqueue_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 void
dequeue_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的类中选择下一个可运行进程。

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>

版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

    进程调度程序模拟 进程调度程序模拟 进程调度程序模拟

    进程调度程序模拟 进程调度程序模拟 进程调度程序模拟 进程调度程序模拟 进程调度程序模拟 进程调度程序模拟

    单处理器系统的进程调度

    实验二 单处理器系统的进程调度 1.实验目的 加深对进程概念的理解,明确进程和程序的区别; 深入了解系统如何组织进程、创建进程; 进一步认识如何实现处理器调度。 2.实验预备知识 进程的概念; 进程的组织方式...

    进程调度论文进程调度论文 part2

    进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文

    实验一 进程调度实验报告

    进程调度课程设计 给需要的人 呵呵 实验一 进程调度实验 一、实验目的 通过对进程调度算法的模拟加深对进程概念和进程调度算法的理解。 二、实验要求 编写程序实现对5个进程的调度模拟,要求至少采用两种不同的...

    进程调度的设计与分析实验报告

    综合应用下列知识点设计并实现操作系统的进程调度:邻接表,布尔数组,非阻塞输入,图形用户界面GUI,进程控制块,进程状态转换,多级反馈队列进程调度算法。 2、 加深理解操作系统进程调度的过程。 3、 加深理解...

    C#进程调度模拟算法

    C#进程调度模拟算法C#进程调度模拟算法C#进程调度模拟算法

    操作系统进程调度算法 c语言实现

    实现进程调度算法,具有后备序列的调度 题目:设计一个有 N个进程共行的进程调度程序。 进程调度算法:采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法。 每个进程有一个进程...

    操作系统进程调度实验

    进程调度模拟程序:假设有10个进程需要在CPU上执行,分别用: 先进先出调度算法; 基于优先数的调度算法; 最短执行时间调度算法 确定这10个进程在CPU上的执行过程。要求每次进程调度时在屏幕上显示: 当前...

    进程调度算法的设计

    进程调度算法的设计 设计要求: ①设计进程控制块PCB表结构,分别适用于优先数调度算法和循环轮转调度算法。 ②建立进程就绪队列。对两种不同算法编制入链子程序。 ③编制两种进程调度算法:1)优先数调度;2)循环...

    设计一个有 N个进程共行的进程调度程序

    1、进程调度算法:采用动态最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)。 2、每个进程有一个进程控制块( PCB)表示。进程控制块可以包含如下信息: 进程名---进程标示数 ID 优先数 PRIORITY ...

    操作系统实验一: 进程调度

    实验1 进程调度(2学时) 一、实验目的 通过实验加强对进程调度算法的理解和掌握。 二、实验内容 编写程序实现基于优先级的时间片轮转调度算法。 三、实验要求 1、假定系统有5个进程,每个进程用一个进程...

    C++进程优先级调度进程优先级调度进程优先级调度

    C++进程优先级调度进程优先级调度进程优先级调度C++进程优先级调度进程优先级调度进程优先级调度

    广东工业大学操作系统实验一进程调度

    广东工业大学 操作系统 实验一 进程调度 一、 实验目的 用高级语言编写和调试一个进程调度程序,以加深对进程的概念及进程调度算法的理解. 二、 实验内容和要求 设计一个有 N个进程共行的进程调度程序。 进程调度...

    操作系统进程调度C++代码实现

    目的: 在进程控制、请求分页存储器管理、设备管理基础上 实现按先来...5.调度时应适当输出调度过程中各进程状态队列的变化情况以及进程的已执行时 间、还需服务时间(针对时间片轮转算法)。 6.完成银行家算法的实现。

    进程调度设计与实现

    1、 综合应用下列知识点设计并实现操作系统的进程调度:邻接表,布尔数组,非阻塞输入,图形用户界面GUI,进程控制块,进程状态转换,多级反馈队列进程调度算法。 2、 加深理解操作系统进程调度的过程。 3、 加深...

    使用动态优先权的进程调度算法的模拟

    通过动态优先权算法的模拟加深对进程概念和进程调度过程的理解。 2、实验内容 (1)用C语言来实现对N个进程采用动态优先算法的进程调度; (2)每个用来标识进程的进程控制块 PCB用结构来描述,包括以下字段: 进程...

    进程调度论文进程调度论文 part1

    进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文进程调度论文

    进程调度模拟程序 图形演示

    这就要求系统能按某种算法,动态地把处理机分配给就绪队列中的一个进程,使之运行,分配处理机的任务是由进程调度程序完成的。一个进程被创建后,系统为了便于对进程进行管理,将系统中的所有进程按其状态,将其组织...

    模拟进程调度(程序+代码+完整报告)

    设计、编写一个进程调度程序,允许多个进程共同运行的进程调度程序。 (1)进程调度算法:采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法。 (2)每个进程有一个进程控制块( ...

    【C语言源代码】 操作系统-短进程优先-进程调度算法

    C语言实现:短进程优先-进程调度算法 1. 采用“短进程优先”调度算法对五个进程进行调度。每个进程有一个进程控制块( PCB)表示。进程控制块可以包含如下信息:进程名、到达时间、需要运行时间、已用CPU时间、进程...

Global site tag (gtag.js) - Google Analytics