Merge branch 'sched/cache'

Merge the cache aware balancer topic branch.

# Conflicts:
#	kernel/sched/topology.c
This commit is contained in:
Peter Zijlstra 2026-05-19 12:18:01 +02:00
commit a26d9208c1
17 changed files with 1849 additions and 56 deletions

View File

@ -7236,6 +7236,18 @@ Kernel parameters
Not specifying this option is equivalent to
spec_store_bypass_disable=auto.
split_llc=
[X86,EARLY] Split the LLC N-ways
When set, the LLC is split this many ways by matching
'core_id % n'. This is setup before SMP bringup and
used during SMP bringup before it knows the full
topology. If your core count doesn't nicely divide by
the number given, you get to keep the pieces.
This is mostly a debug feature to emulate multiple LLCs
on hardware that only have a single LLC.
split_lock_detect=
[X86] Enable split lock detection or bus lock detection

View File

@ -704,6 +704,11 @@ static inline u32 per_cpu_l2c_id(unsigned int cpu)
return per_cpu(cpu_info.topo.l2c_id, cpu);
}
static inline u32 per_cpu_core_id(unsigned int cpu)
{
return per_cpu(cpu_info.topo.core_id, cpu);
}
#ifdef CONFIG_CPU_SUP_AMD
/*
* Issue a DIV 0/1 insn to clear any division data from previous DIV

View File

@ -424,6 +424,21 @@ static const struct x86_cpu_id intel_cod_cpu[] = {
{}
};
/*
* Allows splitting the LLC by matching 'core_id % split_llc'.
*
* This is mostly a debug hack to emulate systems with multiple LLCs per node
* on systems that do not naturally have this.
*/
static unsigned int split_llc = 0;
static int __init split_llc_setup(char *str)
{
get_option(&str, &split_llc);
return 0;
}
early_param("split_llc", split_llc_setup);
static bool match_llc(struct cpuinfo_x86 *c, struct cpuinfo_x86 *o)
{
const struct x86_cpu_id *id = x86_match_cpu(intel_cod_cpu);
@ -438,6 +453,11 @@ static bool match_llc(struct cpuinfo_x86 *c, struct cpuinfo_x86 *o)
if (per_cpu_llc_id(cpu1) != per_cpu_llc_id(cpu2))
return false;
if (split_llc &&
(per_cpu_core_id(cpu1) % split_llc) !=
(per_cpu_core_id(cpu2) % split_llc))
return false;
/*
* Allow the SNC topology without warning. Return of false
* means 'c' does not share the LLC of 'o'. This will be

View File

@ -17,6 +17,7 @@
#include <linux/init.h>
#include <linux/of.h>
#include <linux/sched.h>
#include <linux/sched/topology.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/sysfs.h>
@ -68,6 +69,24 @@ bool last_level_cache_is_valid(unsigned int cpu)
}
/*
* Get the cacheinfo of the LLC associated with @cpu.
* Derived from update_per_cpu_data_slice_size_cpu().
*/
struct cacheinfo *get_cpu_cacheinfo_llc(unsigned int cpu)
{
struct cacheinfo *llc;
if (!last_level_cache_is_valid(cpu))
return NULL;
llc = per_cpu_cacheinfo_idx(cpu, cache_leaves(cpu) - 1);
if (llc->type != CACHE_TYPE_DATA && llc->type != CACHE_TYPE_UNIFIED)
return NULL;
return llc;
}
bool last_level_cache_is_shared(unsigned int cpu_x, unsigned int cpu_y)
{
struct cacheinfo *llc_x, *llc_y;
@ -1018,6 +1037,7 @@ static int cacheinfo_cpu_online(unsigned int cpu)
goto err;
if (cpu_map_shared_cache(true, cpu, &cpu_map))
update_per_cpu_data_slice_size(true, cpu, cpu_map);
sched_update_llc_bytes(cpu);
return 0;
err:
free_cache_attributes(cpu);
@ -1036,6 +1056,9 @@ static int cacheinfo_cpu_pre_down(unsigned int cpu)
free_cache_attributes(cpu);
if (nr_shared > 1)
update_per_cpu_data_slice_size(false, cpu, cpu_map);
sched_update_llc_bytes(cpu);
return 0;
}

View File

@ -89,6 +89,7 @@ int populate_cache_leaves(unsigned int cpu);
int cache_setup_acpi(unsigned int cpu);
bool last_level_cache_is_valid(unsigned int cpu);
bool last_level_cache_is_shared(unsigned int cpu_x, unsigned int cpu_y);
struct cacheinfo *get_cpu_cacheinfo_llc(unsigned int cpu);
int fetch_cache_info(unsigned int cpu);
int detect_cache_attributes(unsigned int cpu);
#ifndef CONFIG_ACPI_PPTT

View File

@ -1222,6 +1222,8 @@ struct mm_struct {
/* MM CID related storage */
struct mm_mm_cid mm_cid;
/* sched_cache related statistics */
struct sched_cache_stat sc_stat;
#ifdef CONFIG_MMU
atomic_long_t pgtables_bytes; /* size of all page tables */
#endif
@ -1628,6 +1630,36 @@ static inline unsigned int mm_cid_size(void)
# define MM_CID_STATIC_SIZE 0
#endif /* CONFIG_SCHED_MM_CID */
#ifdef CONFIG_SCHED_CACHE
void mm_init_sched(struct mm_struct *mm,
struct sched_cache_time __percpu *pcpu_sched);
static inline int mm_alloc_sched_noprof(struct mm_struct *mm)
{
struct sched_cache_time __percpu *pcpu_sched =
alloc_percpu_noprof(struct sched_cache_time);
if (!pcpu_sched)
return -ENOMEM;
mm_init_sched(mm, pcpu_sched);
return 0;
}
#define mm_alloc_sched(...) alloc_hooks(mm_alloc_sched_noprof(__VA_ARGS__))
static inline void mm_destroy_sched(struct mm_struct *mm)
{
free_percpu(mm->sc_stat.pcpu_sched);
mm->sc_stat.pcpu_sched = NULL;
}
#else /* !CONFIG_SCHED_CACHE */
static inline int mm_alloc_sched(struct mm_struct *mm) { return 0; }
static inline void mm_destroy_sched(struct mm_struct *mm) { }
#endif /* CONFIG_SCHED_CACHE */
struct mmu_gather;
extern void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm);
extern void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm);

