Skip to main content
 首页 » 操作系统

Linux 调度器之调度相关trace汇总—MTK

2022年07月19日50artech

基于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 //drivers\misc\mediatek\sched\sched_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 //drivers\misc\mediatek\sched\rq_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状态的

调用传参:

实参解析:


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