llc_aggr_tolerance scales two independent gates with a single value:
the number of cores in an LLC compared against the process's active
thread count in invalid_llc_nr(), and the LLC size compared against
the process's memory footprint in exceed_llc_capacity().

The right settings are workload and platform specific. A Verilator
run is one example: its RSS is large while only a small part of it
is hot, so an RSS-based footprint estimate overstates its cache
usage and should not decide whether it is aggregated. And running
its threads on the SMT siblings of a single LLC can beat spreading
them across LLCs on some platforms (e.g. AMD EPYC Turin) but not on
others (e.g. EPYC Milan). Tuning for such a workload means a larger
thread-count tolerance while the footprint tolerance keeps its own
setting - which a single combined knob cannot express.

Split the knob into llc_aggr_tolerance_nr for the thread-count gate
and llc_aggr_tolerance_size for the footprint gate, and let
get_sched_cache_scale() take the tolerance value from the caller.
Both new debugfs files keep the semantics of the old knob (0 disables
aggregation for that gate, values >= 100 mean unlimited).

While making the tunables independently settable, also make the
percentage knobs safe for any value: llc_overaggr_pct and llc_imb_pct
are exposed via debugfs as unbounded u32 values and feed percentage
multiplications:

        util * 100 < max * aggr_pct               (fits_llc_capacity())
        util1 * 100 > util2 * (100 + llc_imb_pct) (util_greater())

CONFIG_SCHED_CACHE only depends on SMP, so this also builds on 32-bit
where unsigned long is 32 bits: a large enough percentage makes
max * aggr_pct (or util2 * (100 + pct)) wrap and produce a garbage
comparison, and the SMT-1 bump aggr_pct * 3 / 2 can wrap the u32
itself. Rather than capping the tunables, detect the overflow at the
point of use with check_mul_overflow()/check_add_overflow() and fall
back to the saturated meaning of the comparison: an overflowing
threshold is effectively unlimited (the LLC always has room), and an
overflowing bias is effectively infinite (the destination utilization
is never considered noticeably greater).

Assisted-by: Claude:claude-fable-5
Signed-off-by: Yangyu Chen <[email protected]>
---
 kernel/sched/debug.c |  6 +++--
 kernel/sched/fair.c  | 62 +++++++++++++++++++++++++++++++-------------
 kernel/sched/sched.h |  3 ++-
 3 files changed, 50 insertions(+), 21 deletions(-)

diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index 40584b27ea0c..b7be245b9fc2 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -672,8 +672,10 @@ static __init int sched_init_debug(void)
        llc = debugfs_create_dir("llc_balancing", debugfs_sched);
        debugfs_create_file("enabled", 0644, llc, NULL,
                            &sched_cache_enable_fops);
-       debugfs_create_u32("aggr_tolerance", 0644, llc,
-                          &llc_aggr_tolerance);
+       debugfs_create_u32("aggr_tolerance_nr", 0644, llc,
+                          &llc_aggr_tolerance_nr);
+       debugfs_create_u32("aggr_tolerance_size", 0644, llc,
+                          &llc_aggr_tolerance_size);
        debugfs_create_u32("epoch_period", 0644, llc,
                           &llc_epoch_period);
        debugfs_create_u32("epoch_affinity_timeout", 0644, llc,
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index d78467ec6ee1..c31acbfa3247 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1404,7 +1404,8 @@ static void set_next_buddy(struct sched_entity *se);
  */
 #define EPOCH_PERIOD   (HZ / 100)      /* 10 ms */
 #define EPOCH_LLC_AFFINITY_TIMEOUT     5       /* 50 ms */
-__read_mostly unsigned int llc_aggr_tolerance  = 1;
+__read_mostly unsigned int llc_aggr_tolerance_nr       = 1;
+__read_mostly unsigned int llc_aggr_tolerance_size     = 1;
 __read_mostly unsigned int llc_epoch_period    = EPOCH_PERIOD;
 __read_mostly unsigned int llc_epoch_affinity_timeout = 
EPOCH_LLC_AFFINITY_TIMEOUT;
 __read_mostly unsigned int llc_imb_pct         = 20;
@@ -1418,10 +1419,8 @@ static int llc_id(int cpu)
        return per_cpu(sd_llc_id, cpu);
 }
 