View File

@ -1408,6 +1408,13 @@ struct task_struct {
unsigned long numa_pages_migrated;
#endif /* CONFIG_NUMA_BALANCING */
#ifdef CONFIG_SCHED_CACHE
struct callback_head cache_work;
int preferred_llc;
/* 1: task was enqueued to its preferred LLC, 0 otherwise */
int pref_llc_queued;
#endif
struct rseq_data rseq;
struct sched_mm_cid mm_cid;
@ -2408,6 +2415,29 @@ static __always_inline int task_mm_cid(struct task_struct *t)
}
#endif
#ifdef CONFIG_SCHED_CACHE
struct sched_cache_time {
u64 runtime;
unsigned long epoch;
};
struct sched_cache_stat {
struct sched_cache_time __percpu *pcpu_sched;
raw_spinlock_t lock;
unsigned long epoch;
u64 nr_running_avg;
unsigned long next_scan;
unsigned long footprint;
int cpu;
} ____cacheline_aligned_in_smp;
#else
struct sched_cache_stat { };
#endif
#ifndef MODULE
#ifndef COMPILE_OFFSETS

View File

@ -68,6 +68,10 @@ struct sched_domain_shared {
atomic_t nr_busy_cpus;
int has_idle_cores;
int nr_idle_scan;
#ifdef CONFIG_SCHED_CACHE
unsigned long util_avg;
unsigned long capacity;
#endif
};
struct sched_domain {
@ -99,6 +103,12 @@ struct sched_domain {
u64 max_newidle_lb_cost;
unsigned long last_decay_max_lb_cost;
#ifdef CONFIG_SCHED_CACHE
unsigned int llc_max;
unsigned int *llc_counts __counted_by_ptr(llc_max);
unsigned long llc_bytes;
#endif
#ifdef CONFIG_SCHEDSTATS
/* sched_balance_rq() stats */
unsigned int lb_count[CPU_MAX_IDLE_TYPES];
@ -256,4 +266,10 @@ static inline int task_node(const struct task_struct *p)
return cpu_to_node(task_cpu(p));
}
#ifdef CONFIG_SCHED_CACHE
extern void sched_update_llc_bytes(unsigned int cpu);
#else
static inline void sched_update_llc_bytes(unsigned int cpu) { }
#endif
#endif /* _LINUX_SCHED_TOPOLOGY_H */

View File

@ -1019,6 +1019,17 @@ config NUMA_BALANCING
This system will be inactive on UMA systems.
config SCHED_CACHE
bool "Cache aware load balance"
default y
depends on SMP
help
When enabled, the scheduler will attempt to aggregate tasks from
the same process onto a single Last Level Cache (LLC) domain when
possible. This improves cache locality by keeping tasks that share
resources within the same cache domain, reducing cache misses and
lowering data access latency.
config NUMA_BALANCING_DEFAULT_ENABLED
bool "Automatically enable NUMA aware memory/task placement"
default y

View File

@ -215,6 +215,10 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
.numa_group = NULL,
.numa_faults = NULL,
#endif
#ifdef CONFIG_SCHED_CACHE
.preferred_llc = -1,
.pref_llc_queued = 0,
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
.kasan_depth = 1,
#endif

View File

@ -543,6 +543,32 @@ void mm_update_next_owner(struct mm_struct *mm)
}
#endif /* CONFIG_MEMCG */
#if defined(CONFIG_SCHED_CACHE) && defined(CONFIG_NUMA_BALANCING)
/*
* Subtract the memory footprint of the current task from
* mm.
*/
static void exit_mm_sched_cache(struct mm_struct *mm)
{
unsigned long fp, sub;
if (!current->total_numa_faults)
return;
/*
* No lock protection due to performance considerations.
* Make sure mm->sc_stat.footprint does not become
* negative.
*/
fp = READ_ONCE(mm->sc_stat.footprint);
sub = min(fp, current->total_numa_faults);
WRITE_ONCE(mm->sc_stat.footprint, fp - sub);
}
#else
static inline void exit_mm_sched_cache(struct mm_struct *mm)
{
}
#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */
/*
* Turn us into a lazy TLB process if we
* aren't already..
@ -554,6 +580,9 @@ static void exit_mm(void)
exit_mm_release(current, mm);
if (!mm)
return;
exit_mm_sched_cache(mm);
mmap_read_lock(mm);
mmgrab_lazy_tlb(mm);
BUG_ON(mm != current->active_mm);

View File

@ -726,6 +726,7 @@ void __mmdrop(struct mm_struct *mm)
cleanup_lazy_tlbs(mm);
WARN_ON_ONCE(mm == current->active_mm);
mm_destroy_sched(mm);
mm_free_pgd(mm);
mm_free_id(mm);
destroy_context(mm);
@ -1128,6 +1129,9 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
if (mm_alloc_cid(mm, p))
goto fail_cid;
if (mm_alloc_sched(mm))
goto fail_sched;
if (percpu_counter_init_many(mm->rss_stat, 0, GFP_KERNEL_ACCOUNT,
NR_MM_COUNTERS))
goto fail_pcpu;
@ -1137,6 +1141,8 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
return mm;
fail_pcpu:
mm_destroy_sched(mm);
fail_sched:
mm_destroy_cid(mm);
fail_cid:
destroy_context(mm);

View File

@ -548,6 +548,11 @@ void __trace_set_current_state(int state_value)
}
EXPORT_SYMBOL(__trace_set_current_state);
int task_llc(const struct task_struct *p)
{
return per_cpu(sd_llc_id, task_cpu(p));
}
/*
* Serialization rules:
*
@ -4506,6 +4511,7 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
init_numa_balancing(clone_flags, p);
p->wake_entry.u_flags = CSD_TYPE_TTWU;
p->migration_pending = NULL;
init_sched_mm(p);
}
DEFINE_STATIC_KEY_FALSE(sched_numa_balancing);
@ -8699,6 +8705,8 @@ int sched_cpu_deactivate(unsigned int cpu)
*/
synchronize_rcu();
sched_domains_free_llc_id(cpu);
sched_set_rq_offline(rq, cpu);
scx_rq_deactivate(rq);
@ -9030,6 +9038,11 @@ void __init sched_init(void)
rq->core_cookie = 0UL;
#endif
#ifdef CONFIG_SCHED_CACHE
raw_spin_lock_init(&rq->cpu_epoch_lock);
rq->cpu_epoch_next = jiffies;
#endif
zalloc_cpumask_var_node(&rq->scratch_mask, GFP_KERNEL, cpu_to_node(i));
}

