Skip to main content
 首页 » 操作系统

Linux 调度器之cat /proc//sched内容分析

2022年07月19日24TianFang

一、文件内容和统计

1. /proc/<pid>/sched 文件内容

# cat /proc/1040/sched 
surfaceflinger (1040, #threads: 35) 
------------------------------------------------------------------- 
se.exec_start                                :      13494669.924940 
se.vruntime                                  :       3727092.232665 
se.sum_exec_runtime                          :         66530.390986 
se.nr_migrations                             :                24462 
se.statistics.sum_sleep_runtime              :      12630670.892047 
se.statistics.wait_start                     :             0.000000 
se.statistics.sleep_start                    :      13618468.809058 
se.statistics.block_start                    :             0.000000 
se.statistics.sleep_max                      :       6094680.389694 
se.statistics.block_max                      :           399.362397 
se.statistics.exec_max                       :             4.024010 
se.statistics.slice_max                      :            18.451818 
se.statistics.wait_max                       :           236.678333 
se.statistics.wait_sum                       :          1173.521716 
se.statistics.wait_count                     :                 1413 
se.statistics.iowait_sum                     :           531.031575 
se.statistics.iowait_count                   :                  338 
se.statistics.nr_migrations_cold             :                    0 
se.statistics.nr_failed_migrations_affine    :                    0 
se.statistics.nr_failed_migrations_running   :                  207 
se.statistics.nr_failed_migrations_hot       :                    0 
se.statistics.nr_forced_migrations           :                   31 
se.statistics.nr_wakeups                     :                88930 
se.statistics.nr_wakeups_sync                :                51540 
se.statistics.nr_wakeups_migrate             :                23234 
se.statistics.nr_wakeups_local               :                  787 
se.statistics.nr_wakeups_remote              :                88143 
se.statistics.nr_wakeups_affine              :                    0 
se.statistics.nr_wakeups_affine_attempts     :                    0 
se.statistics.nr_wakeups_passive             :                    0 
se.statistics.nr_wakeups_idle                :                    0 
avg_atom                                     :             0.739399 
avg_per_cpu                                  :             2.719744 
nr_switches                                  :                89979 
nr_voluntary_switches                        :                88921 
nr_involuntary_switches                      :                 1058 
se.load.weight                               :              6246400 
se.runnable_weight                           :              6246400 
se.avg.load_sum                              :                  425 
se.avg.runnable_load_sum                     :                  425 
se.avg.util_sum                              :               435200 
se.avg.load_avg                              :                  154 
se.avg.runnable_load_avg                     :                  154 
se.avg.util_avg                              :                   25 
se.avg.last_update_time                      :       13440015644236 
se.avg.util_est.ewma                         :                   76 
se.avg.util_est.enqueued                     :                   69 
policy                                       :                    0 
prio                                         :                  112 
clock-delta                                  :                  104

2. 文件导出函数

//fs/proc/base.c 
static const struct pid_entry tgid_base_stuff[] = { 
... 
#ifdef CONFIG_SCHED_DEBUG 
    REG("sched", S_IRUGO|S_IWUSR, proc_pid_sched_operations), 
#endif 
... 
}; 
 
static const struct file_operations proc_pid_sched_operations = { 
    .open        = sched_open, 
    .read        = seq_read, 
    .write        = sched_write, 
    .llseek        = seq_lseek, 
    .release    = single_release, 
};

有写权限,sched_write 中 p->se.statistics 清0,写之后,再cat会发现se.statistics.X成员全部是0了,这样就可以实现观测感兴趣的线程一段时间内的 se.statistics 的统计情况了。sched_show()中可知 打印出来的 se.statistics.X 的单位都是ms

#define P_SCHEDSTAT(F)    SEQ_printf(m, "  .%-30s: %lld\n",    #F, (long long)schedstat_val(F)) //直接显示的是值 
 
//这个pid_namespace只是为了获取一个pid 
void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, struct seq_file *m) 
{ 
    unsigned long nr_switches; 
 
    SEQ_printf(m, "%s (%d, #threads: %d)\n", p->comm, task_pid_nr_ns(p, ns), get_nr_threads(p));//return task->signal->nr_threads; 
    SEQ_printf(m, "------------------------------------------------------------------\n"); 
//宏在编译预处理时起作用,作用域不会被限制,放在函数外是一样的 
#define __P(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)F) 
#define P(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)p->F) 
#define P_SCHEDSTAT(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)schedstat_val(p->F)) 
#define __PN(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)F)) 
#define PN(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)p->F)) //SPLIT_NS搞过后单位是ms 
#define PN_SCHEDSTAT(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)schedstat_val(p->F))) 
 
    PN(se.exec_start); 
    PN(se.vruntime); 
    PN(se.sum_exec_runtime); 
 
    nr_switches = p->nvcsw + p->nivcsw; 
 
    P(se.nr_migrations); //单位是次数 
 
    if (schedstat_enabled()) { 
        u64 avg_atom, avg_per_cpu; 
 
        PN_SCHEDSTAT(se.statistics.sum_sleep_runtime); 
        PN_SCHEDSTAT(se.statistics.wait_start); 
        PN_SCHEDSTAT(se.statistics.sleep_start); 
        PN_SCHEDSTAT(se.statistics.block_start); 
        PN_SCHEDSTAT(se.statistics.sleep_max); 
        PN_SCHEDSTAT(se.statistics.block_max); 
        PN_SCHEDSTAT(se.statistics.exec_max); 
        PN_SCHEDSTAT(se.statistics.slice_max); 
        PN_SCHEDSTAT(se.statistics.wait_max); 
        PN_SCHEDSTAT(se.statistics.wait_sum); 
        P_SCHEDSTAT(se.statistics.wait_count); 
        PN_SCHEDSTAT(se.statistics.iowait_sum); 
        P_SCHEDSTAT(se.statistics.iowait_count); 
        P_SCHEDSTAT(se.statistics.nr_migrations_cold); 
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_affine); 
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_running); 
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_hot); 
        P_SCHEDSTAT(se.statistics.nr_forced_migrations); 
        P_SCHEDSTAT(se.statistics.nr_wakeups); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_sync); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_migrate); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_local); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_remote); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_affine); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_affine_attempts); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_passive); 
        P_SCHEDSTAT(se.statistics.nr_wakeups_idle); 
 
        avg_atom = p->se.sum_exec_runtime; 
        if (nr_switches) 
            avg_atom = div64_ul(avg_atom, nr_switches); //除的是切换的次数 
        else 
            avg_atom = -1LL; 
 
        avg_per_cpu = p->se.sum_exec_runtime; 
        if (p->se.nr_migrations) { 
            avg_per_cpu = div64_u64(avg_per_cpu, p->se.nr_migrations); //除的是迁移的次数 
        } else { 
            avg_per_cpu = -1LL; 
        } 
 
        __PN(avg_atom); //单位也是ms 
        __PN(avg_per_cpu); 
    } 
 
    __P(nr_switches); 
    SEQ_printf(m, "%-45s:%21Ld\n", "nr_voluntary_switches", (long long)p->nvcsw); 
    SEQ_printf(m, "%-45s:%21Ld\n", "nr_involuntary_switches", (long long)p->nivcsw); 
 
    P(se.load.weight); 
    P(se.runnable_weight); 
