linux进程调度与管理(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flaoter/article/details/75674415

转载请标明出处floater的csdn blog,http://blog.csdn.net/flaoter

本节内容介绍用于普通进程调度的完全公平调度类(Completely Fair Scheduler),它的基本原理是这样的:设定一个调度周期(sched_latency_ns),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的虚拟时间(vruntime)字段里,哪个进程的vruntime最小就获得本轮运行的权利。
下面先对引入的两个概念虚拟时间和调度周期等进行介绍,然后再对调度器类进行说明。

1 虚拟时间

CFS算法引入的虚拟时间概念,用于度量等待进程在完全公平系统中能得到的CPU时间,与实际运行时间和上一节提到的负荷权重相关,关系如下,
delta_vruntime = (now - curr->exec_start)* NICE_0_LOAD/se->load
task->vruntime += delta_vruntime
虚拟运行时间增量与实际运行时间(now - curr->exec_start)成正比,与权重成反比。虚拟运行时间是虚拟运行时间增量的累加和。
内核会根据vruntime的值决定进程在红黑树排序中的位置,vruntime越小进程越靠近红黑树的最左端,意味着更早地调度。
vruntime的计算代码如下,

static void update_curr(struct cfs_rq *cfs_rq)
{
    struct sched_entity *curr = cfs_rq->curr;  //当前进程
    u64 now = rq_clock_task(rq_of(cfs_rq));  //rq的clock_task
    u64 delta_exec;

    if (unlikely(!curr))
        return;

    delta_exec = now - curr->exec_start;   //计算当前时间与上一次update_curr的时间差
    if (unlikely((s64)delta_exec <= 0))
        return;

    curr->exec_start = now;  //更新exec_start,供下次使用

    schedstat_set(curr->statistics.exec_max,
              max(delta_exec, curr->statistics.exec_max));

    curr->sum_exec_runtime += delta_exec; //当前进程总的运行时间
    schedstat_add(cfs_rq, exec_clock, delta_exec);

    curr->vruntime += calc_delta_fair(delta_exec, curr);  //公式计算
    update_min_vruntime(cfs_rq);    //更新cfs_rq的最小vruntime
    ...
}

vruntime的计算是在update_curr函数中进行的,主要工作是
1. 计算vruntime,如代码中注释
2. 更新cfs_rq的min_vruntime。先在当前进程或就绪队列最左子节点进程中找出最小vruntime,再与队列的min_vruntime比较,如果某个节点的vruntime超出了队列的min_vruntime就进行更新。内核可确保min_vruntime只能增加,不能减少。

static void update_min_vruntime(struct cfs_rq *cfs_rq)
{
    u64 vruntime = cfs_rq->min_vruntime;

    if (cfs_rq->curr)
        vruntime = cfs_rq->curr->vruntime;

    if (cfs_rq->rb_leftmost) {
        struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost,
                           struct sched_entity,
                           run_node);

        if (!cfs_rq->curr)
            vruntime = se->vruntime;
        else
            vruntime = min_vruntime(vruntime, se->vruntime);
    }

    /* ensure we never gain time by being placed backwards. */
    cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
}

红黑树的排序是依赖进程的键值vruntime与cfs_rq->min_vruntime的差值决定的,键值越小的节点,排序位置越靠左。
下面是《深入理解Linux内核架构》中的一段话,
(1)在进程运行时, 其vruntime稳定地增加, 它在红黑树中总是向右移动的.
因为越重要的进程vruntime增加的越慢, 因此它们向右移动的速度也越慢, 这样其被调度的机会要大于次要进程, 这刚好是我们需要的
(2) 如果进程进入睡眠, 则其vruntime保持不变. 因为每个队列min_vruntime同时会单调增加(回想一下,它是单调的!), 那么当进程从睡眠中苏醒, 在红黑树中的位置会更靠左, 因为其键值相对来说变得更小了.
task_minvruntime

2 理想运行时间

调度延迟 sched_latency_ns:cfs调度保证在一个延迟周期内每个可运行的进程都应该至少运行一次。延迟周期可通过/proc/sys/kernel/sched_latency_ns控制, 默认值为20000000纳秒, 即20毫秒。

延迟周期中处理的最大活动进程数目 sched_nr_latency,sched_nr_latency可以通过sysctl_sched_min_granularity间接的控制, 后者可通过/procsys/kernel/sched_min_granularity_ns设置. 默认值是4000000纳秒, 即4毫秒, 每次sysctl_sched_latency/sysctl_sched_min_granularity之一改变时, 都会重新计算sched_nr_latency.

延迟周期 __sched_period = sysctl_sched_latency * nr_running / sched_nr_latency

每个进程的 理想运行时间 ideal_runtime值的大小与它的权重weight成正比。比如: 如果period=20ms,当前系统中只有A、B、C、D四个进程,它们的weight分别为:1、2、3、4。那么A的ideal_runtime = 2ms,B,C,D的ideal_runtime依次为4ms,6ms, 8ms。

ideal_runtime的计算方法如下,
task[i]->ideal_runtime = task[i]->weight * period / sun_weight(task,N) ;

