sched/core: Wait for tasks being pushed away on hotplug
authorThomas Gleixner <tglx@linutronix.de>
Mon, 14 Sep 2020 12:47:28 +0000 (14:47 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Tue, 10 Nov 2020 17:38:58 +0000 (18:38 +0100)
RT kernels need to ensure that all tasks which are not per CPU kthreads
have left the outgoing CPU to guarantee that no tasks are force migrated
within a migrate disabled section.

There is also some desire to (ab)use fine grained CPU hotplug control to
clear a CPU from active state to force migrate tasks which are not per CPU
kthreads away for power control purposes.

Add a mechanism which waits until all tasks which should leave the CPU
after the CPU active flag is cleared have moved to a different online CPU.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Valentin Schneider <valentin.schneider@arm.com>
Reviewed-by: Daniel Bristot de Oliveira <bristot@redhat.com>
Link: https://lkml.kernel.org/r/20201023102346.377836842@infradead.org
kernel/sched/core.c
kernel/sched/sched.h

index 1f8bfc9..e1093c4 100644 (file)
@@ -6896,8 +6896,21 @@ static void balance_push(struct rq *rq)
         * Both the cpu-hotplug and stop task are in this case and are
         * required to complete the hotplug process.
         */
-       if (is_per_cpu_kthread(push_task))
+       if (is_per_cpu_kthread(push_task)) {
+               /*
+                * If this is the idle task on the outgoing CPU try to wake
+                * up the hotplug control thread which might wait for the
+                * last task to vanish. The rcuwait_active() check is
+                * accurate here because the waiter is pinned on this CPU
+                * and can't obviously be running in parallel.
+                */
+               if (!rq->nr_running && rcuwait_active(&rq->hotplug_wait)) {
+                       raw_spin_unlock(&rq->lock);
+                       rcuwait_wake_up(&rq->hotplug_wait);
+                       raw_spin_lock(&rq->lock);
+               }
                return;
+       }
 
        get_task_struct(push_task);
        /*
@@ -6928,6 +6941,20 @@ static void balance_push_set(int cpu, bool on)
        rq_unlock_irqrestore(rq, &rf);
 }
 
+/*
+ * Invoked from a CPUs hotplug control thread after the CPU has been marked
+ * inactive. All tasks which are not per CPU kernel threads are either
+ * pushed off this CPU now via balance_push() or placed on a different CPU
+ * during wakeup. Wait until the CPU is quiescent.
+ */
+static void balance_hotplug_wait(void)
+{
+       struct rq *rq = this_rq();
+
+       rcuwait_wait_event(&rq->hotplug_wait, rq->nr_running == 1,
+                          TASK_UNINTERRUPTIBLE);
+}
+
 #else
 
 static inline void balance_push(struct rq *rq)
@@ -6938,6 +6965,10 @@ static inline void balance_push_set(int cpu, bool on)
 {
 }
 
+static inline void balance_hotplug_wait(void)
+{
+}
+
 #endif /* CONFIG_HOTPLUG_CPU */
 
 void set_rq_online(struct rq *rq)
@@ -7092,6 +7123,10 @@ int sched_cpu_deactivate(unsigned int cpu)
                return ret;
        }
        sched_domains_numa_masks_clear(cpu);
+
+       /* Wait for all non per CPU kernel threads to vanish. */
+       balance_hotplug_wait();
+
        return 0;
 }
 
@@ -7332,6 +7367,9 @@ void __init sched_init(void)
 
                rq_csd_init(rq, &rq->nohz_csd, nohz_csd_func);
 #endif
+#ifdef CONFIG_HOTPLUG_CPU
+               rcuwait_init(&rq->hotplug_wait);
+#endif
 #endif /* CONFIG_SMP */
                hrtick_rq_init(rq);
                atomic_set(&rq->nr_iowait, 0);
index a71ac84..c6f707a 100644 (file)
@@ -1004,6 +1004,10 @@ struct rq {
 
        /* This is used to determine avg_idle's max value */
        u64                     max_idle_balance_cost;
+
+#ifdef CONFIG_HOTPLUG_CPU
+       struct rcuwait          hotplug_wait;
+#endif
 #endif /* CONFIG_SMP */
 
 #ifdef CONFIG_IRQ_TIME_ACCOUNTING