#ifdef CONFIG_SMP 
    P(se.avg.load_sum); 
    P(se.avg.runnable_load_sum); 
    P(se.avg.util_sum); 
    P(se.avg.load_avg); 
    P(se.avg.runnable_load_avg); 
    P(se.avg.util_avg); 
    P(se.avg.last_update_time); 
    P(se.avg.util_est.ewma); 
    P(se.avg.util_est.enqueued); 
#endif 
    P(policy); 
    P(prio); 
    if (task_has_dl_policy(p)) { 
        P(dl.runtime); 
        P(dl.deadline); 
    } 
#undef PN_SCHEDSTAT //使上面定义的宏失效 
#undef PN 
#undef __PN 
#undef P_SCHEDSTAT 
#undef P 
#undef __P 
 
    { 
        unsigned int this_cpu = raw_smp_processor_id(); 
        u64 t0, t1; 
 
        t0 = cpu_clock(this_cpu); 
        t1 = cpu_clock(this_cpu); 
        SEQ_printf(m, "%-45s:%21Ld\n", "clock-delta", (long long)(t1-t0)); //获取cpu时间的代码的执行时长,但是ns 
    } 
 
    sched_show_numa(p, m); //没使能CONFIG_NUMA_BALANCING,不执行 
}

二、每个成员解析

1. se.exec_start

(1) CFS
在 update_curr() 中给他赋值为 rq->clock_task

update_curr 的调用路径:

pick_next_task_fair 
set_next_task_fair 
    set_next_entity 
        update_stats_curr_start //里面只做了更新se.exec_start的操作 
fair_sched_class.task_fork 
    task_fork_fair //对current的cfs_rq调用 
fair_sched_class.yield_task 
    yield_task_fair //对rq->curr的cfs_rq调用 
fair_sched_class.pick_next_task 
    pick_next_task_fair //若cfs_rq->curr->on_rq调用 
fair_sched_class.check_preempt_curr 
    check_preempt_wakeup //对rq->curr的cfs_rq调用 
task_tick_fair 
    entity_tick //若参数queued为真就对参数cfs_rq调用 
put_prev_task_fair 
pick_next_task_fair //在pick_next_entity得到的不是prev的时候调用 
    put_prev_entity //若prev->on_rq才调用 
dequeue_task_fair 
throttle_cfs_rq 
    dequeue_entity //入口处无条件调用 
unthrottle_cfs_rq 
enqueue_task_fair 
    enqueue_entity //入口处无条件调用 
update_cfs_group 
reweight_task //更改prio时调用 
    reweight_entity //若要被reweight正在执行,就调用 
fair_sched_class.update_curr 
    update_curr_fair 
        update_curr 
            se.exec_start = rq->clock_task;

注意,虽然每个事件都更新 se.exec_start,但是并不是每个se的都使用,对于curr这个se可以用于做差求上个事件距现在运行的时长。

(2) DL

pick_next_task_dl 
dl_sched_class.set_next_task 
    set_next_task_dl 
dequeue_task_dl 
yield_task_dl 
put_prev_task_dl 
task_tick_dl 
dl_sched_class.update_curr 
    update_curr_dl 
        curr->se.exec_start = rq->clock_task;

(3) STOP

stop_sched_class.put_prev_task 
    put_prev_task_stop //stop_task.c 
        curr->se.exec_start = rq->clock_task; 
stop_sched_class.set_next_task 
pick_next_task_stop 
    set_next_task_stop //stop_task.c 
        stop->se.exec_start = rq->clock_task;

(4) RT

pick_next_task_rt 
rt_sched_class.set_next_task 
    set_next_task_rt 
dequeue_task_rt 
put_prev_task_rt 
task_tick_rt 
rt_sched_class.update_curr 
    update_curr_rt 
        curr->se.exec_start = rq->clock_task;

(5) 其它

init_idle //core.c 
    idle->se.exec_start = sched_clock(); 
normalize_rt_tasks 
    p->se.exec_start = 0;

使用到的 rq->clock_task 的更新路径:

hrtick //core.c 
enqueue_task //core.c 若参数flags中无ENQUEUE_NOCLOCK才执行 
dequeue_task //core.c 若参数flags中无ENQUEUE_NOCLOCK才执行 
move_queued_task //core.c 若参数rq->clock_update_flags中无RQCF_UPDATED才执行 
__migrate_task //core.c 
__set_cpus_allowed_ptr //core.c 
ttwu_remote //core.c 
sched_ttwu_pending //core.c 
ttwu_queue //core.c 
wake_up_new_task //core.c 
task_sched_runtime //core.c 
scheduler_tick //core.c 
sched_tick_remote //core.c 
__schedule //core.c 
rt_mutex_setprio //core.c 
set_user_nice //core.c 
__sched_setscheduler //core.c 
migrate_tasks //core.c 在迁移任务的for循环执行前调用一次,然后在迁移一个任务之前和之后,只要rq->clock_update_flags中无RQCF_UPDATED都调用一次 
sched_move_task 
cpu_cgroup_fork 
    update_rq_clock //里面会更新rq->clock 
        update_rq_clock_task //core.c 参数delta是当前时间减去rq->clock的差值,然后将当前时间赋值为rq->clock 
            rq->clock_task += delta; //加上的是参数delta中除去中断时间后的delta

看来,几乎在所有事件执行前都要对 rq->clock_task 进行赋值,它累积的是除去中断时间后的时间差值。

两者维护的主体不同,rq->clock_task 是 core.c 维护的 rq 的时间线信息, 是个时间点,se.exec_start 是各个调度类维护的时间线信息,两者都频繁更新,cat sched文件中的这个字段没啥参考意义了。

2. se.vruntime

(1) 更新位置1

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)); 
    delta_exec = now - curr->exec_start; //这个delta是不包括中断时间的 
    curr->vruntime += calc_delta_fair(delta_exec, curr); //return delta_exec * NICE_0_LOAD / cfs_rq->curr->load.weight; 
}

update_curr更上层的调用路径见对 se.exec_start 字段的说明。几乎在所有事件下都会更新当前任务cfs_rq->curr的虚拟时间。

(2) 更为位置2

static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial) 
{ 
    u64 vruntime = cfs_rq->min_vruntime; //update_curr中更新 
 
    //这个feature是对新线程进程惩罚的,惩罚的虚拟时间其权重在cfs_rq上分得的时间大小 
    if (initial && sched_feat(START_DEBIT)) //默认使能 
        vruntime += sched_vslice(cfs_rq, se); 
 
    /*对非新fork()的线程, 再进行一点补偿,这里主要是sleep唤醒的线程*/ 
    if (!initial) { 
        unsigned long thresh = sysctl_sched_latency; 
 
        if (sched_feat(GENTLE_FAIR_SLEEPERS)) //默认使能,使能后补偿时间减少一半 
            thresh >>= 1; 
        vruntime -= thresh; 
 
#ifdef CONFIG_SCHED_WALT 
        if (entity_is_task(se)) { 
            /*若是写/proc/<pid>/sched_boost正在进行boost的线程,或是标记的low_latency的线程并且负载低于指定的阈值, 
            或是rtg组里面的线程并且优先级高于指定的阈值,就进行虚拟时间补偿。 
            */ 
            if ((per_task_boost(task_of(se)) == TASK_BOOST_STRICT_MAX) || 
                    walt_low_latency_task(task_of(se)) || 
                    task_rtg_high_prio(task_of(se))) { 
                vruntime -= sysctl_sched_latency; 
                vruntime -= thresh; 
                se->vruntime = vruntime; 
                return; 
            } 
        } 
#endif 
    } 
 
    /*为了保证虚拟时间不会反向减少,取个最大值*/ 
    se->vruntime = max_vruntime(se->vruntime, vruntime); 
}

