Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

sched/fair: Check if a task has a fitting CPU when updating misfit

If a misfit task is affined to a subset of the possible CPUs, we need to
verify that one of these CPUs can fit it. Otherwise the load balancer
code will continuously trigger needlessly leading the balance_interval
to increase in return and eventually end up with a situation where real
imbalances take a long time to address because of this impossible
imbalance situation.

This can happen in Android world where it's common for background tasks
to be restricted to little cores.

Similarly if we can't fit the biggest core, triggering misfit is
pointless as it is the best we can ever get on this system.

To be able to detect that; we use asym_cap_list to iterate through
capacities in the system to see if the task is able to run at a higher
capacity level based on its p->cpus_ptr. We do that when the affinity
change, a fair task is forked, or when a task switched to fair policy.
We store the max_allowed_capacity in task_struct to allow for cheap
comparison in the fast path.

Improve check_misfit_status() function by removing redundant checks.
misfit_task_load will be 0 if the task can't move to a bigger CPU. And
nohz_balancer_kick() already checks for cpu_check_capacity() before
calling check_misfit_status().

Test:
=====

Add

trace_printk("balance_interval = %lu\n", interval)

in get_sd_balance_interval().

run
if [ "$MASK" != "0" ]; then
adb shell "taskset -a $MASK cat /dev/zero > /dev/null"
fi
sleep 10
// parse ftrace buffer counting the occurrence of each valaue

Where MASK is either:

* 0: no busy task running
* 1: busy task is pinned to 1 cpu; handled today to not cause
misfit
* f: busy task pinned to little cores, simulates busy background
task, demonstrates the problem to be fixed

Results:
========

Note how occurrence of balance_interval = 128 overshoots for MASK = f.

BEFORE
------

MASK=0

1 balance_interval = 175
120 balance_interval = 128
846 balance_interval = 64
55 balance_interval = 63
215 balance_interval = 32
2 balance_interval = 31
2 balance_interval = 16
4 balance_interval = 8
1870 balance_interval = 4
65 balance_interval = 2

MASK=1

27 balance_interval = 175
37 balance_interval = 127
840 balance_interval = 64
167 balance_interval = 63
449 balance_interval = 32
84 balance_interval = 31
304 balance_interval = 16
1156 balance_interval = 8
2781 balance_interval = 4
428 balance_interval = 2

MASK=f

1 balance_interval = 175
1328 balance_interval = 128
44 balance_interval = 64
101 balance_interval = 63
25 balance_interval = 32
5 balance_interval = 31
23 balance_interval = 16
23 balance_interval = 8
4306 balance_interval = 4
177 balance_interval = 2

AFTER
-----

Note how the high values almost disappear for all MASK values. The
system has background tasks that could trigger the problem without
simulate it even with MASK=0.

MASK=0

103 balance_interval = 63
19 balance_interval = 31
194 balance_interval = 8
4827 balance_interval = 4
179 balance_interval = 2

MASK=1

131 balance_interval = 63
1 balance_interval = 31
87 balance_interval = 8
3600 balance_interval = 4
7 balance_interval = 2

MASK=f

8 balance_interval = 127
182 balance_interval = 63
3 balance_interval = 31
9 balance_interval = 16
415 balance_interval = 8
3415 balance_interval = 4
21 balance_interval = 2

Signed-off-by: Qais Yousef <qyousef@layalina.io>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Vincent Guittot <vincent.guittot@linaro.org>
Link: https://lore.kernel.org/r/20240324004552.999936-3-qyousef@layalina.io

authored by

Qais Yousef and committed by
Ingo Molnar
22d56074 77222b0d