View File

@ -210,6 +210,48 @@ static const struct file_operations sched_scaling_fops = {
.release = single_release,
};
#ifdef CONFIG_SCHED_CACHE
static ssize_t
sched_cache_enable_write(struct file *filp, const char __user *ubuf,
size_t cnt, loff_t *ppos)
{
bool val;
int ret;
ret = kstrtobool_from_user(ubuf, cnt, &val);
if (ret)
return ret;
sysctl_sched_cache_user = val;
sched_cache_active_set();
*ppos += cnt;
return cnt;
}
static int sched_cache_enable_show(struct seq_file *m, void *v)
{
seq_printf(m, "%d\n", sysctl_sched_cache_user);
return 0;
}
static int sched_cache_enable_open(struct inode *inode,
struct file *filp)
{
return single_open(filp, sched_cache_enable_show, NULL);
}
static const struct file_operations sched_cache_enable_fops = {
.open = sched_cache_enable_open,
.write = sched_cache_enable_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
#ifdef CONFIG_PREEMPT_DYNAMIC
static ssize_t sched_dynamic_write(struct file *filp, const char __user *ubuf,
@ -593,7 +635,7 @@ static void debugfs_ext_server_init(void)
static __init int sched_init_debug(void)
{
struct dentry __maybe_unused *numa;
struct dentry __maybe_unused *numa, *llc;
debugfs_sched = debugfs_create_dir("sched", NULL);
@ -626,6 +668,22 @@ static __init int sched_init_debug(void)
debugfs_create_u32("hot_threshold_ms", 0644, numa, &sysctl_numa_balancing_hot_threshold);
#endif /* CONFIG_NUMA_BALANCING */
#ifdef CONFIG_SCHED_CACHE
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("epoch_period", 0644, llc,
&llc_epoch_period);
debugfs_create_u32("epoch_affinity_timeout", 0644, llc,
&llc_epoch_affinity_timeout);
debugfs_create_u32("overaggr_pct", 0644, llc,
&llc_overaggr_pct);
debugfs_create_u32("imb_pct", 0644, llc,
&llc_imb_pct);
#endif
debugfs_create_file("debug", 0444, debugfs_sched, NULL, &sched_debug_fops);
debugfs_fair_server_init();

File diff suppressed because it is too large Load Diff

View File

@ -1187,6 +1187,12 @@ struct rq {
struct scx_rq scx;
struct sched_dl_entity ext_server;
#endif
#ifdef CONFIG_SCHED_CACHE
raw_spinlock_t cpu_epoch_lock ____cacheline_aligned;
u64 cpu_runtime;
unsigned long cpu_epoch;
unsigned long cpu_epoch_next;
#endif
struct sched_dl_entity fair_server;
@ -1199,6 +1205,12 @@ struct rq {
#ifdef CONFIG_NUMA_BALANCING
unsigned int numa_migrate_on;
#endif
#ifdef CONFIG_SCHED_CACHE
unsigned int nr_pref_llc_running;
unsigned int nr_llc_running;
#endif
/*
* This is part of a global counter where only the total sum
* over all CPUs matters. A task can increase this counter on
@ -1546,6 +1558,14 @@ extern void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags);
extern void sched_core_get(void);
extern void sched_core_put(void);
static inline bool task_has_sched_core(struct task_struct *p)
{
if (sched_core_disabled())
return false;
return !!p->core_cookie;
}
#else /* !CONFIG_SCHED_CORE: */
static inline bool sched_core_enabled(struct rq *rq)
@ -1586,6 +1606,11 @@ static inline bool sched_group_cookie_match(struct rq *rq,
return true;
}
static inline bool task_has_sched_core(struct task_struct *p)
{
return false;
}
#endif /* !CONFIG_SCHED_CORE */
#ifdef CONFIG_RT_GROUP_SCHED
@ -2076,6 +2101,8 @@ init_numa_balancing(u64 clone_flags, struct task_struct *p)
#endif /* !CONFIG_NUMA_BALANCING */
int task_llc(const struct task_struct *p);
static inline void
queue_balance_callback(struct rq *rq,
struct balance_callback *head,
@ -2164,6 +2191,7 @@ DECLARE_PER_CPU(struct sched_domain __rcu *, sd_llc);
DECLARE_PER_CPU(int, sd_llc_size);
DECLARE_PER_CPU(int, sd_llc_id);
DECLARE_PER_CPU(int, sd_share_id);
DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_llc_shared);
DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_balance_shared);
DECLARE_PER_CPU(struct sched_domain __rcu *, sd_numa);
DECLARE_PER_CPU(struct sched_domain __rcu *, sd_asym_packing);
@ -4031,6 +4059,29 @@ static inline void mm_cid_switch_to(struct task_struct *prev, struct task_struct
static inline void mm_cid_switch_to(struct task_struct *prev, struct task_struct *next) { }
#endif /* !CONFIG_SCHED_MM_CID */
#ifdef CONFIG_SCHED_CACHE
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_epoch_period;
extern unsigned int llc_epoch_affinity_timeout;
extern unsigned int llc_imb_pct;
extern unsigned int llc_overaggr_pct;
static inline bool sched_cache_enabled(void)
{
return static_branch_unlikely(&sched_cache_active);
}
extern void sched_cache_active_set(void);
#endif
void sched_domains_free_llc_id(int cpu);
extern void init_sched_mm(struct task_struct *p);
extern u64 avg_vruntime(struct cfs_rq *cfs_rq);
extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se);
static inline

View File

@ -19,8 +19,10 @@ void sched_domains_mutex_unlock(void)
}
/* Protected by sched_domains_mutex: */
static cpumask_var_t sched_domains_llc_id_allocmask;
static cpumask_var_t sched_domains_tmpmask;
static cpumask_var_t sched_domains_tmpmask2;
int max_lid;
static int __init sched_debug_setup(char *str)
{
@ -632,6 +634,11 @@ static void destroy_sched_domain(struct sched_domain *sd)
if (sd->shared && atomic_dec_and_test(&sd->shared->ref))
kfree(sd->shared);
#ifdef CONFIG_SCHED_CACHE
/* only the bottom sd has llc_counts array */
kfree(sd->llc_counts);
#endif
kfree(sd);
}
@ -663,8 +670,9 @@ static void destroy_sched_domains(struct sched_domain *sd)
*/
DEFINE_PER_CPU(struct sched_domain __rcu *, sd_llc);
DEFINE_PER_CPU(int, sd_llc_size);
DEFINE_PER_CPU(int, sd_llc_id);
DEFINE_PER_CPU(int, sd_llc_id) = -1;
DEFINE_PER_CPU(int, sd_share_id);
DEFINE_PER_CPU(struct sched_domain_shared __rcu *, sd_llc_shared);
DEFINE_PER_CPU(struct sched_domain_shared __rcu *, sd_balance_shared);
DEFINE_PER_CPU(struct sched_domain __rcu *, sd_numa);
DEFINE_PER_CPU(struct sched_domain __rcu *, sd_asym_packing);
@ -680,38 +688,19 @@ static void update_top_cache_domain(int cpu)
int id = cpu;
int size = 1;
sd = lowest_flag_domain(cpu, SD_ASYM_CPUCAPACITY_FULL);
/*
* The shared object is attached to sd_asym_cpucapacity only when the
* asym domain is non-overlapping (i.e., not built from SD_NUMA).
* On overlapping (NUMA) asym domains we fall back to letting the
* SD_SHARE_LLC path own the shared object, so sd->shared may be NULL
* here.
*/
if (sd && sd->shared)
sds = sd->shared;
rcu_assign_pointer(per_cpu(sd_asym_cpucapacity, cpu), sd);
sd = highest_flag_domain(cpu, SD_SHARE_LLC);
if (sd) {
id = cpumask_first(sched_domain_span(sd));
size = cpumask_weight(sched_domain_span(sd));
/*
* If sd_asym_cpucapacity didn't claim the shared object,
* sd_llc must have one linked.
*/
if (!sds) {
WARN_ON_ONCE(!sd->shared);
sds = sd->shared;
}
/* If sd_llc exists, sd_llc_shared should exist too. */
WARN_ON_ONCE(!sd->shared);
sds = sd->shared;
}
rcu_assign_pointer(per_cpu(sd_llc, cpu), sd);
per_cpu(sd_llc_size, cpu) = size;
per_cpu(sd_llc_id, cpu) = id;
rcu_assign_pointer(per_cpu(sd_balance_shared, cpu), sds);
rcu_assign_pointer(per_cpu(sd_llc_shared, cpu), sds);
sd = lowest_flag_domain(cpu, SD_CLUSTER);
if (sd)
@ -729,6 +718,20 @@ static void update_top_cache_domain(int cpu)
sd = highest_flag_domain(cpu, SD_ASYM_PACKING);
rcu_assign_pointer(per_cpu(sd_asym_packing, cpu), sd);
sd = lowest_flag_domain(cpu, SD_ASYM_CPUCAPACITY_FULL);
/*
* The shared object is attached to sd_asym_cpucapacity only when the
* asym domain is non-overlapping (i.e., not built from SD_NUMA).
* On overlapping (NUMA) asym domains we fall back to letting the
* SD_SHARE_LLC path own the shared object, so sd->shared may be NULL
* here.
*/
if (sd && sd->shared)
sds = sd->shared;
rcu_assign_pointer(per_cpu(sd_asym_cpucapacity, cpu), sd);
rcu_assign_pointer(per_cpu(sd_balance_shared, cpu), sds);
}
/*
@ -777,10 +780,20 @@ cpu_attach_domain(struct sched_domain *sd, struct root_domain *rd, int cpu)
if (sd && sd_degenerate(sd)) {
tmp = sd;
sd = sd->parent;
destroy_sched_domain(tmp);
if (sd) {
struct sched_group *sg = sd->groups;
#ifdef CONFIG_SCHED_CACHE
/* move buffer to parent as child is being destroyed */
sd->llc_counts = tmp->llc_counts;
sd->llc_max = tmp->llc_max;
sd->llc_bytes = tmp->llc_bytes;
/* make sure destroy_sched_domain() does not free it */
tmp->llc_counts = NULL;
tmp->llc_max = 0;
tmp->llc_bytes = 0;
#endif
/*
* sched groups hold the flags of the child sched
* domain for convenience. Clear such flags since
@ -792,6 +805,8 @@ cpu_attach_domain(struct sched_domain *sd, struct root_domain *rd, int cpu)
sd->child = NULL;
}
destroy_sched_domain(tmp);
}
sched_domain_debug(sd, cpu);
@ -819,6 +834,239 @@ enum s_alloc {
sa_none,
};
#ifdef CONFIG_SCHED_CACHE
/* hardware support for cache aware scheduling */
DEFINE_STATIC_KEY_FALSE(sched_cache_present);
/*
* Indicator of whether cache aware scheduling
* is active, used by the scheduler.
*/
DEFINE_STATIC_KEY_FALSE(sched_cache_active);
/* user wants cache aware scheduling [0 or 1] */
int sysctl_sched_cache_user = 1;
/*
* Get the effective LLC size in bytes that @cpu's bottom sched_domain
* can use. A CPU within a cpuset partition can only use a proportion
* of the physical LLC, scaled by the ratio of the partition's span
* weight to the hardware LLC sharing weight. @sd should be the
* topmost domain with SD_SHARE_LLC.
*
* Returns 0 if cacheinfo is not yet populated. This happens during
* early boot when build_sched_domains() runs before the generic
* cacheinfo framework has been initialized (cacheinfo_cpu_online()
* is a device_initcall cpuhp callback). In that case,
* cacheinfo_cpu_online() will later call sched_update_llc_bytes()
* to fill in the bottom domain's llc_bytes once the cache attributes
* are available.
*/
static unsigned long get_effective_llc_bytes(int cpu,
struct sched_domain *sd)
{
struct cacheinfo *ci;
unsigned int hw_weight;
ci = get_cpu_cacheinfo_llc(cpu);
if (!ci)
return 0;
hw_weight = cpumask_weight(&ci->shared_cpu_map);
if (!hw_weight)
return 0;
return div_u64((u64)ci->size * sd->span_weight, hw_weight);
}
static bool alloc_sd_llc(const struct cpumask *cpu_map,
struct s_data *d)
{
struct sched_domain *sd, *top_llc, *parent;
unsigned int *p;
int i;
for_each_cpu(i, cpu_map) {
sd = *per_cpu_ptr(d->sd, i);
if (!sd)
goto err;
p = kcalloc_node(max_lid + 1, sizeof(unsigned int),
GFP_KERNEL, cpu_to_node(i));
if (!p)
goto err;
top_llc = sd;
/*
* Find the topmost SD_SHARE_LLC domain.
* Not yet attached to the CPU, so per_cpu(sd_llc, i)
* can not be used.
*/
while ((parent = rcu_dereference_protected(top_llc->parent, true)) &&
(parent->flags & SD_SHARE_LLC))
top_llc = parent;
if (top_llc->flags & SD_SHARE_LLC) {
sd->llc_max = max_lid + 1;
sd->llc_counts = p;
sd->llc_bytes = get_effective_llc_bytes(i, top_llc);
} else {
/* avoid memory leak */
kfree(p);
}
}
return true;
err:
for_each_cpu(i, cpu_map) {
sd = *per_cpu_ptr(d->sd, i);
if (sd) {
kfree(sd->llc_counts);
sd->llc_counts = NULL;
sd->llc_max = 0;
sd->llc_bytes = 0;
}
}
return false;
}
/*
* Enable/disable cache aware scheduling according to
* user input and the presence of hardware support.
*/
static void _sched_cache_active_set(void)
{
lockdep_assert_cpus_held();
lockdep_assert_held(&sched_domains_mutex);
/* hardware does not support */
if (!static_branch_likely(&sched_cache_present)) {
static_branch_disable_cpuslocked(&sched_cache_active);
if (sched_debug())
pr_info("%s: cache aware scheduling not supported on this platform\n", __func__);
return;
}
/*
* user wants it or not ?
* TBD: read before writing the static key.
* It is not in the critical path, leave as-is
* for now.
*/
if (sysctl_sched_cache_user) {
static_branch_enable_cpuslocked(&sched_cache_active);
if (sched_debug())
pr_info("%s: enabling cache aware scheduling\n", __func__);
} else {
static_branch_disable_cpuslocked(&sched_cache_active);
if (sched_debug())
pr_info("%s: disabling cache aware scheduling\n", __func__);
}
}
/* used by debugfs */
void sched_cache_active_set(void)
{
cpus_read_lock();
sched_domains_mutex_lock();
_sched_cache_active_set();
sched_domains_mutex_unlock();
cpus_read_unlock();
}
/*
* Update the bottom sched_domain's llc_bytes for @cpu and all its
* LLC siblings. Called from cacheinfo_cpu_online() or
* cacheinfo_cpu_pre_down() with cpu hotplug lock held.
*
* Note: get_effective_llc_bytes() returns 0 on PowerPC.
* thus cache aware scheduling is disabled on PowerPC for
* now. PowerPC does not use the generic cacheinfo framework --
* it has its own cacheinfo with a separate struct cache hierarchy
* and does not populates the per-CPU struct cpu_cacheinfo array
* that get_cpu_cacheinfo_llc() reads.
*/
void sched_update_llc_bytes(unsigned int cpu)
{
struct sched_domain *sd, *sdp;
unsigned int i;
sched_domains_mutex_lock();
sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, cpu));
if (!sdp)
goto unlock;
/*
* ci->shared_cpu_map is built incrementally as CPUs come
* online, so the first CPU in an LLC initially sees
* hw_weight == 1 and computes an inflated llc_bytes in
* get_effective_llc_bytes(). Re-evaluating every LLC
* sibling on each online event corrects this once the full
* shared_cpu_map is known.
*/
for_each_cpu(i, sched_domain_span(sdp)) {
sd = rcu_dereference_sched_domain(cpu_rq(i)->sd);
if (sd)
sd->llc_bytes = get_effective_llc_bytes(i, sdp);
}
unlock:
sched_domains_mutex_unlock();
}
static void sched_cache_set(bool has_multi_llcs)
{
/*
* TBD: check before writing to it. sched domain rebuild
* is not in the critical path, leave as-is for now.
*/
if (has_multi_llcs)
static_branch_enable_cpuslocked(&sched_cache_present);
else
static_branch_disable_cpuslocked(&sched_cache_present);
_sched_cache_active_set();
}
#else
static bool alloc_sd_llc(const struct cpumask *cpu_map,
struct s_data *d)
{
return false;
}
static inline void sched_cache_set(bool has_multi_llcs) { }
#endif
/*
* Return true if @sd belongs to an LLC group whose enclosing
* partition spans more than one LLC. @sd must be the topmost
* SD_SHARE_LLC domain.
*
* Any duplicated parent domains with the same span as @sd are
* skipped: before cpu_attach_domain() degeneration these still
* exist, after degeneration the loop is a no-op. This makes the
* helper usable both during sched domain build and against an
* already-attached domain tree.
*
* Note: For systems with a single LLC per node, cache-aware
* scheduling is still enabled when multiple nodes exist.
* However, NUMA balancing decisions take precedence over
* cache-aware scheduling. Conversely, if there is only one
* LLC per partition, cache-aware scheduling should be disabled.
*/
static bool sd_in_multi_llcs(struct sched_domain *sd)
{
struct sched_domain *sdp = sd->parent;
/* it does not make sense to aggregate to 1 CPU */
if (sd->span_weight == 1)
return false;
while (sdp && sdp->span_weight == sd->span_weight)
sdp = sdp->parent;
return !!sdp;
}
/*
* Return the canonical balance CPU for this group, this is the first CPU
* of this group that's also in the balance mask.
@ -1803,6 +2051,11 @@ const struct cpumask *tl_mc_mask(struct sched_domain_topology_level *tl, int cpu
{
return cpu_coregroup_mask(cpu);
}
#define llc_mask(cpu) cpu_coregroup_mask(cpu)
#else
#define llc_mask(cpu) cpumask_of(cpu)
#endif
const struct cpumask *tl_pkg_mask(struct sched_domain_topology_level *tl, int cpu)
@ -2711,14 +2964,71 @@ static bool claim_asym_sched_domain_shared(struct s_data *d, int cpu)
return true;
}
static int __sched_domains_alloc_llc_id(void)
{
int lid, max;
lockdep_assert_held(&sched_domains_mutex);
lid = cpumask_first_zero(sched_domains_llc_id_allocmask);
/*
* llc_id space should never grow larger than the
* possible number of CPUs in the system.
*/
if (lid >= nr_cpu_ids)
return -1;
__cpumask_set_cpu(lid, sched_domains_llc_id_allocmask);
max = cpumask_last(sched_domains_llc_id_allocmask);
if (max > max_lid)
max_lid = max;
return lid;
}
static void __sched_domains_free_llc_id(int cpu)
{
int i, lid, max;
lockdep_assert_held(&sched_domains_mutex);
lid = per_cpu(sd_llc_id, cpu);
if (lid == -1 || lid >= nr_cpu_ids)
return;
per_cpu(sd_llc_id, cpu) = -1;
for_each_cpu(i, llc_mask(cpu)) {
/* An online CPU owns the llc_id. */
if (per_cpu(sd_llc_id, i) == lid)
return;
}
__cpumask_clear_cpu(lid, sched_domains_llc_id_allocmask);
max = cpumask_last(sched_domains_llc_id_allocmask);
/* shrink max lid to save memory */
if (max < max_lid)
max_lid = max;
}
void sched_domains_free_llc_id(int cpu)
{
sched_domains_mutex_lock();
__sched_domains_free_llc_id(cpu);
sched_domains_mutex_unlock();
}
/*
* Build sched domains for a given set of CPUs and attach the sched domains
* to the individual CPUs
*/
static int
build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr)
build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr,
bool *multi_llcs)
{
enum s_alloc alloc_state = sa_none;
bool has_multi_llcs = false;
struct sched_domain *sd;
struct s_data d;
struct rq *rq = NULL;
@ -2736,6 +3046,7 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att
/* Set up domains for CPUs specified by the cpu_map: */
for_each_cpu(i, cpu_map) {
struct sched_domain_topology_level *tl;
int lid;
sd = NULL;
for_each_sd_topology(tl) {
@ -2749,6 +3060,29 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att
if (cpumask_equal(cpu_map, sched_domain_span(sd)))
break;
}
lid = per_cpu(sd_llc_id, i);
if (lid == -1) {
/* try to reuse the llc_id of its siblings */
for (int j = cpumask_first(llc_mask(i));
j < nr_cpu_ids;
j = cpumask_next(j, llc_mask(i))) {
if (i == j)
continue;
lid = per_cpu(sd_llc_id, j);
if (lid != -1) {
per_cpu(sd_llc_id, i) = lid;
break;
}
}
/* a new LLC is detected */
if (lid == -1)
per_cpu(sd_llc_id, i) = __sched_domains_alloc_llc_id();
}
}
if (WARN_ON(!topology_span_sane(cpu_map)))
@ -2769,33 +3103,31 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att
}
for_each_cpu(i, cpu_map) {
bool asym_claimed = false;
sd = *per_cpu_ptr(d.sd, i);
if (!sd)
continue;
if (has_asym)
asym_claimed = claim_asym_sched_domain_shared(&d, i);
claim_asym_sched_domain_shared(&d, i);
/* First, find the topmost SD_SHARE_LLC domain */
while (sd->parent && (sd->parent->flags & SD_SHARE_LLC))
sd = sd->parent;
if (sd->flags & SD_SHARE_LLC) {
/*
* Initialize the sd->shared for SD_SHARE_LLC unless
* the asym path above already claimed it.
*/
if (!asym_claimed)
init_sched_domain_shared(&d, sd);
init_sched_domain_shared(&d, sd);
/*
* In presence of higher domains, adjust the
* NUMA imbalance stats for the hierarchy.
*/
if (IS_ENABLED(CONFIG_NUMA) && sd->parent)
adjust_numa_imbalance(sd);
if (sd->parent) {
if (IS_ENABLED(CONFIG_NUMA))
adjust_numa_imbalance(sd);
if (sd_in_multi_llcs(sd))
has_multi_llcs = true;
}
}
}
@ -2810,6 +3142,8 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att
init_sched_groups_capacity(i, sd);
}
alloc_sd_llc(cpu_map, &d);
/* Attach the domains */
rcu_read_lock();
for_each_cpu(i, cpu_map) {
@ -2834,6 +3168,7 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att
ret = 0;
error:
*multi_llcs = has_multi_llcs;
__free_domain_allocs(&d, alloc_state, cpu_map);
return ret;
@ -2896,8 +3231,10 @@ void free_sched_domains(cpumask_var_t doms[], unsigned int ndoms)
*/
int __init sched_init_domains(const struct cpumask *cpu_map)
{
bool multi_llcs;
int err;
zalloc_cpumask_var(&sched_domains_llc_id_allocmask, GFP_KERNEL);
zalloc_cpumask_var(&sched_domains_tmpmask, GFP_KERNEL);
zalloc_cpumask_var(&sched_domains_tmpmask2, GFP_KERNEL);
zalloc_cpumask_var(&fallback_doms, GFP_KERNEL);
@ -2909,7 +3246,9 @@ int __init sched_init_domains(const struct cpumask *cpu_map)
if (!doms_cur)
doms_cur = &fallback_doms;
cpumask_and(doms_cur[0], cpu_map, housekeeping_cpumask(HK_TYPE_DOMAIN));
err = build_sched_domains(doms_cur[0], NULL);
err = build_sched_domains(doms_cur[0], NULL, &multi_llcs);
if (!err)
sched_cache_set(multi_llcs);
return err;
}
@ -2982,6 +3321,7 @@ static void partition_sched_domains_locked(int ndoms_new, cpumask_var_t doms_new
struct sched_domain_attr *dattr_new)
{
bool __maybe_unused has_eas = false;
bool has_multi_llcs = false, multi_llcs;
int i, j, n;
int new_topology;
@ -3031,14 +3371,41 @@ static void partition_sched_domains_locked(int ndoms_new, cpumask_var_t doms_new
for (i = 0; i < ndoms_new; i++) {
for (j = 0; j < n && !new_topology; j++) {
if (cpumask_equal(doms_new[i], doms_cur[j]) &&
dattrs_equal(dattr_new, i, dattr_cur, j))
dattrs_equal(dattr_new, i, dattr_cur, j)) {
/*
* Reused partition has to be taken care
* of here, because there could be a corner
* case that if the reused partition is skipped
* and only new partition is considered, an
* incorrect has_multi_llcs would be set. For
* example:
* If the only multi-LLC partition is reused
* and a new single-LLC partition is built,
* sched_cache_set(false) disables cache-aware
* scheduling globally despite the reused
* multi-LLC partition still being active.
*/
struct sched_domain *sd;
int cpu = cpumask_first(doms_cur[j]);
guard(rcu)();
sd = rcu_dereference(cpu_rq(cpu)->sd);
while (sd && sd->parent && (sd->parent->flags & SD_SHARE_LLC))
sd = sd->parent;
if (sd && (sd->flags & SD_SHARE_LLC) && sd->parent &&
sd_in_multi_llcs(sd))
has_multi_llcs = true;
goto match2;
}
}
/* No match - add a new doms_new */
build_sched_domains(doms_new[i], dattr_new ? dattr_new + i : NULL);
build_sched_domains(doms_new[i], dattr_new ? dattr_new + i : NULL,
&multi_llcs);
has_multi_llcs |= multi_llcs;
match2:
;
}
sched_cache_set(has_multi_llcs);
#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL)
/* Build perf domains: */