place_entity的调用路径:

enqueue_entity //若参数 flags 包含 ENQUEUE_WAKEUP(唤醒任务后进行的enqueue) 时调用,传参(cfs_rq, se, 0) 
task_fork_fair //直接调用,传参(cfs_rq, se, 1) 
detach_task_cfs_rq //任务迁移出去时,若p不在cfs队列上且不在迁移中且不是新fork的任务,就调用,传参(cfs_rq, se, 0) 
    place_entity

place_entity 是主要的调度实体虚拟时间更新函数,在更新时会对其进行补偿。比如对于长时间休眠的线程唤醒后,在 cfs_rq->min_vruntime 的基础上再补偿一些一时间,有助于其及时被调度到。而对比刚 fork 出来的新任务,对其惩罚一定时间,可以避免其刚 fork 出来就立即被调度。

(3) 更新位置3

static void detach_task_cfs_rq(struct task_struct *p) 
{ 
    struct sched_entity *se = &p->se; 
    struct cfs_rq *cfs_rq = cfs_rq_of(se); 
 
    /*若p不在cfs队列上且不在迁移中且不是新fork的任务,就执行,应该就是对应sleep的线程*/ 
    if (!vruntime_normalized(p)) { 
        place_entity(cfs_rq, se, 0); 
        se->vruntime -= cfs_rq->min_vruntime; 
    } 
    detach_entity_cfs_rq(se); 
} 
 
static void attach_task_cfs_rq(struct task_struct *p) 
{ 
    struct sched_entity *se = &p->se; 
    struct cfs_rq *cfs_rq = cfs_rq_of(se); 
 
    attach_entity_cfs_rq(se); 
 
    if (!vruntime_normalized(p)) 
        se->vruntime += cfs_rq->min_vruntime; 
}

在CFS任务迁移的时候,虚拟时间保存的是差值,对于迁移的sleep状态的线程,先对其虚拟时间有补偿,然后求差值。

(4) 更新位置4

static void dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) 
{ 
    ... 
    if (!(flags & DEQUEUE_SLEEP)) //被抢占的或被迁移的 
        se->vruntime -= cfs_rq->min_vruntime; //计算差值,enqueue时再加上min_vruntime,注意这里保存的是相对值 
    ... 
} 
 
static void enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) 
{ 
    /* 此次enqueue不是唤醒 或 是迁移导致的enqueue*/ 
    bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATED); 
    bool curr = cfs_rq->curr == se; //等于 
 
    if (renorm && curr) 
        se->vruntime += cfs_rq->min_vruntime; 
 
    update_curr(cfs_rq); //里面有重新计算虚拟时间 
 
    if (renorm && !curr) 
        se->vruntime += cfs_rq->min_vruntime; 
    ... 
}

renorm的判断条件和上面 detach_task_cfs_rq/attach_task_cfs_rq 的互补,应该在 detach/attach 时不在cfs队列上且不在迁移中且不是新fork的任务就已经对 se->vruntime 做了差值和在新rq上恢复。也就是说正在运行或在cfs队列上或正在迁移的任务的虚拟时间的做差和恢复是在 dequeue_entity/enqueue_entity 中完成了。

(5) 更新位置5

move_queued_task //core.c 将一个已经queue的task移动到另一个队列中 
__migrate_swap_task //core.c 
detach_task //fair.c 将p->wake_cpu = dest_cpu; 
try_to_wake_up //task之前的cpu不等于新选出的cpu的时候执行,这里面唯一执行p->state = TASK_WAKING; 
    set_task_cpu //task之前运行的cpu和新选出的cpu不是同一个cpu的时候执行 
        fair_sched_class.migrate_task_rq 
            migrate_task_rq_fair 
                if (p->state == TASK_WAKING) { 
                    se->vruntime -= min_vruntime; //这里执行还是有个条件的 
                }

根据 if (p->state == TASK_WAKING) 判断后才赋值,这个是针对唤醒过程中发现选出的cpu不是task之前运行的cpu的时候执行,也就是说正在唤醒过程中迁移的任务的虚拟时间做差值是在 migrate_task_rq_fair 函数中执行的,应该也是在 enqueue_entity 加回来的。

(6) 更新位置6

fork_idle //fork.c 
_do_fork //fork.c 
    copy_process //fork.c 
        sched_fork //core.c 
            fair_sched_class.task_fork 
                task_fork_fair //fair.c 
                    se->vruntime -= cfs_rq->min_vruntime;

在 wake_up_new_task-->activate_task(rq, p, ENQUEUE_NOCLOCK) 传参 flag=ENQUEUE_NOCLOCK,对应在 enqueue_entity 中将 se->vruntime 差值加回来的。

3. se.sum_exec_runtime

update_curr //fair.c 
    curr->sum_exec_runtime += delta_exec; //delta_exec = now - curr->exec_start; 
 
update_curr_dl //deadline.c 
    curr->se.sum_exec_runtime += delta_exec; 
 
put_prev_task_stop //stop_task.c 
    curr->se.sum_exec_runtime += delta_exec; 
 
__sched_fork //core.c 
    p->se.sum_exec_runtime = 0; 
    p->se.prev_sum_exec_runtime = 0;

update_curr 的执行路径见对 1. se.exec_start 的说明,它通常和 se->prev_sum_exec_runtime 配合起来使用,如下:

set_next_task_fair 
pick_next_task_fair 
    set_next_entity //se->prev_sum_exec_runtime的唯一赋值位置 
 
static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) 
{ 
    ... 
    se->prev_sum_exec_runtime = se->sum_exec_runtime; 
}

当 se dequeue出去运行的时候,prev_sum_exec_runtime 记录 sum_exec_runtime 的值。而 sum_exec_runtime 在运行途中不停累加,此时任何时刻下做差值就可以得到任务运行的时间。

static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) //fair.c 
{ 
    ideal_runtime = sched_slice(cfs_rq, curr); //curr根据其权重分配的虚拟时间 
    delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; 
    if (delta_exec > ideal_runtime) { //时间片用完了就触发抢占 
        resched_curr(rq_of(cfs_rq)); //只是设置 TIF_NEED_RESCHED 标志,在抢占点到来时抢占 
        clear_buddies(cfs_rq, curr); 
        return; 
    } 
}

调用路径:

scheduler_tick 
    task_tick_fair 
        entity_tick //这里执行了 update_curr(cfs_rq),对 curr->sum_exec_runtime 进行了更新 
            check_preempt_tick

