• 调度器11—调度相关trace汇总—MTK


    基于MTK Linux-4.14

    1. sched_util ——负载调频

    打印:

    RenderThread-2837  [006] d.h2  8163.520041: sched_util: cid=1 next=1624000 last_freq_update_time=8163524121923

    形参:

    TP_PROTO(int cid, unsigned int next_freq, u64 time),

    打印解析:
    分别为三个传入参数

    调用路径:

    enqueue_entity //fair.c 无条件条调用
    dequeue_entity //fair.c 无条件条调用
    set_next_entity //fair.c 只有se->on_rq才调用,选一个cfs任务去运行
    put_prev_entity //fair.c 只有se->on_rq才调用,取消一个在运行的cfs任务
    entity_tick //fair.c 在scheduler_tick()中对正在运行的任务以Hz为频度更新负载
    enqueue_task_fair //fair.c 无条件调用
    dequeue_task_fair //fair.c 无条件调用
    update_blocked_averages //fair.c 负载均衡路径
    propagate_entity_cfs_rq //fair.c fair group sched里面的
    detach_entity_cfs_rq //fair.c 无条件条调用
    attach_entity_cfs_rq //fair.c 无条件条调用
    sched_group_set_shares //fair.c fair group sched cgroup分组
        update_load_avg //fair.c
    update_sd_lb_stats //fair.c 负载均衡路径
    idle_balance //fair.c
    nohz_idle_balance //fair.c
    run_rebalance_domains //fair.c
        update_blocked_averages //fair.c
            update_cfs_rq_load_avg //fair.c 更新负载后会触发调频
            attach_entity_load_avg //fair.c 将一个se的load添加到cfs_rq上后会触发调频
            detach_entity_load_avg //fair.c 将一个se的load从cfs_rq上移除后会触发调频
            enqueue_task_fair //fair.c 向一个idle的cpu中挂入一个任务会触发提频
                cfs_rq_util_change //fair.c 传参(rq, 0)
                update_curr_dl //deadline.c DL任务也会触发调频,传参(rq, SCHED_CPUFREQ_DL) 原语上说只在CFS任务中触发调频,担心CFS任务不能及时运行而不能及时提频。
                update_curr_rt //rt.c rt任务无条件触发提频,传参(rq, SCHED_CPUFREQ_RT)
                enqueue_task_fair //fair.c enqueue一个从iowait退出的任务会触发提频,传参(rq, SCHED_CPUFREQ_IOWAIT)
                    cpufreq_update_util //sched.h sugov_start()中将下面两个函数的指针放到per-cpu的cpufreq_update_util_data中,若policy中只有一个cpu放的才是sugov_update_single
                        sugov_update_single //cpufreq_schedutil.c 只有单核的cluster使用这个
                        sugov_update_shared //cpufreq_schedutil.c 两者传参一样
                            sugov_update_commit
                                trace_sched_util(cid, next_freq, time) //mtk加的

    说明:

    看名字是trace util的,但是实际上是trace cluster 频点设置的

    调用传参:
    见 调用路径

    实参解析:

    cid: 
        为 cpu_topology.cluster_id 表示对哪个cluster调频
    next_freq:
        先赋值给 cpufreq_policy.cur,,表示要设置的频率
    time:
        频点最后一次更新的时间戳。TODO: 应该可以和update limit限制配合来看
    
    总共有三个,另两个是 sched_util_est_task 和 sched_util_est_cpu

    2. sched_util_est_cpu ——cfs_rq的预估负载

    打印:

    <idle>-0     [005] d.H2  8163.520044: sched_util_est_cpu: cpu=5 util_avg=364 util_est_enqueued=359

    形参:

    TP_PROTO(int cpu, struct cfs_rq *cfs_rq),

    打印解析:

    cpu:参数cpu
    util_avg:取自 cfs_rq->avg.util_avg
    util_est_enqueued:取自 cfs_rq->avg.util_est.enqueued

    调用路径:

    //<1>
    update_load_avg //fair.c 调用路径见对 sched_util 的解析
        __update_load_avg_se //若是定义了 UTIL_EST_DEBUG 且 se 是task才会调用
            trace_sched_util_est_cpu(cpu, cfs_rq);
    
    //<2>
    enqueue_task_fair //fair.c
        util_est_enqueue //fair.c 在更新schedutil freq前,简洁的增加util
            trace_sched_util_est_cpu(cpu_of(rq_of(cfs_rq)), cfs_rq);
    
    //<3>
    dequeue_task_fair //fair.c
        util_est_dequeue
            trace_sched_util_est_cpu(cpu_of(rq_of(cfs_rq)), cfs_rq);

    说明:
    在任务enqueue、dequeue、和负载更新 中 trace cpu 的 util_avg 和 util_est.enqueued,

    调用传参:
    cpu和其上的cfs_rq

    实参解析:
    见调用路径

    补充:

    struct util_est 注释:
    enqueued 属性对于 tasks 和 cpus 的含义略有不同:
        - task:上次任务出队时任务的 util_avg
        - cfs_rq:该 CPU 上每个 RUNNABLE 任务的 util_est.enqueued 总和。因此,任务(非cfs_rq)的 util_est.enqueued 表示该任务对当前排队的 CPU 估计利用率的贡献。

    一个任务的util很大的话,由于pelt算法下util增加的比较慢,util_est 就是对pelt的这一缺陷进行的补救。

    3. sched_util_est_task ——任务的预估负载

    打印:

    <idle>-0     [005] d.H2  8163.520044: sched_util_est_task: comm=cat pid=15061 cpu=5 util_avg=359 util_est_ewma=350 util_est_enqueued=359

    形参:

    TP_PROTO(struct task_struct *tsk, struct sched_avg *avg),

    打印解析:

    comm:取自 tsk->comm
    pid:取自 tsk->pid
    cpu:取自 task_cpu(tsk)
    util_avg:取自 avg->util_avg
    util_est_ewma:取自 avg->util_est.ewma
    util_est_enqueued:取自 avg->util_est.enqueued

    调用路径:

    <1>
    __update_load_avg_se //fair.c
        trace_sched_util_est_task(tsk, &se->avg); //若 se 是 task 调用,和 trace_sched_util_est_cpu 一起调用
    
    <2>
    util_est_enqueue
        trace_sched_util_est_task(p, &p->se.avg); //和 trace_sched_util_est_cpu 一起调用
    
    <3>
    util_est_dequeue
        trace_sched_util_est_task(p, &p->se.avg); //和 trace_sched_util_est_cpu 一起调用

    说明:
    tarce se 的预估负载,util_est.enqueued 和 util_est.ewma

    调用传参:
    见 调用路径

    实参解析:
    见调用路径

    4. sched_select_task_rq ——任务选核

    打印:

    MDP-0-17450 [006] d..2 790259.891744: sched_select_task_rq: pid= 598 policy=0x00880002 pre-cpu=3 target=2 util=36 boost=36 mask=0xf prefer=1 cpu_prefer=0 flags=1

    形参:

    TP_PROTO(struct task_struct *tsk, int policy, int prev_cpu, int target_cpu, int task_util, int boost, bool prefer, int wake_flags),

    打印解析:

    pid: 取自 tsk->pid
    policy: 取自参数 policy
    pre-cpu: 取自参数 prev_cpu
    target: 取自参数 target_cpu
    util: 取自参数 task_util
    boost: 取自参数 boost
    mask: 取自 tsk->cpus_allowed.bits[0],也就是任务task的cpu亲和性掩码
    prefer: 取自参数 prefer
    cpu_prefer: 取自 tsk->cpu_prefer,配置文件/sys/devices/system/cpu/sched/cpu_prefer,设置<pid, prefer_value>后者取 SCHED_PREFER_NONE=0,BIG=1,LITTLE=2,MEDIUM=3,END=4,目前看主要在负载均衡中使用
    flags: 取自参数 wake_flags

    调用路径:

        scheduler_tick //core.c 只有当前任务是CFS任务时才调用
            check_for_migration //fair.c 任务迁移路径中的选核,传参 (p, cpu, SD_BALANCE_WAKE, 0, 1)
    wake_up_q //core.c
    wake_up_process //core.c
    wake_up_state //core.c 只唤醒 p->state & state 的交集不为空的任务
    default_wake_function //core.c 内核等待队列默认的唤醒函数,很多机制都是基于等待机制实现的
        try_to_wake_up //core.c 任务唤醒,当任务的cpu亲和性大于一个cpu时才调用,传参 (p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags, sibling_count_hint)
    _do_fork
        wake_up_new_task //core.c 为新创建的任务选核,传参 (p, task_cpu(p), SD_BALANCE_FORK, 0, 1)
            fair_sched_class.select_task_rq
                select_task_rq_fair //fair.c
                    trace_sched_select_task_rq

    说明:
    在为任务tsk的选核路径中trace.

    调用传参:

    trace_sched_select_task_rq(p, result, prev_cpu, cpu, task_util_est(p), boosted_task_util(p), (schedtune_prefer_idle(p) > 0), wake_flags)

    实参解析:

    p: 
        待选核的任务
    result:
        是policy,是SELECT_TASK_RQ_FAIR()的返回值,
    prev_cpu:
        不同的执行路径表示不同含义,check_for_migration 路径和 wake_up_new_task 路径为 task_cpu(p),表示任务先前运行的cpu。try_to_wake_up路径传参为 p->wake_cpu,其取值如下描述
    cpu:
        target 字段打印的是这个,为 SELECT_TASK_RQ_FAIR() 选出来的 target_cpu
    task_util_est(p): 
        util 字段打印的是这个,表示任务的 util,不使用walt算法情况下是 max(p->se.avg.util_avg, p->se.avg.util_est),取任务的util_avg和预估的util_est的两者之间的较大值。
    
    boosted_task_util(p): 
        boost 字段打印的是这个,其获取方法见下面对trace sched_boost_task的解析。
    
    schedtune_prefer_idle(p) > 0:
        prefer 字段打印的是这个,取值 st->prefer_idle 来自 /dev/stune/[group/]schedtune.prefer_idle,若是没有分组就是来自根目录下的这个文件。
    
    wake_flags:
        flag 字段打印的是这个,取值可为 WF_SYNC(1) WF_FORK(2) WF_MIGRATED(4),但是选核函数中只对 WF_SYNC 标志进行了判断,此表示表示唤醒者将要睡眠,被唤醒者更倾向于允许在唤醒者所在的cpu上。

    p->wake_cpu 的赋值路径:

                __set_cpus_allowed_ptr //core.c 用户设置亲和性时,传cpu为新设置的亲和性里面的cpu(1)
                __migrate_task //core.c 传的cpu为迁移时的目的cpu(2)
                    move_queued_task //core.c 参数来自上层
                migrate_swap_stop //core.c 调用两次,分别传(src_task,dst_cpu)和(dst_task,src_cpu),这是stop调度类的,先不看
                    __migrate_swap_task //core.c 参数来自上层
                    try_to_wake_up //core.c 传参 cpu=select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0, 1)选出的cpu(3)
            idle_balance //fair.c 传参cpu为目前处于idle的cpu(4)
        nohz_idle_balance //fair.c 除了当前cpu外,对每个idle的cpu都调用,依次传每个idle cpu(5)
            rebalance_domains //fair.c 来自其第一个参数rq
                load_balance //fair.c env->dst_cpu 为参数 this_cpu
                    detach_task //传参 cpu=env->dst_cpu
                        set_task_cpu //core.c 参数来自上层
                        sched_fork //core.c 传参 cpu=smp_processor_id() 就是执行 fork() 系统调用的父任务运行的cpu(6)
                        wake_up_new_task //core.c 传参 cpu=select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0, 1)选出的cpu,选核在后,选核中的 p->wake_cpu(7)
                        init_idle //core.c 参数来自上层
                            __set_task_cpu(struct task_struct *p, unsigned int cpu)
                                set_task_rq(p, cpu) //使能组调度时赋值
                                p->cpu = cpu;
                                p->wake_cpu = cpu //这里

    5. sched_boost_task ——任务的boost负载

    打印:

    UsbFfs-worker-810   [000] d..3 800865.820424: sched_boost_task: comm=UsbFfs-worker pid=810 util=17 margin=503 util_min=102

    形参:

    TP_PROTO(struct task_struct *tsk, unsigned long util, long margin, unsigned int util_min),

    打印解析:

    comm: 取自 tsk->comm
    pid: 取自 tsk->pid
    util: 取自参数 util
    margin: 取自参数 margin
    util_min: 取自参数 util_min

    调用路径:

                update_load_avg //fair.c pelt的负载更新函数
                enqueue_task_fair
                dequeue_task_fair
                    inc_nr_heavy_running //rq_stats.c
                        sched_update_nr_heavy_prod //driversmiscmediatekschedsched_avg.c
                store_btask_down_thresh //core_ctl.c 来自/sys/devices/system/cpu/cpuX/core_ctl/btask_down_thresh
                core_ctl_set_btask_up_thresh //没有调用
            store_btask_up_thresh //core_ctl.c 来自/sys/devices/system/cpu/cpuX/core_ctl/btask_up_thresh
                set_btask_up_thresh
                    set_overutil_threshold //driversmiscmediateksched
    q_stats.c
                    store_overutil //rq_stats.c 对应/sys/devices/system/cpu/rq-stats/over_util
                        overutil_thresh_chg_notify //sched_avg.c
                            is_task_overutil //fair.c 唯一调用路径
                                get_task_util
    task_numa_fault //fair.c
        task_numa_placement //fair.c
    do_huge_pmd_numa_page //huge_memory.c 最终还和内存挂上勾了
    do_numa_page //memory.c
        task_numa_fault //fair.c
            numa_migrate_preferred //fair.c
                task_numa_migrate //fair.c
                    task_numa_find_cpu //fair.c
                        task_numa_compare //fair.c
                            select_idle_sibling //fair.c
                                select_idle_sibling_cstate_aware
                        pick_next_task_fair //fair.c 选到cfs任务了更新,若cpu idle了将rq->misfit_task_load = 0
                    hrtick //core.c
                    scheduler_tick //core.c 在tick中断中更新task的misfit信息
                        task_tick_fair //fair.c fair_sched_class.task_tick回调
                            update_misfit_status //fair.c
                        SELECT_TASK_RQ_FAIR //fair.c 判断算力是否容得下任务,赋值给want_affine
                            wake_cap //fair.c
                        scheduler_tick //core.c 若curr是CFS任务才调用
                            check_for_migration //fair.c
                                task_fits_capacity
                            find_energy_efficient_cpu
                                get_eenv //fair.c
                                find_energy_efficient_cpu //fair.c 若返回的util是0直接退出,一般不会发生
                                select_task_rq_fair //fair.c 作为trace_sched_select_task_rq的一个实参
                                    boosted_task_util //fair.c
                                        trace_sched_boost_task

    boosted_task_util 函数:

    /*
     * 此函数可实现只对指定组内的heavy_task进行boot,并且可以对util进行钳位
     */
    static inline unsigned long boosted_task_util(struct task_struct *task)
    {
        unsigned long util = task_util_est(task); //返回任务的利用率和预估利用率两者的较大值
        /* st->boost是来自/dev/stune/[group/]schedtune.boost,大多数任务是没有分组的,因此根
         * 目录下的schedtune.boost配置影响大多数任务。boost_write()中只允许写入[0,100]的数,
         * 也就是说只能boost util,不能削减util,但是可通过下面的uclamp钳位实现。
         * 公式:margin = st->boost% * (SCHED_CAPACITY_SCALE - util)
         */
        long margin = schedtune_task_margin(task);
        /*
         * 值是来自/dev/stune/[group/]schedtune.util.min,若任务没有在任何组中,则自根目录下
         * 的/dev/stune/schedtune.util.min。往里面echo值的时候,会从opp列表中找一个不小于echo
         * 的值写进去。
         */
        unsigned long util_min = uclamp_task_effective_util(task, UCLAMP_MIN);
        /* 同理值来自 /dev/stune/[group/]/schedtune.util.max */
        unsigned long util_max = uclamp_task_effective_util(task, UCLAMP_MAX);
    
        trace_sched_boost_task(task, util, margin, util_min); //注意:trace这里还是没有过滤小util任务时的
    
        /*
         * 只对heavy任务进行boost,判断是否heavy的阈值stune_task_threshold来自
         * /proc/sys/kernel/sched_stune_task_threshold
         */
        if (util >= stune_task_threshold) {
            util = util + margin;
            return clamp(util, util_min, util_max); //boost后还要被uclamp限制一下
        } else {
            return clamp(util, util_min, util_max);
        }
    }

    说明:
    trace获取boost后的util,如上函数还有过滤逻辑,trace打印的不表示返回的值。由调用路径可见,在选核、迁移、tick、用户sysfs接口配置,都有涉及。

    调用传参:
    见 boosted_task_util 函数

    实参解析:
    见上面 boosted_task_util() 中的注释。

    6. schedutil_uclamp_util

    打印:

    <idle>-0     [000] d.h3  9895.552700: schedutil_uclamp_util: cpu=0 util=145 util_min=0 util_max=1024

    形参:

    TP_PROTO(int cpu, unsigned long util),

    打印解析:

    cpu:参数cpu
    util:参数util
    util_min:取自 uclamp_value(cpu, UCLAMP_MIN) 展开为 cpu_rq(cpu)->uclamp.value[UCLAMP_MIN]
    util_max:取自 uclamp_value(cpu, UCLAMP_MAX) 展开为 cpu_rq(cpu)->uclamp.value[UCLAMP_MAX]

    调用路径:

    //见 trace sched_util 的调用路径
        cpufreq_update_util //sched.h
            sugov_update_shared //cpufreq_schedutil.c
                sugov_next_freq_shared //cpufreq_schedutil.c
                    trace_schedutil_uclamp_util(j, j_util); //对policy->cpus中的每个cpu j 都进行trace

    说明:
    应该是trace cpu cgroup 设置下来的 util 的max和min值。util_min/util_max 的值应该是来自 /dev/cpuctl/<group_name>/cpu.uclamp.min 和 /dev/cpuctl/<group_name>/cpu.uclamp.max,通过cpu_util_max_write_u64(core.c)写到变量中。但是MTK BSP 里面没有这些文件,Qcom 5.4 BSP 有这些文件。

    调用传参:
    见 调用路径

    实参解析:
    见 调用路径

    注:看来任务和 cfs_rq 都有 uclamp 成员,但是分布在不同的cgroup中,任务的是 stune cgroup,对应 /dev/stune 目录,而rq的是 cpu cgroup,对应的是 /dev/cpuctl 目录

    7. uclamp_util_se

    打印:

    kworker/u16:6-15247 [000] d..2  9895.551953: uclamp_util_se: pid=15247 comm=kworker/u16:6 cpu=0 active=0 util_avg=82 uclamp_avg=82 uclamp_min=0 uclamp_max=1024
    uclamp_min_eff=0 uclamp_max_eff=1024

    形参:

    TP_PROTO(bool is_task, struct task_struct *p, struct rq *rq),

    打印解析:

    pid:取自 p->pid
    comm:取自 p->comm
    cpu:取自 rq->cpu
    active:取自 p->uclamp[UCLAMP_MIN].active 其中UCLAMP_MIN=0,UCLAMP_MAX=1
    util_avg:取自 p->se.avg.util_avg
    uclamp_avg:取自 uclamp_util(rq, p->se.avg.util_avg) 将 p->se.avg.util_avg 钳位在 uclamp 的 min 和 max 之间。
    uclamp_min:取自 p->uclamp[UCLAMP_MIN].value  应该来自 /dev/stune/schedtune.util.min,但是MTK BSP上不生效
    uclamp_max:取自 p->uclamp[UCLAMP_MAX].value
    uclamp_min_eff:取自 p->uclamp[UCLAMP_MIN].effective.value 来自 /dev/stune/schedtune.util.min.effective
    uclamp_max_eff:取自 p->uclamp[UCLAMP_MAX].effective.value  TODO: 什么意思?

    说明:
    这个是在负载统计的时候进行trace.

    调用传参:
    trace_uclamp_util_se(entity_is_task(se), container_of(se, struct task_struct, se), cpu_rq(cpu));

    实参解析:

    注:此trace看以看出 uclamp_avg 是否是被 uclamp 钳位。测试发现改/proc/sys/kernel/sched_uclamp_util_min和max,/dev/stune/<group>/schedtune.util.max和min这里的uclamp_min和max
    始终都是0和1024

    8. uclamp_util_cfs

    打印:
    <idle>-0 [000] d.h2 9895.552814: uclamp_util_cfs: cpu=0 util_avg=145 uclamp_avg=145 uclamp_min=0 uclamp_max=1024

    形参:
    TP_PROTO(bool is_root, int cpu, struct cfs_rq *cfs_rq),

    打印解析:
    cpu:参数 cpu
    util_avg:取自 cfs_rq->avg.util_avg
    uclamp_avg:取自 uclamp_util(cpu_rq(cpu), cfs_rq->avg.util_avg); 将 cfs_rq->avg.util_avg 钳位在 uclamp 的 min 和 max 之间。
    uclamp_min:取自 cpu_rq(cpu)->uclamp.value[UCLAMP_MIN]; 应该是来自 /dev/cpuctl/<cgroup>/cpu.uclamp.min,但是MTK BSP没有导出这个文件
    uclamp_max:取自 cpu_rq(cpu)->uclamp.value[UCLAMP_MAX];

    调用路径:

    //调用路径见trace sched_util
        update_cfs_rq_load_avg
            __update_load_avg_cfs_rq
                trace_uclamp_util_cfs

    说明:
    也是在PELT负载更新时进行trace

    调用传参:
    trace_uclamp_util_cfs(is_root_rq, cpu, cfs_rq)

    实参解析:
    is_root_rq:
    判断方法 is_root_rq = (&cpu_rq(cpu)->cfs == cfs_rq)

    9. sched_waking

    打印:
    cat-1843 [003] d..3 18706.110171: sched_waking: comm=kworker/u16:1 pid=30684 prio=120 success=1 target_cpu=002 state=D|N

    形参:
    TP_PROTO(struct task_struct *p),

    打印解析:
    comm: 取自 p->comm
    pid: 取自 p->pid
    prio: 取自 p->prio
    success: 常量赋值,恒为1,没有意义
    target_cpu:取自 task_cpu(p),表示任务p唤醒之前是运行在哪个cpu上
    state: 取自对 p->state 的解析后的,唤醒时任务的状态

    调用路径:

    //对应各种唤醒路径
        default_wake_function
        wake_up_state
        wake_up_process
        wake_up_q
            try_to_wake_up //core.c
                trace_sched_waking(p);
    
    //如果worker进入睡眠状态,通知并询问workqueue是否要唤醒其上的其它任务以保持连续运行,这会抬高workerqueue上任务的优先级。
        __schedule //core.c
            try_to_wake_up_local //core.c Qcom的5.4内核没有这个执行路径,最新内核目前已从这里移除,改放在 sched_submit_work 中
                trace_sched_waking(p);

    说明:
    开始唤醒任务时的trace, 可以用来看任务唤醒早期的信息。

    调用传参:

    实参解析:

    10. sched_wakeup

    打印:
    kworker/u16:4-31213 [003] d.h2 20499.768856: sched_wakeup: comm=kworker/u16:2 pid=2048 prio=120 success=1 target_cpu=003 state=R

    形参:
    TP_PROTO(struct task_struct *p),

    打印解析:
    comm: 取自 p->comm
    pid: 取自 p->pid
    prio: 取自 p->prio
    success: 常量赋值,恒为1,没有意义
    target_cpu:取自 task_cpu(p),结合调用路径可知,此时就表示任务p唤醒之后是运行在哪个cpu上了
    state: 取自对 p->state 的解析后的,唤醒时任务的状态

    调用路径:

    try_to_wake_up
        ttwu_queue
            ttwu_do_activate
        try_to_wake_up
            ttwu_remote
        __schedule //最新内核这个唤醒worker保持连续的分支已经删除
            try_to_wake_up_local
                ttwu_do_wakeup //core.c
                    p->state = TASK_RUNNING;
                    trace_sched_wakeup(p)

    说明:
    可以用来trace任务唤醒后是在哪个cpu上运行。由于trace前设置了p->state = TASK_RUNNING,所以trace上state恒等于R。

    调用传参:

    实参解析:

    11. sched_overutilized

    打印:
    logd.writer-583 [000] d..2 27589.723001: sched_overutilized: overutilized=1 sd_span=0-3

    形参:
    TP_PROTO(struct sched_domain *sd, bool was_overutilized, bool overutilized)

    打印解析:
    overutilized: 取自参数 overutilized
    sd_span: 是以 "%*pbl" 格式打印出来的 cpumask_pr_args(sched_domain_span(sd)),表示此调度域中的cpu,可以从cpu分布上看出是哪个调度域

    调用路径:

    (1)设置overutilized

    load_balance //fair.c
        find_busiest_group //负载均衡路径更新
            update_sd_lb_stats //fair.c MTK的BSP通过对SCHED_MTK_EAS的判断将这个函数中的设置给关闭了
        enqueue_task_fair //插入cfs任务时调用更新
        task_tick_fair //tick周期更新
            update_overutilized_status //fair.c 只是在MC层级的判断cpu_overutilized后设置sd_overutilized
    load_balance    
        find_busiest_group //fair.c 入口位置无条件调用
            update_system_overutilized //eas_plus.c 这里是实际更新位置,此cluster的所有核需要的算力之和超过了此cluster的cpu算力之和设置为true
                set_sd_overutilized
                    trace_sched_overutilized(sd, sd->shared->overutilized, true);
                    sd->shared->overutilized = true;
    
    (2)清理overutilized
    load_balance
        find_busiest_group
            update_sd_lb_stats
            update_system_overutilized //eas_plus.c 调用路径见<1>
                clear_sd_overutilized
                    trace_sched_overutilized(sd, sd->shared->overutilized, false);
                    sd->shared->overutilized = false;

    sd->shared->overutilized 的使用位置:

    static inline int wake_energy(struct task_struct *p, int prev_cpu, int sd_flag, int wake_flags) //fair.c
    {
        sd = rcu_dereference_sched(cpu_rq(prev_cpu)->sd);
        ...
        if (sd_overutilized(sd)) //prev_cpu已经过载了,MC层级
            return false; //false:we shouldn't attempt energy-aware wakeup
        ...
    }
    //调用路径:
    SELECT_TASK_RQ_FAIR
        wake_energy //返回false更容易进入根据亲和性进行选核的路径
    
    
    <2> 若是cpu的调度域没有超载,更偏向于EAS选核
    static int SELECT_TASK_RQ_FAIR(struct task_struct *p, int prev_cpu, int sd_flag, int wake_flags, int sibling_count_hint)//fair.c
    {
        /*以waker执行的cpu调度域逐步向上 MC-->DIE */
        for_each_domain(cpu, tmp) {
            ...
            //此domain没有超载,并且perv_cpu也在此cluster中
            if (want_energy &&  !sd_overutilized(tmp) && cpumask_test_cpu(prev_cpu, sched_domain_span(tmp)))
                energy_sd = tmp;
        }
        ...
        if (energy_sd) {
                new_cpu = __find_energy_efficient_cpu(energy_sd, p, cpu, prev_cpu, sync); //TODO:
                select_reason = LB_EAS;
        }
        ...
    }
    
    
    <3>
    static struct sched_group *find_busiest_group(struct lb_env *env) //fair.c
    {
        ...
        intra = is_intra_domain(local_cpu, busiest_cpu); //两个cpu同一个cluster就认为是domain内部
        if (energy_aware() && !sd_overutilized(env->sd) && !intra) //满足条件认为cluster之间是均衡的,退出
            goto out_balanced;
        ...
    out_balanced:
        env->imbalance = 0;
        return NULL;
    }
    //调用路径:
    load_balance
        find_busiest_group
    
    <4>
    static inline unsigned long get_sd_balance_interval(struct sched_domain *sd, int cpu_busy) //fair.c
    {
        ...
        if (energy_aware() && sd_overutilized(sd)) {
            /* we know the root is overutilized, let's check for a misfit task */
            for_each_cpu(cpu, sched_domain_span(sd)) {
                if (cpu_rq(cpu)->misfit_task_load)
                    return 1; //希望立即触发balance
            }
        }
        return interval;
    }
    //调用路径:
    rebalance_domains //在interval时间之后才触发负载均衡
    update_next_balance //只是更新 next_balance 的值
        get_sd_balance_interval
    
    <5>
    static void rebalance_domains(struct rq *rq, enum cpu_idle_type idle)
    {
        ...
        for_each_domain(cpu, sd) { //MC --> DIE
            if (energy_aware() && !sd_overutilized(sd))
            continue; //跳过没有超载的调度域
        }
        ...
    }
    //调用路径:
            run_rebalance_domains //也是SCHED_SOFTIRQ软中断相应函数
                nohz_idle_balance
    handle_IPI //arch/arm64/kernel/smp.c case IPI_RESCHEDULE:才调用,对应cluster间唤醒路径
        scheduler_ipi
            raise_softirq_irqoff(SCHED_SOFTIRQ); //core.c if (unlikely(got_nohz_idle_kick()) && !cpu_isolated(cpu))为真才执行
    scheduler_tick //core.c
        trigger_load_balance    
            raise_softirq(SCHED_SOFTIRQ)//if (time_after_eq(jiffies, rq->next_balance)) 到期后才触发balance
                run_rebalance_domains //open_softirq(SCHED_SOFTIRQ, run_rebalance_domains) //fair.c
                    rebalance_domains
    
    IPI_RESCHEDULE ipi中断在任务cluster间唤醒时触发:
    try_to_wake_up //core.c
        ttwu_queue //唤醒后的cpu和当前cpu不共享cache,说明不是同一个cluster
            ttwu_queue_remote
                smp_send_reschedule
                    smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE);

    fair.c中除了有 sd_overutilized(sd) 还有 cpu_overutilized(rq->cpu) 和 group_is_overloaded():

    static int need_active_balance(struct lb_env *env) //fair.c
    {
        ...
        if (capacity_of(env->src_cpu) < capacity_of(env->dst_cpu) &&
            (capacity_orig_of(env->src_cpu) < capacity_orig_of(env->dst_cpu)) &&
            env->src_rq->cfs.h_nr_running == 1 &&
            cpu_overutilized(env->src_cpu) &&
            !cpu_overutilized(env->dst_cpu)) {
                return 1;
        }
        ...
    }
    
    static inline bool group_is_overloaded(struct lb_env *env, struct sg_lb_stats *sgs)
    {
        if (sgs->sum_nr_running <= sgs->group_weight)
            return false;
    
        /* 整理:sgs->group_util > sgs->group_capacity * 100/env->sd->imbalance_pct */
        if ((sgs->group_capacity * 100) < (sgs->group_util * env->sd->imbalance_pct))
            return true;
    
        return false;
    }

    说明:
    这个是trace sd->shared->overutilized 的设置和清除的trace,参数2没有打印。主要在负载均衡路径和选核路径中使用,对是否要均衡和均衡时机都有影响,选核路径中会影响是否通过wake_affinity选核。

    调用传参:

    实参解析:

    13. sched_update_lb_sg

    打印:
    kworker/u16:13-24358 [006] d..1 49578.126201: sched_update_lb_sg: avg_load=3821 group_load=3650 group_capacity=978 group_no_capacity=1 group_type=3

    形参:
    TP_PROTO(unsigned long avg_load, unsigned long group_load, unsigned long group_capacity, int group_no_capacity, int group_type),

    打印解析:
    全部打印都是取自参数

    调用路径:

    load_balance //fair.c
        find_busiest_group //fair.c
            update_sd_lb_stats
                update_sg_lb_stats
                    trace_sched_update_lb_sg

    说明:
    负载均衡路径中trace struct sg_lb_stats 的成员的值。

    调用传参:
    trace_sched_update_lb_sg(sgs->avg_load, sgs->group_load, sgs->group_capacity, sgs->group_no_capacity, sgs->group_type);

    实参解析:
    sgs->avg_load: 都是 struct sg_lb_stats 的成员,赋值为 (sgs->group_load*SCHED_CAPACITY_SCALE) / sgs->group_capacity
    sgs->group_load: 对于 group->span 和 env->cpus 里面的每一个cpu,加上其对应的负载 target_load(i, load_idx)
    sgs->group_capacity: 赋值为 group->sgc->capacity,约等于一个cluster的所有能用的cpu的算力之和。
    sgs->group_no_capacity: 赋值为 group_is_overloaded(env, sgs)的返回值,整理后为 sgs->group_util > sgs->group_capacity * 100/env->sd->imbalance_pct 为真,表示group没有空余算力了。
    sgs->group_type: 可以取值 group_other=0、group_misfit_task=1、group_imbalanced=2、group_overloaded=3

    14. sched_stat_template

    模板的三个使用 sched_stat_wait、sched_stat_sleep、sched_stat_iowait、sched_stat_blocked

    打印:
    <idle>-0 [003] dn.2 51863.925458: sched_stat_wait: comm=kworker/u16:2 pid=23411 delay=0 [ns]
    <idle>-0 [005] d.h2 51863.925523: sched_stat_sleep: comm=cat pid=29224 delay=19385 [ns]
    RenderThread-5572 [003] d.s3 51958.206163: sched_stat_iowait: comm=SettingsProvide pid=2006 delay=76846 [ns]
    <idle>-0 [005] d.s4 52145.249923: sched_stat_blocked: comm=kworker/5:1 pid=25088 delay=1983674774 [ns]

    形参:

    打印解析:

    调用路径:

    static inline void update_stats_wait_end(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
    {
        delta = rq_clock(rq_of(cfs_rq)) - schedstat_val(se->statistics.wait_start);
        trace_sched_stat_wait(p, delta);
    }

    在 se 入队列进入 runable 状态开始等待时在 update_stats_wait_start 中对 se->statistics.wait_start 进行赋值,此trace表示在cfs_rq上等待的时间,也就是runnable的时间。

    static inline void update_stats_enqueue_sleeper(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
    {
        sleep_start = schedstat_val(se->statistics.sleep_start);
        block_start = schedstat_val(se->statistics.block_start);
        
        if (sleep_start) {
            u64 delta = rq_clock(rq_of(cfs_rq)) - sleep_start;
            trace_sched_stat_sleep(tsk, delta);
        }
        if (block_start) {
            u64 delta = rq_clock(rq_of(cfs_rq)) - block_start;
            if (tsk->in_iowait) {
                trace_sched_stat_iowait(tsk, delta);
            }
            trace_sched_stat_blocked(tsk, delta);
            trace_sched_blocked_reason(tsk);
        }
    }
    调用路径:
    enqueue_entity
        update_stats_enqueue //if (flags & ENQUEUE_WAKEUP) 才执行
            update_stats_enqueue_sleeper
    
    
    
    /* dequeue_entity -> update_stats_dequeue */
    static inline void update_stats_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) //fair.c
    {
        //任务切换时主动放弃CPU的话有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 中若任务是主动放弃CPU的,则根据任务的状态初始化 sleep_start 或 block_start,在任务唤醒入队列时打印经历的时间,就是任务的睡眠时间或D状态的持续时间。若任务是由于 iowait 而进入的休眠,还会打印 iowait 导致的休眠的时间。

    说明:

    调用传参:

    实参解析:

    15. sched_blocked_reason
    打印:
    kworker/u16:6-30772 [005] d..3 61768.396767: sched_blocked_reason: pid=9852 iowait=0 caller=flush_work+0x1ac/0x204

    形参:
    TP_PROTO(struct task_struct *tsk),

    打印解析:
    pid: 取自 tsk->pid
    iowait: 取自 tsk->in_iowait
    caller: 打印格式控制为"%pS",取自 (void*)get_wchan(tsk),通过 frame->fp 指针进行rewind遍历,然后通过 %pF 格式打印 frame->pc 就是函数名,这里只打印了一级函数名

    调用路径:

    enqueue_entity //fair.c
        update_stats_enqueue //if (flags & ENQUEUE_WAKEUP) 才执行
            update_stats_enqueue_sleeper
                trace_sched_blocked_reason(tsk);

    说明:
    对于cfs任务,唤醒时发现是从D状态唤醒的,就会trace其在哪个函数中进入D状态的

    调用传参:

    实参解析:

  • 相关阅读:
    一台Ubuntu server上安装多实例MySQL
    用VirtualBox构建MySQL测试环境笔记
    Mac点滴
    MySQL复制(三) --- 高可用性和复制
    Linux性能监控的几个工具(转)
    前端读者 | Javascript设计模式理论与实战:状态模式
    前端读者 | 为什么页面跟设计稿差距这么大?是啊!为毛啊?
    前端读者 | 由setTimeout引发的JS引擎运行机制的研究
    前端读者 | Web App开发入门
    前端读者 | 从一行代码里面学点JavaScript
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15452157.html
Copyright © 2020-2023  润新知