+52 -16
+1
include/linux/sched.h
··· 835 835 #endif 836 836 837 837 unsigned int policy; 838 + unsigned long max_allowed_capacity; 838 839 int nr_cpus_allowed; 839 840 const cpumask_t *cpus_ptr; 840 841 cpumask_t *user_cpus_ptr;
+1
init/init_task.c
··· 77 77 .cpus_ptr = &init_task.cpus_mask, 78 78 .user_cpus_ptr = NULL, 79 79 .cpus_mask = CPU_MASK_ALL, 80 + .max_allowed_capacity = SCHED_CAPACITY_SCALE, 80 81 .nr_cpus_allowed= NR_CPUS, 81 82 .mm = NULL, 82 83 .active_mm = &init_mm,
+50 -16
kernel/sched/fair.c
··· 5098 5098 5099 5099 static inline void update_misfit_status(struct task_struct *p, struct rq *rq) 5100 5100 { 5101 + int cpu = cpu_of(rq); 5102 + 5101 5103 if (!sched_asym_cpucap_active()) 5102 5104 return; 5103 5105 5104 - if (!p || p->nr_cpus_allowed == 1) { 5105 - rq->misfit_task_load = 0; 5106 - return; 5107 - } 5106 + /* 5107 + * Affinity allows us to go somewhere higher? Or are we on biggest 5108 + * available CPU already? Or do we fit into this CPU ? 5109 + */ 5110 + if (!p || (p->nr_cpus_allowed == 1) || 5111 + (arch_scale_cpu_capacity(cpu) == p->max_allowed_capacity) || 5112 + task_fits_cpu(p, cpu)) { 5108 5113 5109 - if (task_fits_cpu(p, cpu_of(rq))) { 5110 5114 rq->misfit_task_load = 0; 5111 5115 return; 5112 5116 } ··· 8257 8253 remove_entity_load_avg(&p->se); 8258 8254 } 8259 8255 8256 + /* 8257 + * Set the max capacity the task is allowed to run at for misfit detection. 8258 + */ 8259 + static void set_task_max_allowed_capacity(struct task_struct *p) 8260 + { 8261 + struct asym_cap_data *entry; 8262 + 8263 + if (!sched_asym_cpucap_active()) 8264 + return; 8265 + 8266 + rcu_read_lock(); 8267 + list_for_each_entry_rcu(entry, &asym_cap_list, link) { 8268 + cpumask_t *cpumask; 8269 + 8270 + cpumask = cpu_capacity_span(entry); 8271 + if (!cpumask_intersects(p->cpus_ptr, cpumask)) 8272 + continue; 8273 + 8274 + p->max_allowed_capacity = entry->capacity; 8275 + break; 8276 + } 8277 + rcu_read_unlock(); 8278 + } 8279 + 8280 + static void set_cpus_allowed_fair(struct task_struct *p, struct affinity_context *ctx) 8281 + { 8282 + set_cpus_allowed_common(p, ctx); 8283 + set_task_max_allowed_capacity(p); 8284 + } 8285 + 8260 8286 static int 8261 8287 balance_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) 8262 8288 { ··· 8295 8261 8296 8262 return sched_balance_newidle(rq, rf) != 0; 8297 8263 } 8264 + #else 8265 + static inline void set_task_max_allowed_capacity(struct task_struct *p) {} 8298 8266 #endif /* CONFIG_SMP */ 8299 8267 8300 8268 static void set_next_buddy(struct sched_entity *se) ··· 9646 9610 (arch_scale_cpu_capacity(cpu_of(rq)) * 100)); 9647 9611 } 9648 9612 9649 - /* 9650 - * Check whether a rq has a misfit task and if it looks like we can actually 9651 - * help that task: we can migrate the task to a CPU of higher capacity, or 9652 - * the task's current CPU is heavily pressured. 9653 - */ 9654 - static inline int check_misfit_status(struct rq *rq, struct sched_domain *sd) 9613 + /* Check if the rq has a misfit task */ 9614 + static inline bool check_misfit_status(struct rq *rq) 9655 9615 { 9656 - return rq->misfit_task_load && 9657 - (arch_scale_cpu_capacity(rq->cpu) < rq->rd->max_cpu_capacity || 9658 - check_cpu_capacity(rq, sd)); 9616 + return rq->misfit_task_load; 9659 9617 } 9660 9618 9661 9619 /* ··· 11953 11923 * When ASYM_CPUCAPACITY; see if there's a higher capacity CPU 11954 11924 * to run the misfit task on. 11955 11925 */ 11956 - if (check_misfit_status(rq, sd)) { 11926 + if (check_misfit_status(rq)) { 11957 11927 flags = NOHZ_STATS_KICK | NOHZ_BALANCE_KICK; 11958 11928 goto unlock; 11959 11929 } ··· 12678 12648 rq_lock(rq, &rf); 12679 12649 update_rq_clock(rq); 12680 12650 12651 + set_task_max_allowed_capacity(p); 12652 + 12681 12653 cfs_rq = task_cfs_rq(current); 12682 12654 curr = cfs_rq->curr; 12683 12655 if (curr) ··· 12802 12770 static void switched_to_fair(struct rq *rq, struct task_struct *p) 12803 12771 { 12804 12772 attach_task_cfs_rq(p); 12773 + 12774 + set_task_max_allowed_capacity(p); 12805 12775 12806 12776 if (task_on_rq_queued(p)) { 12807 12777 /* ··· 13176 13142 .rq_offline = rq_offline_fair, 13177 13143 13178 13144 .task_dead = task_dead_fair, 13179 - .set_cpus_allowed = set_cpus_allowed_common, 13145 + .set_cpus_allowed = set_cpus_allowed_fair, 13180 13146 #endif 13181 13147 13182 13148 .task_tick = task_tick_fair,