在 hrtick_start_fair 中也有使用:

static void hrtick_start_fair(struct rq *rq, struct task_struct *p) 
{ 
    struct sched_entity *se = &p->se; 
    struct cfs_rq *cfs_rq = cfs_rq_of(se); 
 
    if (rq->cfs.h_nr_running > 1) { 
        u64 slice = sched_slice(cfs_rq, se); 
        u64 ran = se->sum_exec_runtime - se->prev_sum_exec_runtime; 
        s64 delta = slice - ran; 
 
        if (delta < 0) { 
            if (rq->curr == p) 
                resched_curr(rq); //时间片使用完了,要被切走 
            return; 
        } 
        hrtick_start(rq, delta); 
    } 
}

此时 se->sum_exec_runtime - se->prev_sum_exec_runtime 差值就是任务从上次开始运行,到这次tick时所此次调度到运行的时间。单纯的 se->sum_exec_runtime 表示任务在测试时间段的运行的总时间。

4. se.nr_migrations

赋值位置:

void set_task_cpu(struct task_struct *p, unsigned int new_cpu) 
{ 
    if (task_cpu(p) != new_cpu) { 
        if (p->sched_class->migrate_task_rq) 
            //CFS的:做一些负载的加减和调频,然后将被迁移的进程设置为新cpu上的新进程。 
            p->sched_class->migrate_task_rq(p, new_cpu); //migrate_task_rq_fair 
        p->se.nr_migrations++; 
    } 
}

调用路径:

move_queued_task 
__migrate_swap_task 
try_to_wake_up //新cpu和原cpu不同时执行 
dl_task_offline_migration 
push_dl_task 
pull_dl_task 
detach_task 
push_rt_task 
pull_rt_task 
    set_task_cpu

se.nr_migrations 记录任务在不同CPU间迁移的次数。

5. se.statistics.sum_sleep_runtime

赋值位置:

static inline void update_stats_enqueue_sleeper(struct cfs_rq *cfs_rq, struct sched_entity *se) 
{ 
    struct task_struct *tsk = NULL; 
    u64 sleep_start, block_start; 
 
    if (!schedstat_enabled()) 
        return; 
 
    /*一个是sleep状态,一个是D状态*/ 
    sleep_start = schedstat_val(se->statistics.sleep_start); 
    block_start = schedstat_val(se->statistics.block_start); 
 
    if (entity_is_task(se)) 
        tsk = task_of(se); 
 
    /*上次是由于任务是 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 而进行的dequeue时赋值的 */  
    if (sleep_start) { 
        u64 delta = rq_clock(rq_of(cfs_rq)) - sleep_start; 
        if ((s64)delta < 0) 
            delta = 0; 
 
        if (unlikely(delta > schedstat_val(se->statistics.sleep_max))) 
            __schedstat_set(se->statistics.sleep_max, delta); //sleep_max是单次sleep的最大值 
 
        __schedstat_set(se->statistics.sleep_start, 0); //这里又将sleep_start设置为0了,确保 sleep_start 不为0的时候是 TASK_INTERRUPTIBLE 的睡眠状态进来的 
        __schedstat_add(se->statistics.sum_sleep_runtime, delta); //无论是block的还是sleep的,都加到这里了 
        ... 
    } 
 
    if (block_start) { 
        u64 delta = rq_clock(rq_of(cfs_rq)) - block_start; 
        if ((s64)delta < 0) 
            delta = 0; 
 
        if (unlikely(delta > schedstat_val(se->statistics.block_max))) 
            __schedstat_set(se->statistics.block_max, delta); //block_max是单次sleep的最大值 
 
        __schedstat_set(se->statistics.block_start, 0); //这里又将block_start设置为0了 
        __schedstat_add(se->statistics.sum_sleep_runtime, delta); //无论是block的还是sleep的,都加到这里了 
 
        if (tsk) { 
            if (tsk->in_iowait) { 
                __schedstat_add(se->statistics.iowait_sum, delta); //记录iowait的总时间 
                __schedstat_inc(se->statistics.iowait_count); //记录iowait的次数 
                ... 
            } 
 
            /*"comm=%s pid=%d delay=%Lu [ns]" 可以trace blcok的时间*/ 
            trace_sched_stat_blocked(tsk, delta); 
            /*"pid=%d iowait=%d caller=%pS" 能监控D状态,那么也能写在sleep状态那里*/ 
            trace_sched_blocked_reason(tsk); 
        } 
    } 
}

调用路径:

enqueue_entity 
    update_stats_enqueue //只有if (flags & ENQUEUE_WAKEUP) 成立才执行,也就是唤醒导致的enqueue才执行 
        update_stats_enqueue_sleeper //fair.c 记录任务 sleep 和 D状态 的时间

(1) sleep_start 和 block_start 的更新位置

static inline void update_stats_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) 
{ 
    if (se != cfs_rq->curr) //出队,迁移,设置优先级,调度策略,都会走这里 
        update_stats_wait_end(cfs_rq, se); 
 
    //被throttle的时候会传flags=DEQUEUE_SLEEP, 还有: 
    if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) { 
        struct task_struct *tsk = task_of(se); 
 
        if (tsk->state & TASK_INTERRUPTIBLE) 
            __schedstat_set(se->statistics.sleep_start, rq_clock(rq_of(cfs_rq))); 
        if (tsk->state & TASK_UNINTERRUPTIBLE) 
            __schedstat_set(se->statistics.block_start, rq_clock(rq_of(cfs_rq))); 
    } 
}

schedule()中非抢占是由于 sleep/block 而休眠切走的任务 flags 中包含 DEQUEUE_SLEEP,若是此时任务是 INTERRUPTIBLE 的就记录在 sleep_start 上,若是 UNINTERRUPTIBLE 的,就记录 block_start 上。

调用路径:

update_stats_dequeue  
dequeue_entity 
    update_stats_dequeue

说明:开始记录的时间点是任务任务休眠,结束记录的时间点是任务被唤醒入队列时,也就是说 se->statistics.sum_sleep_runtime 统计的是任务测试时间段内 sleep 和 block 两种休眠状态的的时长之和。

(2) tsk->in_iowait 的标记位置

int io_schedule_prepare(void) 
{ 
    current->in_iowait = 1; 
} 
void io_schedule_finish(int token) 
{ 
    current->in_iowait = token; 
}

调用路径:

blkcg_maybe_throttle_blkg //blk-cgroup.c 先调用prepare,处理完后调用finish设置回原来的状态 
io_schedule_timeout //core.c 先调用prepare,处理完后调用finish设置回原来的状态 
io_schedule //core.c 先调用prepare,然后将任务切走,处理完后调用finish设置回原来的状态 
mutex_lock_io_nested //mutex.c 还有一个mutex! 
mutex_lock_io 
    io_schedule_prepare 
    ... 
    io_schedule_finish

从 TASK_UNINTERRUPTIBLE 状态唤醒 enqueue 时(iowait是在D状态里面),若发现 tsk->in_iowait 是被设置的,se->statistics.iowait_sum 记录的是 iowait 导致休眠的时间之和,记录 iowait次数的 se->statistics.iowait_count 也加1.

