The housekeeping cpumasks for kernel noise (HK_TYPE_KERNEL_NOISE) and managed interrupts (HK_TYPE_MANAGED_IRQ) are computed once at boot from the nohz_full= and isolcpus=managed_irq arguments. Dynamic CPU isolation driven by cpuset isolated partitions needs to update these masks after boot.
Add housekeeping_update_types() to recompute one or more housekeeping masks as (boot snapshot & ~isolated), publish them via RCU and free the old masks after a grace period. Introduce HK_TYPE_KERNEL_NOISE_BOOT and HK_TYPE_MANAGED_IRQ_BOOT to record the immutable boot configuration. Anchoring every update on the boot snapshot keeps the runtime mask a subset of the boot set and lets de-isolation restore the boot configuration exactly. When neither nohz_full= nor isolcpus=nohz was given at boot no boot snapshot exists, so cpu_possible_mask is the implicit boot set and HK_TYPE_KERNEL_NOISE is enabled on the first runtime call. In that case housekeeping_update_types() also calls sched_tick_offload_init() to allocate the tick offload percpu data that would otherwise only be allocated by housekeeping_init() when nohz_full= is present at boot. Remove __init from sched_tick_offload_init() and guard it with a tick_work_cpu check so it is safe to call at runtime. Co-developed-by: Qiliang Yuan <[email protected]> Signed-off-by: Qiliang Yuan <[email protected]> Signed-off-by: Jing Wu <[email protected]> --- include/linux/sched/isolation.h | 32 ++++++- kernel/sched/core.c | 4 +- kernel/sched/isolation.c | 197 +++++++++++++++++++++++++++++++++++----- kernel/sched/sched.h | 2 +- 4 files changed, 209 insertions(+), 26 deletions(-) diff --git a/include/linux/sched/isolation.h b/include/linux/sched/isolation.h index cf0fd03dd7a24..70602a74c1410 100644 --- a/include/linux/sched/isolation.h +++ b/include/linux/sched/isolation.h @@ -14,10 +14,26 @@ enum hk_type { * is always a subset of HK_TYPE_DOMAIN_BOOT. */ HK_TYPE_DOMAIN, - /* Inverse of boot-time isolcpus=managed_irq argument */ - HK_TYPE_MANAGED_IRQ, - /* Inverse of boot-time nohz_full= or isolcpus=nohz arguments */ + + /* + * Inverse of the boot-time nohz_full= or isolcpus=nohz arguments. + * When neither is given, DHM still records cpu_possible_mask here so + * that kernel-noise isolation can be enabled purely at runtime. + */ + HK_TYPE_KERNEL_NOISE_BOOT, + /* + * A subset of HK_TYPE_KERNEL_NOISE_BOOT: it may exclude additional + * CPUs isolated at runtime via cpuset isolated partitions. + */ HK_TYPE_KERNEL_NOISE, + + /* Inverse of the boot-time isolcpus=managed_irq argument */ + HK_TYPE_MANAGED_IRQ_BOOT, + /* + * A subset of HK_TYPE_MANAGED_IRQ_BOOT: it may exclude additional + * CPUs isolated at runtime via cpuset isolated partitions. + */ + HK_TYPE_MANAGED_IRQ, HK_TYPE_MAX, /* @@ -40,10 +56,13 @@ enum hk_type { DECLARE_STATIC_KEY_FALSE(housekeeping_overridden); extern int housekeeping_any_cpu(enum hk_type type); extern const struct cpumask *housekeeping_cpumask(enum hk_type type); +extern const struct cpumask *housekeeping_cpumask_rcu(enum hk_type type); extern bool housekeeping_enabled(enum hk_type type); extern void housekeeping_affine(struct task_struct *t, enum hk_type type); extern bool housekeeping_test_cpu(int cpu, enum hk_type type); extern int housekeeping_update(struct cpumask *isol_mask); +extern int housekeeping_update_types(unsigned long type_mask, + struct cpumask *isol_mask); extern void __init housekeeping_init(void); #else @@ -58,6 +77,11 @@ static inline const struct cpumask *housekeeping_cpumask(enum hk_type type) return cpu_possible_mask; } +static inline const struct cpumask *housekeeping_cpumask_rcu(enum hk_type type) +{ + return cpu_possible_mask; +} + static inline bool housekeeping_enabled(enum hk_type type) { return false; @@ -72,6 +96,8 @@ static inline bool housekeeping_test_cpu(int cpu, enum hk_type type) } static inline int housekeeping_update(struct cpumask *isol_mask) { return 0; } +static inline int housekeeping_update_types(unsigned long type_mask, + struct cpumask *isol_mask) { return 0; } static inline void housekeeping_init(void) { } #endif /* CONFIG_CPU_ISOLATION */ diff --git a/kernel/sched/core.c b/kernel/sched/core.c index b8871449d3c69..79c3349f65bb4 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5813,8 +5813,10 @@ static void sched_tick_stop(int cpu) } #endif /* CONFIG_HOTPLUG_CPU */ -int __init sched_tick_offload_init(void) +int sched_tick_offload_init(void) { + if (tick_work_cpu) + return 0; tick_work_cpu = alloc_percpu(struct tick_work); BUG_ON(!tick_work_cpu); return 0; diff --git a/kernel/sched/isolation.c b/kernel/sched/isolation.c index ef152d401fe20..4602c8d0108e4 100644 --- a/kernel/sched/isolation.c +++ b/kernel/sched/isolation.c @@ -12,10 +12,12 @@ #include "sched.h" enum hk_flags { - HK_FLAG_DOMAIN_BOOT = BIT(HK_TYPE_DOMAIN_BOOT), - HK_FLAG_DOMAIN = BIT(HK_TYPE_DOMAIN), - HK_FLAG_MANAGED_IRQ = BIT(HK_TYPE_MANAGED_IRQ), - HK_FLAG_KERNEL_NOISE = BIT(HK_TYPE_KERNEL_NOISE), + HK_FLAG_DOMAIN_BOOT = BIT(HK_TYPE_DOMAIN_BOOT), + HK_FLAG_DOMAIN = BIT(HK_TYPE_DOMAIN), + HK_FLAG_KERNEL_NOISE_BOOT = BIT(HK_TYPE_KERNEL_NOISE_BOOT), + HK_FLAG_KERNEL_NOISE = BIT(HK_TYPE_KERNEL_NOISE), + HK_FLAG_MANAGED_IRQ_BOOT = BIT(HK_TYPE_MANAGED_IRQ_BOOT), + HK_FLAG_MANAGED_IRQ = BIT(HK_TYPE_MANAGED_IRQ), }; DEFINE_STATIC_KEY_FALSE(housekeeping_overridden); @@ -34,25 +36,40 @@ bool housekeeping_enabled(enum hk_type type) } EXPORT_SYMBOL_GPL(housekeeping_enabled); +/* + * Types that can change at runtime via cpuset isolated partitions. + * Boot-only types (DOMAIN_BOOT) are always safe to read without lockdep. + */ +static bool housekeeping_type_can_change(enum hk_type type) +{ + switch (type) { + case HK_TYPE_DOMAIN: + case HK_TYPE_KERNEL_NOISE: + case HK_TYPE_MANAGED_IRQ: + return true; + default: + return false; + } +} + static bool housekeeping_dereference_check(enum hk_type type) { - if (IS_ENABLED(CONFIG_LOCKDEP) && type == HK_TYPE_DOMAIN) { - /* Cpuset isn't even writable yet? */ - if (system_state <= SYSTEM_SCHEDULING) - return true; + if (!IS_ENABLED(CONFIG_LOCKDEP) || !housekeeping_type_can_change(type)) + return true; - /* CPU hotplug write locked, so cpuset partition can't be overwritten */ - if (IS_ENABLED(CONFIG_HOTPLUG_CPU) && lockdep_is_cpus_write_held()) - return true; + /* Cpuset isn't even writable yet? */ + if (system_state <= SYSTEM_SCHEDULING) + return true; - /* Cpuset lock held, partitions not writable */ - if (IS_ENABLED(CONFIG_CPUSETS) && lockdep_is_cpuset_held()) - return true; + /* CPU hotplug write locked, so cpuset partition can't be overwritten */ + if (IS_ENABLED(CONFIG_HOTPLUG_CPU) && lockdep_is_cpus_write_held()) + return true; - return false; - } + /* Cpuset lock held, partitions not writable */ + if (IS_ENABLED(CONFIG_CPUSETS) && lockdep_is_cpuset_held()) + return true; - return true; + return false; } static inline struct cpumask *housekeeping_cpumask_dereference(enum hk_type type) @@ -75,12 +92,26 @@ const struct cpumask *housekeeping_cpumask(enum hk_type type) } EXPORT_SYMBOL_GPL(housekeeping_cpumask); +const struct cpumask *housekeeping_cpumask_rcu(enum hk_type type) +{ + const struct cpumask *mask = NULL; + + if (static_branch_unlikely(&housekeeping_overridden)) { + if (READ_ONCE(housekeeping.flags) & BIT(type)) + mask = rcu_dereference(housekeeping.cpumasks[type]); + } + if (!mask) + mask = cpu_possible_mask; + return mask; +} +EXPORT_SYMBOL_GPL(housekeeping_cpumask_rcu); + int housekeeping_any_cpu(enum hk_type type) { int cpu; if (static_branch_unlikely(&housekeeping_overridden)) { - if (housekeeping.flags & BIT(type)) { + if (READ_ONCE(housekeeping.flags) & BIT(type)) { cpu = sched_numa_find_closest(housekeeping_cpumask(type), smp_processor_id()); if (cpu < nr_cpu_ids) return cpu; @@ -162,6 +193,130 @@ int housekeeping_update(struct cpumask *isol_mask) return 0; } +/** + * housekeeping_update_types - Update housekeeping masks for specified types + * @type_mask: Bitmask of housekeeping types to update + * @isol_mask: CPUs being added to the isolation set + * + * For each type in @type_mask, compute the trial mask as + * (boot snapshot & ~@isol_mask), validate it against @cpu_online_mask, + * then swap the RCU mask pointer and free the old mask after + * synchronize_rcu(). Anchoring on the immutable boot snapshot + * (HK_TYPE_*_BOOT) keeps the runtime mask a subset of the boot set and + * lets de-isolation restore the boot configuration exactly. + * + * The updated mask only takes effect for subsystems as CPUs cycle + * through hotplug; callers isolate CPUs via the CPU hotplug machinery so + * that tick, RCU and interrupt state is reconfigured by the existing + * online/offline callbacks rather than reconfigured in place. + * + * HK_TYPE_KERNEL_NOISE also supports runtime first-enable: when neither + * nohz_full= nor isolcpus=nohz was given at boot, no boot snapshot + * exists, so cpu_possible_mask is the implicit boot set and the type + * flag is set in housekeeping.flags on the first call. + * + * Return: 0 on success, -ENOMEM on allocation failure, -EINVAL if + * a trial mask has no online CPUs. + */ +int housekeeping_update_types(unsigned long type_mask, + struct cpumask *isol_mask) +{ + struct cpumask *trials[HK_TYPE_MAX] = {}; + struct cpumask *old_masks[HK_TYPE_MAX] = {}; + enum hk_type type; + int ret = 0; + + for_each_set_bit(type, &type_mask, HK_TYPE_MAX) { + const struct cpumask *base; + + if (type == HK_TYPE_DOMAIN_BOOT) + continue; + if (!housekeeping_enabled(type)) { + /* + * HK_TYPE_KERNEL_NOISE supports runtime first-enable + * for DHM isolated partitions created without nohz_full= + * at boot. All other types must be boot-enabled. + */ + if (type != HK_TYPE_KERNEL_NOISE) + continue; + } + + /* + * Compute the trial mask relative to the immutable boot + * snapshot, never relative to the current (already shrunk) + * mask. Using the current mask would let it shrink + * monotonically across isolation/de-isolation cycles and would + * never recover CPUs once de-isolated. Anchoring on the boot + * snapshot keeps the runtime mask a subset of the boot set and + * lets de-isolation restore exactly the boot configuration. + * + * HK_TYPE_KERNEL_NOISE additionally supports runtime + * first-enable: when no nohz_full=/isolcpus=nohz was given at + * boot, no boot snapshot exists, so cpu_possible_mask is the + * implicit boot set. + */ + if (type == HK_TYPE_KERNEL_NOISE && + !(housekeeping.flags & HK_FLAG_KERNEL_NOISE_BOOT)) + base = cpu_possible_mask; + else if (type == HK_TYPE_KERNEL_NOISE) + base = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE_BOOT); + else if (type == HK_TYPE_MANAGED_IRQ) + base = housekeeping_cpumask(HK_TYPE_MANAGED_IRQ_BOOT); + else + base = housekeeping_cpumask(type); + trials[type] = kmalloc(cpumask_size(), GFP_KERNEL); + if (!trials[type]) { + ret = -ENOMEM; + goto err_free; + } + cpumask_andnot(trials[type], base, isol_mask); + if (!cpumask_intersects(trials[type], cpu_online_mask)) { + ret = -EINVAL; + goto err_free; + } + } + + if (!housekeeping.flags) { + ret = -EINVAL; + goto err_free; + } + + for_each_set_bit(type, &type_mask, HK_TYPE_MAX) { + if (!trials[type]) + continue; + old_masks[type] = housekeeping_cpumask_dereference(type); + /* First-time runtime enable: register the type now. */ + if (!housekeeping_enabled(type)) { + WRITE_ONCE(housekeeping.flags, + housekeeping.flags | BIT(type)); + /* + * HK_TYPE_KERNEL_NOISE first-enable at runtime + * (zero-boot-param path): tick offload percpu data + * was never allocated at boot since nohz_full= was + * absent. Allocate it now before CPUs cycle through + * hotplug and sched_tick_stop() dereferences + * tick_work_cpu. + */ + if (type == HK_TYPE_KERNEL_NOISE) + WARN_ON_ONCE(sched_tick_offload_init()); + } + rcu_assign_pointer(housekeeping.cpumasks[type], trials[type]); + trials[type] = NULL; + } + + synchronize_rcu(); + + for_each_set_bit(type, &type_mask, HK_TYPE_MAX) + kfree(old_masks[type]); + + return 0; + +err_free: + for_each_set_bit(type, &type_mask, HK_TYPE_MAX) + kfree(trials[type]); + return ret; +} + void __init housekeeping_init(void) { enum hk_type type; @@ -305,7 +460,7 @@ static int __init housekeeping_nohz_full_setup(char *str) { unsigned long flags; - flags = HK_FLAG_KERNEL_NOISE; + flags = HK_FLAG_KERNEL_NOISE | HK_FLAG_KERNEL_NOISE_BOOT; return housekeeping_setup(str, flags); } @@ -324,7 +479,7 @@ static int __init housekeeping_isolcpus_setup(char *str) */ if (!strncmp(str, "nohz,", 5)) { str += 5; - flags |= HK_FLAG_KERNEL_NOISE; + flags |= HK_FLAG_KERNEL_NOISE | HK_FLAG_KERNEL_NOISE_BOOT; continue; } @@ -336,7 +491,7 @@ static int __init housekeeping_isolcpus_setup(char *str) if (!strncmp(str, "managed_irq,", 12)) { str += 12; - flags |= HK_FLAG_MANAGED_IRQ; + flags |= HK_FLAG_MANAGED_IRQ | HK_FLAG_MANAGED_IRQ_BOOT; continue; } diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 9f63b15d309d1..9d54ac3f2251d 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2925,7 +2925,7 @@ extern void post_init_entity_util_avg(struct task_struct *p); #ifdef CONFIG_NO_HZ_FULL extern bool sched_can_stop_tick(struct rq *rq); -extern int __init sched_tick_offload_init(void); +extern int sched_tick_offload_init(void); /* * Tick may be needed by tasks in the runqueue depending on their policy and -- 2.43.0