理想运行时间的计算是通过sched_slice函数实现的, 代码实现与计算方法一致。

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);

    for_each_sched_entity(se) {
        struct load_weight *load;
        struct load_weight lw;

        cfs_rq = cfs_rq_of(se);
        load = &cfs_rq->load;

        if (unlikely(!se->on_rq)) {
            lw = cfs_rq->load;

            update_load_add(&lw, se->load.weight);
            load = &lw;
        }
        slice = __calc_delta(slice, se->load.weight, load);
    }
    return slice;
}

3 完全公平调度类

定义如下,

const struct sched_class fair_sched_class = {
    .next           = &idle_sched_class,
    .enqueue_task       = enqueue_task_fair,
    .dequeue_task       = dequeue_task_fair,
    .yield_task     = yield_task_fair,
    .yield_to_task      = yield_to_task_fair,

    .check_preempt_curr = check_preempt_wakeup,

    .pick_next_task     = pick_next_task_fair,
    .put_prev_task      = put_prev_task_fair,

#ifdef CONFIG_SMP
    .select_task_rq     = select_task_rq_fair,
    .migrate_task_rq    = migrate_task_rq_fair,

    .rq_online      = rq_online_fair,
    .rq_offline     = rq_offline_fair,

    .task_waking        = task_waking_fair,
    .task_dead      = task_dead_fair,
    .set_cpus_allowed   = set_cpus_allowed_common,
#endif

    .set_curr_task          = set_curr_task_fair,
    .task_tick      = task_tick_fair,
    .task_fork      = task_fork_fair,

    .prio_changed       = prio_changed_fair,
    .switched_from      = switched_from_fair,
    .switched_to        = switched_to_fair,

    .get_rr_interval    = get_rr_interval_fair,

    .update_curr        = update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
    .task_move_group    = task_move_group_fair,
#endif
};

常用的几个成员函数描述如下,
enqueue_task:当某个任务进入可运行状态时,该函数将得到调用。它将调度实体(进程)放入红黑树中,并对 nr_running 变量加 1。
dequeue_task:当某个任务退出可运行状态时调用该函数,它将从红黑树中去掉对应的调度实体,并从 nr_running 变量中减 1。
yield_task:在 compat_yield sysctl 关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端。
check_preempt_curr:该函数将检查当前运行的任务是否被抢占。在实际抢占正在运行的任务之前,CFS 调度程序模块将执行公平性测试。这将驱动唤醒式(wakeup)抢占。
pick_next_task:该函数选择接下来要运行的最合适的进程。
set_curr_task:当任务修改其调度类或修改其任务组时,将调用这个函数。
task_tick:该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占。
task_new:内核调度程序为调度模块提供了管理新任务启动的机会。CFS 调度模块使用它进行组调度,而用于实时任务的调度模块则不会使用这个函数。

本文将对pick_next_task_fair和task_tick_fair成员进行注释说明。
pick_next_task_fair的实现主要在pick_next_entity函数中实现,

static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
    struct sched_entity *left = __pick_first_entity(cfs_rq);   //取红黑树最左子节点
    struct sched_entity *se;

    /*
     * If curr is set we have to see if its left of the leftmost entity
     * still in the tree, provided there was anything in the tree at all.
     */
    if (!left || (curr && entity_before(curr, left)))  //left为空或者curr进程比left进程的vruntime更小
        left = curr;   //将curr代替最左子节点

    se = left; /* ideally we run the leftmost entity */  

    /*
     * Avoid running the skip buddy, if running something else can
     * be done without getting too unfair.
     */
    if (cfs_rq->skip == se) {   //cfs_rq->skip与得到的se相同的情况下,需要重新选择次优进程
        struct sched_entity *second;  //定义次优进程

        if (se == curr) {   //之前选择的进程是curr进程
            second = __pick_first_entity(cfs_rq);  //使用最左节点作为次优进程
        } else {   //之前选择的最左子节点
            second = __pick_next_entity(se);  //选择红黑树上下一节点
            if (!second || (curr && entity_before(curr, second)))  //重复比较second进程和curr进程
                second = curr;
        }

        if (second && wakeup_preempt_entity(second, left) < 1)
            se = second;
    }
//为了提高缓存利用率,last和net进程要与选出来的进程做比较
    /*
     * Prefer last buddy, try to return the CPU to a preempted task.
     */
    if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
        se = cfs_rq->last;

    /*
     * Someone really wants this to run. If it's not unfair, run it.
     */
    if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
        se = cfs_rq->next;

    clear_buddies(cfs_rq, se);

    return se;
}

task_tick_fair主要通过entity_tick函数实现,entity_tick除了更新统计信息外,会通过check_preempt_tick检查是否需要调度,

static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
    ...
     ideal_runtime = sched_slice(cfs_rq, curr);        //ideal_runtime是理论上的处理器运行时间片    
     delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;   //该进程本轮调度累计运行时间
      if (delta_exec > ideal_runtime) {   // 假如实际运行超过调度器分配的时间,就标记调度标志
            resched_task(rq_of(cfs_rq)->curr);
            clear_buddies(cfs_rq, curr);
            return;
      }
     ...
}

当该进程运行时间超过实际分配的时间片,resched_task(rq_of(cfs_rq)->curr)就设置need_resched标志 ,否则本进程继续执行。

猜你喜欢

转载自blog.csdn.net/flaoter/article/details/75674415
今日推荐