-static inline int get_sched_cache_scale(int mul)
+static inline int get_sched_cache_scale(unsigned int tol, int mul)
 {
-       unsigned int tol = READ_ONCE(llc_aggr_tolerance);
-
        if (!tol)
                return 0;
 
@@ -1453,23 +1452,23 @@ static bool exceed_llc_capacity(struct mm_struct *mm, 
int cpu)
                footprint = READ_ONCE(mm->sc_stat.footprint);
 
                /*
-                * Scale the LLC size by 256*llc_aggr_tolerance
+                * Scale the LLC size by 256*llc_aggr_tolerance_size
                 * and compare it to the task's footprint.
                 *
                 * Suppose the L3 size is 32MB. If the
-                * llc_aggr_tolerance is 1:
+                * llc_aggr_tolerance_size is 1:
                 * When the footprint is larger than 32MB, the
                 * process is regarded as exceeding the LLC
-                * capacity. If the llc_aggr_tolerance is 99:
+                * capacity. If the llc_aggr_tolerance_size is 99:
                 * When the footprint is larger than 784GB, the
                 * process is regarded as exceeding the LLC
                 * capacity:
                 * 784GB = (1 + (99 - 1) * 256) * 32MB
-                * If the llc_aggr_tolerance is 100:
+                * If the llc_aggr_tolerance_size is 100:
                 * ignore the footprint and do the aggregation
                 * anyway.
                 */
-               scale = get_sched_cache_scale(256);
+               scale = 
get_sched_cache_scale(READ_ONCE(llc_aggr_tolerance_size), 256);
                if (scale == INT_MAX)
                        return false;
 
@@ -1488,10 +1487,10 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct 
task_struct *p,
                return true;
 
        /*
-        * Scale the number of 'cores' in a LLC by llc_aggr_tolerance
+        * Scale the number of 'cores' in a LLC by llc_aggr_tolerance_nr
         * and compare it to the task's active threads.
         */
-       scale = get_sched_cache_scale(1);
+       scale = get_sched_cache_scale(READ_ONCE(llc_aggr_tolerance_nr), 1);
        if (scale == INT_MAX)
                return false;
 
@@ -10418,20 +10417,34 @@ static inline int 
task_is_ineligible_on_dst_cpu(struct task_struct *p, int dest_
  * done.
  * Derived from fits_capacity().
  *
+ * llc_overaggr_pct is an unbounded debugfs u32 and CONFIG_SCHED_CACHE
+ * only depends on SMP, so max * aggr_pct can wrap on 32-bit. A
+ * threshold large enough to overflow is effectively unlimited: the
+ * LLC always has room, so treat that as "fits" rather than letting
+ * the multiplication wrap.
+ *
  * (default: ~50%, tunable via debugfs)
  */
 static bool fits_llc_capacity(unsigned long util, unsigned long max)
 {
-       u32 aggr_pct = llc_overaggr_pct;
+       u32 aggr_pct = READ_ONCE(llc_overaggr_pct);
+       unsigned long thresh;
+       u32 bumped;
 
        /*
         * For single core systems, raise the aggregation
         * threshold to accommodate more tasks.
         */
-       if (cpu_smt_num_threads == 1)
-               aggr_pct = (aggr_pct * 3 / 2);
+       if (cpu_smt_num_threads == 1) {
+               if (check_mul_overflow(aggr_pct, 3U, &bumped))
+                       return true;
+               aggr_pct = bumped / 2;
+       }
+
+       if (check_mul_overflow(max, (unsigned long)aggr_pct, &thresh))
+               return true;
 
-       return util * 100 < max * aggr_pct;
+       return util * 100 < thresh;
 }
 
 /*
@@ -10439,10 +10452,23 @@ static bool fits_llc_capacity(unsigned long util, 
unsigned long max)
  * is 'util1' noticeably greater than 'util2'
  * Derived from capacity_greater().
  * Bias is in perentage.
+ *
+ * Allows dst util to be bigger than src util by up to bias percent.
+ * llc_imb_pct is an unbounded debugfs u32; a bias large enough to
+ * overflow util2 * (100 + llc_imb_pct) is effectively infinite, so
+ * util1 is never noticeably greater - treat that as "not greater"
+ * rather than letting the multiplication wrap.
  */
-/* Allows dst util to be bigger than src util by up to bias percent */
-#define util_greater(util1, util2) \
-       ((util1) * 100 > (util2) * (100 + llc_imb_pct))
+static bool util_greater(unsigned long util1, unsigned long util2)
+{
+       unsigned long bias, rhs;
+
+       if (check_add_overflow(100UL, (unsigned long)READ_ONCE(llc_imb_pct), 
&bias) ||
+           check_mul_overflow(util2, bias, &rhs))
+               return false;
+
+       return util1 * 100 > rhs;
+}
 
 static __maybe_unused bool get_llc_stats(int cpu, unsigned long *util,
                                         unsigned long *cap)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 56acf502ba26..0ebafdcf2c7f 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4095,7 +4095,8 @@ static inline void mm_cid_switch_to(struct task_struct 
*prev, struct task_struct
 DECLARE_STATIC_KEY_FALSE(sched_cache_present);
 DECLARE_STATIC_KEY_FALSE(sched_cache_active);
 extern int sysctl_sched_cache_user;
-extern unsigned int llc_aggr_tolerance;
+extern unsigned int llc_aggr_tolerance_nr;
+extern unsigned int llc_aggr_tolerance_size;
 extern unsigned int llc_epoch_period;
 extern unsigned int llc_epoch_affinity_timeout;
 extern unsigned int llc_imb_pct;
-- 
2.47.3


Reply via email to