6. se.statistics.wait_start

赋值位置:

static inline void update_stats_wait_start(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 
{ 
    u64 wait_start, prev_wait_start; 
 
    if (!schedstat_enabled()) 
        return; 
 
    wait_start = rq_clock(rq_of(cfs_rq)); 
    prev_wait_start = schedstat_val(se->statistics.wait_start); 
 
    //若是在迁移中,se->statistics.wait_start 保存的也是差值 
    if (entity_is_task(se) && task_on_rq_migrating(task_of(se)) && likely(wait_start > prev_wait_start)) 
        wait_start -= prev_wait_start; //减法 
 
    __schedstat_set(se->statistics.wait_start, wait_start); 
} 
 
static inline void update_stats_wait_end(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 
{ 
    struct task_struct *p; 
    u64 delta; 
 
    delta = rq_clock(rq_of(cfs_rq)) - schedstat_val(se->statistics.wait_start); //return rq->clock; 
 
    if (entity_is_task(se)) { 
        p = task_of(se); 
        if (task_on_rq_migrating(p)) { //判断:p->on_rq == TASK_ON_RQ_MIGRATING; 
            __schedstat_set(se->statistics.wait_start, delta); //若是迁移更新的是start,然后return了。 
            return; 
        } 
        trace_sched_stat_wait(p, delta); 
    } 
 
    __schedstat_set(se->statistics.wait_max, max(schedstat_val(se->statistics.wait_max), delta)); 
    __schedstat_inc(se->statistics.wait_count); 
    __schedstat_add(se->statistics.wait_sum, delta); 
    __schedstat_set(se->statistics.wait_start, 0); //这里做了将 wait_start 清0 
} 
 
void normalize_rt_tasks(void) //core.c 
{ 
    ... 
    p->se.exec_start = 0; 
    schedstat_set(p->se.statistics.wait_start, 0); 
    schedstat_set(p->se.statistics.sleep_start, 0); 
    schedstat_set(p->se.statistics.block_start, 0); 
    ... 
}

调用路径:

pick_next_task_fair 
put_prev_task_fair 
    put_prev_entity //se->on_rq才执行 
enqueue_entity 
    update_stats_enqueue //se != cfs_rq->curr 才执行 
        update_stats_wait_start 
 
pick_next_task_fair 
set_next_task_fair 
    set_next_entity //se->on_rq才执行 
dequeue_entity 
    update_stats_dequeue //se != cfs_rq->curr 才执行 
        update_stats_wait_end 
 
    sysrq_unrt_op.handler //sysrq_key_table[]中的成员 
        sysrq_handle_unrt 
            normalize_rt_tasks //core.c 这个函数将所有CFS、RT、DL 任务都设置为优先级为120的CFS任务。

可以看出来,start 的记录时间是 enqueue 时,end 的记录时间是 dequeue 时。而 sysrq_handle_unrt 的作用是可以一键让系统没有RT任务,全部变成优先级为120的CFS任务,这里不重点关注。因此可以看出 se->statistics.wait_start 记录的是 enqueue 到 cfs队列上的时间点,其次,若此任务是 dequeue 状态,其 wait_start 就是0,enqueue等待状态,其 wait_start 就不为0。se->statistics.wait_max 记录的是在 cfs_rq上等待的最大一次时间。se->statistics.wait_count 记录的是 enqueue 到 cfs 队列上进行等待的次数,se->statistics.wait_sum 记录的是任务在cfs队列上等待的总时间。

7. se.statistics.sleep_start

标记由于sleep而dequeue的时间点,见“5. se.statistics.sum_sleep_runtime”中的分析。

8. se.statistics.block_start

标记由于block而dequeue的时间点,见“5. se.statistics.sum_sleep_runtime”中的分析。

9. se.statistics.sleep_max

标记单次sleep的最大时间间隔,见“5. se.statistics.sum_sleep_runtime”中的分析。

10. se.statistics.block_max

标记单次block的最大时间间隔,见“5. se.statistics.sum_sleep_runtime”中的分析。

11. se.statistics.exec_max

赋值位置:

static void update_curr(struct cfs_rq *cfs_rq) //fair.c 
{ 
    delta_exec = now - curr->exec_start; 
    curr->exec_start = now; //更新exec_start时间 
    schedstat_set(curr->statistics.exec_max, max(delta_exec, curr->statistics.exec_max)); 
}

exec_max 表示两次事件之间任务执行的最大时长,update_curr 的调用路径分析见“1. se.exec_start”,由于 entity_tick 中调用了,因此 exec_max 顶破天最大也只能是一个tick的大小,250Hz下就是4ms.

12. se.statistics.slice_max

赋值位置:

static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 只有CFS线程这个域有意义 
{ 
    if (schedstat_enabled() && rq_of(cfs_rq)->cfs.load.weight >= 2*se->load.weight) { //奇怪,为啥权重高就不更新统计了? 
        schedstat_set(se->statistics.slice_max, max((u64)schedstat_val(se->statistics.slice_max), se->sum_exec_runtime - se->prev_sum_exec_runtime)); 
    } 
    se->prev_sum_exec_runtime = se->sum_exec_runtime; //要运行时保存一下运行前的统计结果 
}

调用路径:

pick_next_task_fair 
set_next_task_fair 
    set_next_entity

slice_max 表示任务单次运行的最大物理时长,单位ms,这个倒不受 tick 的影响,可以是一个很大的值。

13. se.statistics.wait_max

记录的是在cfs_rq上等待的最大一次时间,见“6. se.statistics.wait_start”

14. se.statistics.wait_sum

记录的是任务在cfs队列上等待的总时间,见“6. se.statistics.wait_start”

15. se.statistics.wait_count

记录的是enqueue到cfs队列上进行等待的次数,见“6. se.statistics.wait_start”

16. se.statistics.iowait_sum

记录由于 iowait 而导致任务休眠时间总和,见“5. se.statistics.sum_sleep_runtime”中的分析。

17. se.statistics.iowait_count

记录由于 iowait 而导致任务休眠的次数,见“5. se.statistics.sum_sleep_runtime”中的分析。

18. se.statistics.nr_migrations_cold

Qcom 5.4内核中没有使用到这个成员

19. se.statistics.nr_failed_migrations_affine

赋值位置:

static int can_migrate_task(struct task_struct *p, struct lb_env *env) //fair.c 
{ 
    ... 
    if (!cpumask_test_cpu(env->dst_cpu, p->cpus_ptr)) { 
        schedstat_inc(p->se.statistics.nr_failed_migrations_affine); 
    } 
    ... 
    /*不对正在运行的任务进行迁移*/ 
    if (task_running(env->src_rq, p)) { //return p->on_cpu; 
        schedstat_inc(p->se.statistics.nr_failed_migrations_running); 
        return 0; 
    } 
    ... 
    /* 
     * Aggressive migration if:侵略性迁徙 
     * 1) IDLE or NEWLY_IDLE balance. 
     * 2) destination numa is preferred 
     * 3) task is cache cold, or 
     * 4) too many balance attempts have failed. 
     */ 
    //p在原cpu上即将被调度到(已经设置为next或last buddy)或运行时间小于迁移阈值0.5ms,就是task_hot 
    tsk_cache_hot = task_hot(p, env); 
    if (env->idle != CPU_NOT_IDLE || tsk_cache_hot <= 0 || env->sd->nr_balance_failed > env->sd->cache_nice_tries) { 
        if (tsk_cache_hot == 1) { 
            schedstat_inc(p->se.statistics.nr_forced_migrations); 
        } 
        return 1; 
    } 
 
    schedstat_inc(p->se.statistics.nr_failed_migrations_hot); 
}

调用路径:

load_balance //busiest->nr_running > 1 才执行 
    detach_tasks 
active_load_balance_cpu_stop 
    detach_one_task  
        can_migrate_task //只有此函数判断可以迁移时才会迁移

nr_failed_migrations_affine 表示在迁移时,由于此任务的cpu亲和性设置导致的中止迁移的次数。若执行”taskset -p 01 <pid>“ 将一个死循环绑定在小核上,将观察的非常清楚。若是没有设置亲和性的话,此域一般是0.


20. se.statistics.nr_failed_migrations_running

迁移此任务时,若发现此任务正在运行而中止迁移的计数,也就是不对正在运行的任务进行迁移,设置位置和调用路径见”19. se.statistics.nr_failed_migrations_affine“。

21. se.statistics.nr_failed_migrations_hot

迁移此任务时,若发现此任务在原cpu上即将被调度到(已经设置为next或last buddy)或运行时间小于迁移阈值0.5ms,就是task_hot。若迁移失败次数比较多的话也会迁移

cache_hot的任务。这里是记录由于cache_hot而放弃迁移此任务的计数。设置位置和调用路径见”19. se.statistics.nr_failed_migrations_affine“。

22. se.statistics.nr_forced_migrations

迁移此任务时,虽然此任务是 tsk_cache_hot 的,但是仍然要对其进行迁移的次数。设置位置和调用路径见”19. se.statistics.nr_failed_migrations_affine“。

23. se.statistics.nr_wakeups

赋值位置:

static void ttwu_stat(struct task_struct *p, int cpu, int wake_flags) //core.c 
{ 
    struct rq *rq = this_rq(); 
 
#ifdef CONFIG_SMP 
    if (cpu == rq->cpu) { 
        __schedstat_inc(rq->ttwu_local); //此成员没有在debug.c中进行打印 
        __schedstat_inc(p->se.statistics.nr_wakeups_local); 
    } else { 
        struct sched_domain *sd; 
        __schedstat_inc(p->se.statistics.nr_wakeups_remote); 
        rcu_read_lock(); 
        for_each_domain(rq->cpu, sd) { 
            if (cpumask_test_cpu(cpu, sched_domain_span(sd))) { 
                __schedstat_inc(sd->ttwu_wake_remote); //此成员没有在debug.c中进行打印 
                break; 
            } 
        } 
        rcu_read_unlock(); 
    } 
 
    if (wake_flags & WF_MIGRATED) //WF_MIGRATED: Internal use, task got migrated 
        __schedstat_inc(p->se.statistics.nr_wakeups_migrate); 
#endif /* CONFIG_SMP */ 
 
    __schedstat_inc(rq->ttwu_count); //此成员没有在debug.c中进行打印 
    __schedstat_inc(p->se.statistics.nr_wakeups); 
 
    if (wake_flags & WF_SYNC) //WF_SYNC: Waker goes to sleep after wakeup 
        __schedstat_inc(p->se.statistics.nr_wakeups_sync); 
}

调用路径:

wake_up_q //传参(task, TASK_NORMAL, 0, head->count) wake_flags=0,各种锁、进程间通信机制的唤醒 
wake_up_process //传参(p, state, 0, 1) wake_flags=0,各种驱动中使用,只有一个参数task_truct使用简单方便 
wake_up_state //传参(p, state, 0, 1) wake_flags=0,参数p和state,kernel部分子系统核心实现代码使用 
default_wake_function //传参(curr->private, mode, wake_flags, 1) 叫default的原因是wait机制使用就是这个函数来唤醒的,见 __WAITQUEUE_INITIALIZER 和 init_waitqueue_entry 
    try_to_wake_up 
        ttwu_stat

24. se.statistics.nr_wakeups_sync

唤醒任务时,若任务唤醒函数传参 wake_flags 中包含 WF_SYNC(=1,表示唤醒者唤醒被唤醒者后睡眠)标志就加1,只有wait机制的默认实现使用的唤醒函数可能传这个标志。cat shced节点看 nr_wakeups_sync 的计数值很大,但是内核中却没找到哪里使用了这个flag,比较奇怪, 设置位置和调用路径见”23. se.statistics.nr_wakeups“。

25. se.statistics.nr_wakeups_migrate

唤醒任务时,若任务唤醒函数传参 wake_flags 中包含 WF_MIGRATED 就加1,表示唤醒一个迁移过来的任务。设置位置和调用路径见”23. se.statistics.nr_wakeups“。

26. se.statistics.nr_wakeups_local

唤醒任务时,若任务唤醒在当前CPU上就加1。设置位置和调用路径见”23. se.statistics.nr_wakeups“。

27. se.statistics.nr_wakeups_remote

唤醒任务时,若任务不是唤醒在当前CPU上就加1。设置位置和调用路径见”23. se.statistics.nr_wakeups“。


28. se.statistics.nr_wakeups_affine

设置位置:

static int wake_affine(struct sched_domain *sd, struct task_struct *p, int this_cpu, int prev_cpu, int sync) 
{ 
    int target = nr_cpumask_bits; 
 
    if (sched_feat(WA_IDLE)) 
        target = wake_affine_idle(this_cpu, prev_cpu, sync); 
 
    if (sched_feat(WA_WEIGHT) && target == nr_cpumask_bits) 
        target = wake_affine_weight(sd, p, this_cpu, prev_cpu, sync); 
 
    schedstat_inc(p->se.statistics.nr_wakeups_affine_attempts); //无条件加1 
    if (target == nr_cpumask_bits) 
        return prev_cpu; 
 
    schedstat_inc(sd->ttwu_move_affine); 
    schedstat_inc(p->se.statistics.nr_wakeups_affine); //任务有亲和性的唤醒才加1 
    return target; 
}

调用路径:

select_task_rq_fair 
    for_each_domain(cpu, tmp) { 
        if (want_affine && (tmp->flags & SD_WAKE_AFFINE) && cpumask_test_cpu(prev_cpu, sched_domain_span(tmp))) { 
        if (cpu != prev_cpu) 
            new_cpu = wake_affine(tmp, p, cpu, prev_cpu, sync); 
    }

调用条件苛刻,基本上这个域没有计数值,参考 WAKE_AFFINE 机制

29. se.statistics.nr_wakeups_affine_attempts

调用条件苛刻,基本上这个域没有计数值。设置位置和调用路径见”28. se.statistics.nr_wakeups_affine“。


30. se.statistics.nr_wakeups_passive

Qcom Linux5.4没有使用此成员

31. se.statistics.nr_wakeups_idle

Qcom Linux5.4没有使用此成员


32. avg_atom

此成员是上面 proc_sched_show_task()打印函数中直接设置的,取值为 avg_atom = div64_ul(p->se.sum_exec_runtime, nr_switches),两个参数也都是打印出来的成员。表示任务平均每次运行的实际时间。使用cat /proc/pid/sched的出来的 se.sum_exec_runtime / nr_switches 刚好等于 avg_atom

33. avg_per_cpu

此成员是上面 proc_sched_show_task()打印函数中直接设置的,取值为 avg_per_cpu = div64_u64(p->se.sum_exec_runtime, p->se.nr_migrations),两个参数也都是打印出来的成员。注意除的是迁移次数,而不是cpu个数。表示平均每次迁移在一个cpu上运行的平均时间。

34. nr_switches

此成员是上面 proc_sched_show_task()打印函数中计算出来的,取值为 nr_switches = p->nvcsw + p->nivcsw;

(1) 设置位置1

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk) 
{ 
    ... 
    tsk->nvcsw = tsk->nivcsw = 0; 
#ifdef CONFIG_DETECT_HUNG_TASK //默认不使能 
    tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw; 
    tsk->last_switch_time = 0; 
#endif 
    ... 
}

调用路径:

_do_fork 
    copy_process 
        copy_mm //fork.c

(2) 设置位置2

static void __sched notrace __schedule(bool preempt) 
{ 
    ... 
    switch_count = &prev->nivcsw; /*首先指向nivcsw*/ 
 
    if (!preempt && prev->state) { 
        ... 
        switch_count = &prev->nvcsw; /*非抢占式的切换改为指向nvcsw*/ 
    } 
    ... 
    if (likely(prev != next)) { 
        ++*switch_count; /*只有选中的 prev != next 时才计数*/ 
    } 
}

在任务切换时,若 prev 任务是主动休眠导致的任务切换,prev->nvcsw 计数加1,若 prev 是被抢占而发生的任务切换,prev->nivcsw 计数加1。nr_switches 表示发生任务切换的次数,nvcsw 表示非抢占任务被切走的次数,nivcsw 表示发生抢占任务被切走的次数,这里说的任务切换不包括切换后还是自己的情况。

35. nr_voluntary_switches

取值为 p->nvcsw,表示非抢占任务被切走的次数。见“34. nr_switches”

36. nr_involuntary_switches

取值为 p->nivcsw,表示被抢占而导致的任务被切走的次数。见“34. nr_switches”

37. se.load.weight

(1)设置位置1:

static inline void update_load_add(struct load_weight *lw, unsigned long inc) //fair.c 
{ 
    lw->weight += inc; 
    lw->inv_weight = 0; 
}

调用路径:

sched_fork 
set_user_nice //设置任务的优先级时会更改其权重,进而应该cfs_rq的权重 
__setscheduler_params 
    set_load_weight 
        reweight_task 
        update_cfs_group //fair group sched 中与 shares 对比 
            reweight_entity //if (se->on_rq) 才需要dequeue后设置后再enqueue,这种情况下才需要设置cfs_rq的weight 
        unthrottle_cfs_rq //ENQUEUE_WAKEUP 
        enqueue_task_fair 
            enqueue_entity //enqueue时将se->load的权重加到cfs_rq->load上 
                account_entity_enqueue //无条件执行 
scheduler_tick 
    task_tick_fair 
        entity_tick 
            check_preempt_tick //在每个tick的流程中都会计算任务的理想运行时间(但没改变任务的虚拟时间),运行超出了会触发任务切换 
            hrtick_start_fair 
        sched_rr_get_interval //系统调用 
            get_rr_interval_fair 
                sched_slice //if (!se->on_rq) 才设置,因为不在cfs_rq上的任务其权重不包含在cfs_rq->load内 
                    update_load_add

check_preempt_tick 中调用 sched_slice 计算的只是一个理想的运行时间,但是并没有对任何成员赋值。正在运行的任务每次被tick命中都会判断其运行时间是否超过了理想的分配时间,若超过了,则触发重新调度。注意,由于curr每次被tick命中时cfs_rq上的任务的数量和优先级不同,每次计算出的理想时间也不同,但是只要curr一直在运行,其单次运行时间 delta_exec 就是一直增加的,因此运行时间越长的任务越容易在tick中被触发抢占。

(2)设置位置2:

static inline void update_load_sub(struct load_weight *lw, unsigned long dec) //fair.c 
{ 
    lw->weight -= dec; 
    lw->inv_weight = 0; 
}

调用路径:

reweight_entity //设置优先级导致reweight,若是在队列上,dequeue --> set --> enqueue,这里是sub cfs_rq的weight 
dequeue_entity 
    account_entity_dequeue 
        update_load_sub

(3)设置位置3:

static inline void update_load_set(struct load_weight *lw, unsigned long w) //fair.c 
{ 
    lw->weight = w; 
    lw->inv_weight = 0; 
}

调用路径:

sched_create_group 
    alloc_fair_sched_group //cfs组调度,对每一个cpu都执行 
    sched_init //会创建一个root_task_group,传参rq->cfs_rq 
        init_tg_cfs_entry 
        reweight_entity //调用路径上面有 
            update_load_set

se.load.weight 表示CFS任务的权重,和其优先级挂钩,优先级变化了也会设置。同事cfs_rq也有自己的load和权重,为其上queue的任务的权重之和,若是要设置一个已经enqueue到队列上的任务,需要先dequeue下来,设置后再enqueue回去。

38. se.runnable_weight

(1)设置位置1

void init_entity_runnable_average(struct sched_entity *se) //fair.c 
{ 
    /*等效:*/ 
    sa->runnable_load_avg = sa->load_avg = scale_load_down(se->load.weight); //se->load.weight >> 10 
    se->runnable_weight = se->load.weight; 
}

调用路径:

sched_create_group 
    alloc_fair_sched_group //fair group sched使用 
        init_entity_runnable_average 
 
static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, unsigned long weight, unsigned long runnable) //fair.c 
{ 
    ... 
    se->runnable_weight = runnable; 
    ... 
}
update_cfs_group //fair group sched使用 
reweight_task //传参 runnable 就是参数 weight,为 scale_load(sched_prio_to_weight[prio]),调用路径见“37. se.load.weight” 
    reweight_entity

(2)设置位置2

static void set_load_weight(struct task_struct *p, bool update_load) //core.c 
{ 
    /*SCHED_IDLE tasks get minimal weight:*/ 
    if (task_has_idle_policy(p)) { 
        load->weight = scale_load(WEIGHT_IDLEPRIO); //3<<10,3比139优先级的15还小,weight也需要scale! 
        load->inv_weight = WMULT_IDLEPRIO; //0x55555555 = 2^32 / 3 是最大的,比139的inv还大 
        p->se.runnable_weight = load->weight; 
        return; 
    } 
    /*非idle等效:*/ 
    p->se.runnable_weight = load->weight; 
}

调用路径:

sched_fork 
set_user_nice //设置任务的优先级时会更改其权重,进而应该cfs_rq的权重 
__setscheduler_params 
    set_load_weight

此外,还有cfs_rq.runnable_weight

(1) 赋值位置1:

static inline void dequeue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 
{ 
    cfs_rq->runnable_weight -= se->runnable_weight; 
    ... 
}

调用路径:

reweight_entity //设置优先级时先dequeue--> set --> enqueue 
dequeue_entity 
    dequeue_runnable_load_avg

(2) 赋值位置2

static inline void enqueue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 
{ 
    cfs_rq->runnable_weight += se->runnable_weight; 
    ... 
}

调用路径:

reweight_entity //设置优先级时先dequeue--> set --> enqueue 
enqueue_entity 
    enqueue_runnable_load_avg

若是没有使能 CONFIG_FAIR_GROUP_SCHED,se.runnable_weight 就等于 se.load.weight。

39. se.avg.load_sum

(1) 设置位置1:

//使能 CONFIG_FAIR_GROUP_SCHED 才存在 
static inline void update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq) //fair.c 
{ 
    ... 
    se->avg.load_sum = runnable_sum; 
    se->avg.load_avg = load_avg; 
    ... 
}

