[RFC PATCH 3/6] sched/fair: SMT sibling exclusion after successful push

From: Li kunyu

Date: Tue Oct 20 2026 - 04:45:25 EST


In sched_balance_domains(), after a task is successfully pushed to its
SMT sibling CPU, the pull process continues. To prevent the already-pushed
task from being pulled again, both the SMT scheduling domain and the
sibling CPUs are skipped during selection. As a result, SMT sibling CPUs
are not selected.

This patch skips SMT sibling CPUs.

Tested-by: Li Wang (Hieco) <li.wang@xxxxxxxxx>
Signed-off-by: Li kunyu <likunyu@xxxxxxxxxxxxxx>
---
kernel/sched/fair.c | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 12b3223c6403..6e2271484c86 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10657,6 +10657,8 @@ struct lb_env {
enum fbq_type fbq_type;
enum migration_type migration_type;
struct list_head tasks;
+
+ bool is_smt_move_task;
};

/*
@@ -13377,7 +13379,20 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
struct sched_domain __maybe_unused *sd_tmp;
unsigned int busiest_nr = 0;
int __maybe_unused dst_llc;
- int i;
+ int i, smt_cpu, dst_cpu = env->dst_cpu;
+
+ if (sched_feat(SMT_BUDDY_ASSIST) && env->is_smt_move_task) {
+ /*
+ * Remove the SMT sibling CPU from the mask to avoid the
+ * extremely rare case of pulling tasks from it.
+ */
+ for_each_cpu(smt_cpu, cpu_smt_mask(dst_cpu)) {
+ if (smt_cpu == dst_cpu)
+ continue;
+
+ cpumask_clear_cpu(smt_cpu, env->cpus);
+ }
+ }

for_each_cpu_and(i, sched_group_span(group), env->cpus) {
unsigned long capacity, load, util;
@@ -13743,7 +13758,7 @@ static atomic_t sched_balance_running = ATOMIC_INIT(0);
*/
static int sched_balance_rq(int this_cpu, struct rq *this_rq,
struct sched_domain *sd, enum cpu_idle_type idle,
- int *continue_balancing)
+ int *continue_balancing, bool is_smt_move_task)
{
int ld_moved, cur_ld_moved, active_balance = 0;
struct sched_domain *sd_parent = sd->parent;
@@ -13761,6 +13776,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq,
.cpus = cpus,
.fbq_type = all,
.tasks = LIST_HEAD_INIT(env.tasks),
+ .is_smt_move_task = is_smt_move_task,
};
bool need_unlock = false;

@@ -14390,8 +14406,15 @@ static void sched_balance_domains(struct rq *rq, enum cpu_idle_type idle)

interval = get_sd_balance_interval(sd, busy);
if (time_after_eq(jiffies, sd->last_balance + interval)) {
+ /*
+ * When a task is successfully pushed to its SMT sibling CPU:
+ * 1. Skip traversal of the SMT scheduling domain.
+ * 2. Continue pulling tasks from heavily loaded CPUs.
+ * 3. Avoid pulling tasks from SMT sibling CPUs.
+ */
if (!is_smt_buddy_run(sd, is_smt_move_task) &&
- sched_balance_rq(cpu, rq, sd, idle, &continue_balancing)) {
+ sched_balance_rq(cpu, rq, sd, idle, &continue_balancing,
+ is_smt_move_task)) {
/*
* The LBF_DST_PINNED logic could have changed
* env->dst_cpu, so we can't know our idle
@@ -15059,7 +15082,8 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf)

pulled_task = sched_balance_rq(this_cpu, this_rq,
sd, CPU_NEWLY_IDLE,
- &continue_balancing);
+ &continue_balancing,
+ false);

t1 = sched_clock_cpu(this_cpu);
domain_cost = t1 - t0;
--
2.47.3