Re: [PATCH RT v2 2/3] sched: Lazy migrate_disable processing

From: Waiman Long
Date: Fri Oct 18 2019 - 14:12:13 EST


On 10/12/19 2:52 AM, Scott Wood wrote:
> Avoid overhead on the majority of migrate disable/enable sequences by
> only manipulating scheduler data (and grabbing the relevant locks) when
> the task actually schedules while migrate-disabled. A kernel build
> showed around a 10% reduction in system time (with CONFIG_NR_CPUS=512).
>
> Instead of cpuhp_pin_lock, CPU hotplug is handled by keeping a per-CPU
> count of the number of pinned tasks (including tasks which have not
> scheduled in the migrate-disabled section); takedown_cpu() will
> wait until that reaches zero (confirmed by take_cpu_down() in stop
> machine context to deal with races) before migrating tasks off of the
> cpu.
>
> To simplify synchronization, updating cpus_mask is no longer deferred
> until migrate_enable(). This lets us not have to worry about
> migrate_enable() missing the update if it's on the fast path (didn't
> schedule during the migrate disabled section). It also makes the code
> a bit simpler and reduces deviation from mainline.
>
> While the main motivation for this is the performance benefit, lazy
> migrate disable also eliminates the restriction on calling
> migrate_disable() while atomic but leaving the atomic region prior to
> calling migrate_enable() -- though this won't help with local_bh_disable()
> (and thus rcutorture) unless something similar is done with the recently
> added local_lock.
>
> Signed-off-by: Scott Wood <swood@xxxxxxxxxx>
> ---
> The speedup is smaller than before, due to commit 659252061477862f
> ("lib/smp_processor_id: Don't use cpumask_equal()") achieving
> an overlapping speedup.
> ---
> include/linux/cpu.h | 4 --
> include/linux/sched.h | 11 +--
> init/init_task.c | 4 ++
> kernel/cpu.c | 103 +++++++++++-----------------
> kernel/sched/core.c | 180 ++++++++++++++++++++-----------------------------
> kernel/sched/sched.h | 4 ++
> lib/smp_processor_id.c | 3 +
> 7 files changed, 128 insertions(+), 181 deletions(-)
>
> diff --git a/include/linux/cpu.h b/include/linux/cpu.h
> index f4a772c12d14..2df500fdcbc4 100644
> --- a/include/linux/cpu.h
> +++ b/include/linux/cpu.h
> @@ -113,8 +113,6 @@ static inline void cpu_maps_update_done(void)
> extern void cpu_hotplug_enable(void);
> void clear_tasks_mm_cpumask(int cpu);
> int cpu_down(unsigned int cpu);
> -extern void pin_current_cpu(void);
> -extern void unpin_current_cpu(void);
>
> #else /* CONFIG_HOTPLUG_CPU */
>
> @@ -126,8 +124,6 @@ static inline void cpus_read_unlock(void) { }
> static inline void lockdep_assert_cpus_held(void) { }
> static inline void cpu_hotplug_disable(void) { }
> static inline void cpu_hotplug_enable(void) { }
> -static inline void pin_current_cpu(void) { }
> -static inline void unpin_current_cpu(void) { }
>
> #endif /* !CONFIG_HOTPLUG_CPU */
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 7e892e727f12..c6872b38bf77 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -229,6 +229,8 @@
> extern long io_schedule_timeout(long timeout);
> extern void io_schedule(void);
>
> +int cpu_nr_pinned(int cpu);
> +
> /**
> * struct prev_cputime - snapshot of system and user cputime
> * @utime: time spent in user mode
> @@ -661,16 +663,13 @@ struct task_struct {
> cpumask_t cpus_mask;
> #if defined(CONFIG_SMP) && defined(CONFIG_PREEMPT_RT_BASE)
> int migrate_disable;
> - int migrate_disable_update;
> - int pinned_on_cpu;
> + bool migrate_disable_scheduled;
> # ifdef CONFIG_SCHED_DEBUG
> - int migrate_disable_atomic;
> + int pinned_on_cpu;
> # endif
> -
> #elif !defined(CONFIG_SMP) && defined(CONFIG_PREEMPT_RT_BASE)
> # ifdef CONFIG_SCHED_DEBUG
> int migrate_disable;
> - int migrate_disable_atomic;
> # endif
> #endif
> #ifdef CONFIG_PREEMPT_RT_FULL
> @@ -2074,4 +2073,6 @@ static inline void rseq_syscall(struct pt_regs *regs)
>
> #endif
>
> +extern struct task_struct *takedown_cpu_task;
> +
> #endif
> diff --git a/init/init_task.c b/init/init_task.c
> index e402413dc47d..c0c7618fd2fb 100644
> --- a/init/init_task.c
> +++ b/init/init_task.c
> @@ -81,6 +81,10 @@ struct task_struct init_task
> .cpus_ptr = &init_task.cpus_mask,
> .cpus_mask = CPU_MASK_ALL,
> .nr_cpus_allowed= NR_CPUS,
> +#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPT_RT_BASE) && \
> + defined(CONFIG_SCHED_DEBUG)
> + .pinned_on_cpu = -1,
> +#endif
> .mm = NULL,
> .active_mm = &init_mm,
> .restart_block = {
> diff --git a/kernel/cpu.c b/kernel/cpu.c
> index 25afa2bb1a2c..e1bf3c698a32 100644
> --- a/kernel/cpu.c
> +++ b/kernel/cpu.c
> @@ -76,11 +76,6 @@ static DEFINE_PER_CPU(struct cpuhp_cpu_state, cpuhp_state) = {
> .fail = CPUHP_INVALID,
> };
>
> -#if defined(CONFIG_HOTPLUG_CPU) && defined(CONFIG_PREEMPT_RT_FULL)
> -static DEFINE_PER_CPU(struct rt_rw_lock, cpuhp_pin_lock) = \
> - __RWLOCK_RT_INITIALIZER(cpuhp_pin_lock);
> -#endif
> -
> #if defined(CONFIG_LOCKDEP) && defined(CONFIG_SMP)
> static struct lockdep_map cpuhp_state_up_map =
> STATIC_LOCKDEP_MAP_INIT("cpuhp_state-up", &cpuhp_state_up_map);
> @@ -287,57 +282,6 @@ void cpu_maps_update_done(void)
>
> #ifdef CONFIG_HOTPLUG_CPU
>
> -/**
> - * pin_current_cpu - Prevent the current cpu from being unplugged
> - */
> -void pin_current_cpu(void)
> -{
> -#ifdef CONFIG_PREEMPT_RT_FULL
> - struct rt_rw_lock *cpuhp_pin;
> - unsigned int cpu;
> - int ret;
> -
> -again:
> - cpuhp_pin = this_cpu_ptr(&cpuhp_pin_lock);
> - ret = __read_rt_trylock(cpuhp_pin);
> - if (ret) {
> - current->pinned_on_cpu = smp_processor_id();
> - return;
> - }
> - cpu = smp_processor_id();
> - preempt_lazy_enable();
> - preempt_enable();
> -
> - sleeping_lock_inc();
> - __read_rt_lock(cpuhp_pin);
> - sleeping_lock_dec();
> -
> - preempt_disable();
> - preempt_lazy_disable();
> - if (cpu != smp_processor_id()) {
> - __read_rt_unlock(cpuhp_pin);
> - goto again;
> - }
> - current->pinned_on_cpu = cpu;
> -#endif
> -}
> -
> -/**
> - * unpin_current_cpu - Allow unplug of current cpu
> - */
> -void unpin_current_cpu(void)
> -{
> -#ifdef CONFIG_PREEMPT_RT_FULL
> - struct rt_rw_lock *cpuhp_pin = this_cpu_ptr(&cpuhp_pin_lock);
> -
> - if (WARN_ON(current->pinned_on_cpu != smp_processor_id()))
> - cpuhp_pin = per_cpu_ptr(&cpuhp_pin_lock, current->pinned_on_cpu);
> -
> - current->pinned_on_cpu = -1;
> - __read_rt_unlock(cpuhp_pin);
> -#endif
> -}
> -
> DEFINE_STATIC_PERCPU_RWSEM(cpu_hotplug_lock);
>
> void cpus_read_lock(void)
> @@ -895,6 +839,15 @@ static int take_cpu_down(void *_param)
> int err, cpu = smp_processor_id();
> int ret;
>
> +#ifdef CONFIG_PREEMPT_RT_BASE
> + /*
> + * If any tasks disabled migration before we got here,
> + * go back and sleep again.
> + */
> + if (cpu_nr_pinned(cpu))
> + return -EAGAIN;
> +#endif
> +
> /* Ensure this CPU doesn't handle any more interrupts. */
> err = __cpu_disable();
> if (err < 0)
> @@ -924,11 +877,10 @@ static int take_cpu_down(void *_param)
> return 0;
> }
>
> +struct task_struct *takedown_cpu_task;
> +
> static int takedown_cpu(unsigned int cpu)
> {
> -#ifdef CONFIG_PREEMPT_RT_FULL
> - struct rt_rw_lock *cpuhp_pin = per_cpu_ptr(&cpuhp_pin_lock, cpu);
> -#endif
> struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);
> int err;
>
> @@ -941,17 +893,38 @@ static int takedown_cpu(unsigned int cpu)
> */
> irq_lock_sparse();
>
> -#ifdef CONFIG_PREEMPT_RT_FULL
> - __write_rt_lock(cpuhp_pin);
> +#ifdef CONFIG_PREEMPT_RT_BASE
> + WARN_ON_ONCE(takedown_cpu_task);
> + takedown_cpu_task = current;

I don't know how likely it is for more than one task calling
takedown_cpu() concurrently. But if that can happen, there is a
possibility of missed wakeup.

The current code is fragile. How about something like

while (cmpxchg(&takedown_cpu_task, NULL, current) {
ÂÂÂ WARN_ON_ONCE(1);
ÂÂÂ schedule();
}

Cheers,
Longman