调用路径:

enqueue_entity 
dequeue_entity 
set_next_entity 
put_prev_entity 
entity_tick 
enqueue_task_fair 
dequeue_task_fair 
__update_blocked_fair 
propagate_entity_cfs_rq 
detach_entity_cfs_rq 
attach_entity_cfs_rq 
sched_group_set_shares 
    update_load_avg //若没有使能 CONFIG_FAIR_GROUP_SCHED 就是只是去调频 
        propagate_entity_load_avg 
            update_tg_cfs_runnable

(2) 设置位置2

static void attach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) 
{ 
    u32 divider = LOAD_AVG_MAX - 1024 + cfs_rq->avg.period_contrib; 
    ... 
    se->avg.load_sum = divider; 
    if (se_weight(se)) { 
        se->avg.load_sum = div_u64(se->avg.load_avg * se->avg.load_sum, se_weight(se)); 
    } 
    se->avg.runnable_load_sum = se->avg.load_sum; 
    ... 
}

调用路径:

update_load_avg 
attach_entity_cfs_rq 
    attach_entity_load_avg

(3) 此外,还有cfs_rq的:

static inline void enqueue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) 
{ 
    cfs_rq->avg.load_avg += se->avg.load_avg; 
    cfs_rq->avg.load_sum += se_weight(se) * se->avg.load_sum; 
} 
 
