基于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状态的。
调用传参:
略
实参解析:
略