static inline void dequeue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) 
{ 
    sub_positive(&cfs_rq->avg.load_avg, se->avg.load_avg); 
    sub_positive(&cfs_rq->avg.load_sum, se_weight(se) * se->avg.load_sum); 
}

PELT算法中在 load_sum 就是运行时间乘以权重,衰减累加和,PELT在 accumulate_sum() 中计算负载,见 https://www.cnblogs.com/hellokitty2/p/15335189.html

40. se.avg.runnable_load_sum

对于调度实体,runnable_load_sum 等于 load_sum,对于 group se 两者有区别。

41. se.avg.util_sum

PELT算法下,为scale_up后的运行时间的衰减累加值,与优先级无关,只与delta时间有关,其最终会趋向一个定值:1024 * 47742 = 48887808。


42. se.avg.load_avg

平均负载,若任务一直跑,就会接近其优先级对应的权重值。这里的跑的时间为runnable+running.

43. se.avg.runnable_load_avg

runnable状态的平均负载,对于调度实体和 load_avg 是一致的


44. se.avg.util_avg

PELT算法下,为 running% * 1024,是一个比值,只包含running的时间,通常使用它作为负载来触发调频。


45. se.avg.last_update_time

PELT算法下,负载统计基于的时间点,通常 delta = now - last_update_time,然后拿 delta 去更新负载。


46. se.avg.util_est.ewma

struct util_est 结构体定义位置对此成员的解释:任务的指数加权移动平均 (EWMA) 利用率, 支持数据结构以跟踪 FAIR 任务利用率的指数加权移动平均值 (EWMA)。每次任务完成唤醒时,都会将新样本添加到移动平均值中。选择样本的权重以使 EWMA 对任务工作负载的瞬态变化相对不敏感。


47. se.avg.util_est.enqueued

struct util_est 结构体定义位置对此成员的解释:
enqueued 属性对于 tasks 和 cpus 的含义略有不同:
- task:上次任务出队时任务的 util_avg
- cfs_rq:该 CPU 上每个 RUNNABLE 任务的 util_est.enqueued 总和。因此,任务(非cfs_rq)的 util_est.enqueued 表示该任务当前排队的 CPU 估计利用率的贡献。仅对于我们跟踪过去瞬时估计利用率的移动平均值的任务。这允许吸收其他周期性任务的利用率的零星下降。

48. policy

来自 task_truct 的 policy 成员,表示进程的调度策略,取值如下

#define SCHED_NORMAL    0 //CFS 
#define SCHED_FIFO        1 //RT 
#define SCHED_RR        2 //RT 
#define SCHED_BATCH        3 //CFS 
#define SCHED_IDLE        5 //CFS 
#define SCHED_DEADLINE    6 //DL

49. prio

来自 task_truct 的 prio 成员,表示进程的优先级,RT: 0-99,CFS: 100-139,数值越小优先级越高。

50. clock-delta

这个值是执行 cpu_clock() 的耗时,是记录一次读取CPU时间需要的时长,涉及到读取硬件,测试发现和 CPU 频点高低无线性关系。

51. 5.10内核中会打印uclamp的值

# cat /proc/1/sched 
uclamp.min                                   :                    0 
uclamp.max                                   :                 1024 
effective uclamp.min                         :                    0 
effective uclamp.max                         :                 1024

对应代码:

//kernel/sched/debug.c 
void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, struct seq_file *m) 
{ 
    ... 
    #ifdef CONFIG_UCLAMP_TASK 
    __PS("uclamp.min", p->uclamp_req[UCLAMP_MIN].value); 
    __PS("uclamp.max", p->uclamp_req[UCLAMP_MAX].value); 
    __PS("effective uclamp.min", uclamp_eff_value(p, UCLAMP_MIN)); 
    __PS("effective uclamp.max", uclamp_eff_value(p, UCLAMP_MAX)); 
    #endif 
    ... 
} 
 
unsigned long uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id) 
{ 
    struct uclamp_se uc_eff; 
 
    /* Task currently refcounted: use back-annotated (effective) value */ 
    /*se 当前在 rq 的存储桶中被引用计数的话,active就为1 */ 
    if (p->uclamp[clamp_id].active) 
        return (unsigned long)p->uclamp[clamp_id].value; 
 
    /* 
     * 默认 uclamp_eff_get() 是返回 uc_req(p->uclamp_req[clamp_id]与task_group(p)->uclamp[clamp_id] 
     * 二者之间value较小的那个) 和 uc_max(uclamp_default[clamp_id]) 再比一次,二者之间较小的那个。 
     */ 
    uc_eff = uclamp_eff_get(p, clamp_id); 
 
    return (unsigned long)uc_eff.value; 
}

uclamp.min 和 uclamp.max 直接来自 p->uclamp_req[UCLAMP_MIN].value 和 p->uclamp_req[UCLAMP_MAX].value。effective uclamp.min 和 effective uclamp.max 应该表示正在被引用的值。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/15360115.html