[RFC PATCH] kick sleeping idle CPUS on cpu_idle_wait

From: Steven Rostedt
Date: Mon Jan 07 2008 - 21:28:42 EST



Sometimes cpu_idle_wait gets stuck because it might miss CPUS that are
already in idle, have no tasks waiting to run and have no interrupts
going to them. This is common on bootup when switching cpu idle
governors.

This patch accounts for CPUs already in idle.

Background:
-----------
I notice this while developing the mcount patches, that every once in a
while the system would hang. Looking deeper, the hang was always at boot
up when registering init_menu of the cpu_idle menu governor. Talking
with Thomas Gliexner, we discovered that one of the CPUS had no timer
events scheduled for it and it was in idle (running with NO_HZ). So the
CPU would not set the cpu_idle_state bit.

Hitting sysrq-t a few times would eventually route the interrupt to the
stuck CPU and the system would continue.

Note, I would have used the PDA isidle but that is set after the
cpu_idle_state bit is cleared, and would leave a window open where we
may miss being kicked.

hmm, looking closer at this, we still have a small race window between
clearing the cpu_idle_state and disabling interrupts (hence the RFC).

CPU0: CPU 1:
--------- ---------
cpu_idle_wait(): cpu_idle():
| __cpu_cpu_var(is_idle) = 1;
| if (__get_cpu_var(cpu_idle_state)) /* == 0 */
per_cpu(cpu_idle_state, 1) = 1; |
if (per_cpu(is_idle, 1)) /* == 1 */ |
smp_call_function(1) |
| receives ipi and runs do_nothing.
wait on map == empty idle();
/* waits forever */


So really we need interrupts off for most of this then. One might think
that we could simply clear the cpu_idle_state from do_nothing, but I'm
assuming that cpu_idle governors can be removed, and this might cause a
race that a governor might be used after the module was removed.

One solution I think is that we could keep calling do_nothing to all the
bits of map that are still set in the cpu_idle_wait() loop. That would
work too.

Thoughts?


Signed-off-by: Steven Rostedt <srostedt@xxxxxxxxxx>
---
arch/x86/kernel/process_32.c | 36 ++++++++++++++++++++++++++++++++----
arch/x86/kernel/process_64.c | 28 ++++++++++++++++++++++++----
2 files changed, 56 insertions(+), 8 deletions(-)

Index: linus.git/arch/x86/kernel/process_32.c
===================================================================
--- linus.git.orig/arch/x86/kernel/process_32.c 2005-09-11 20:05:46.000000000 -0400
+++ linus.git/arch/x86/kernel/process_32.c 2008-01-07 20:42:24.000000000 -0500
@@ -83,6 +83,7 @@ unsigned long thread_saved_pc(struct tas
void (*pm_idle)(void);
EXPORT_SYMBOL(pm_idle);
static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
+static DEFINE_PER_CPU(unsigned int, in_idle);

void disable_hlt(void)
{
@@ -178,6 +179,12 @@ void cpu_idle(void)
/* endless idle loop with no priority at all */
while (1) {
tick_nohz_stop_sched_tick();
+ __get_cpu_var(in_idle) = 1;
+ /*
+ * Make sure in_idle is seen by others
+ * before cpu_idle_state is set (and read)
+ */
+ smp_mb();
while (!need_resched()) {
void (*idle)(void);

@@ -197,6 +204,7 @@ void cpu_idle(void)
__get_cpu_var(irq_stat).idle_timestamp = jiffies;
idle();
}
+ __get_cpu_var(in_idle) = 0;
tick_nohz_restart_sched_tick();
preempt_enable_no_resched();
schedule();
@@ -204,6 +212,10 @@ void cpu_idle(void)
}
}

+static void do_nothing(void *unused)
+{
+}
+
void cpu_idle_wait(void)
{
unsigned int cpu, this_cpu = get_cpu();
@@ -212,15 +224,31 @@ void cpu_idle_wait(void)
set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
put_cpu();

+ for_each_online_cpu(cpu)
+ per_cpu(cpu_idle_state, cpu) = 1;
+
+ __get_cpu_var(cpu_idle_state) = 0;
+
+ /*
+ * Make cpu_idle_state viewable by other CPUs
+ * and be able to read in_idle of other CPUs.
+ */
+ smp_mb();
+
+ /* Wake up any CPUs that are already in idle */
cpus_clear(map);
for_each_online_cpu(cpu) {
- per_cpu(cpu_idle_state, cpu) = 1;
- cpu_set(cpu, map);
+ if (per_cpu(in_idle, cpu))
+ cpu_set(cpu, map);
}
+ smp_call_function_mask(map, do_nothing, 0, 0);

- __get_cpu_var(cpu_idle_state) = 0;
+ cpus_clear(map);
+ for_each_online_cpu(cpu) {
+ if (per_cpu(cpu_idle_state, cpu))
+ cpu_set(cpu, map);
+ }

- wmb();
do {
ssleep(1);
for_each_online_cpu(cpu) {
Index: linus.git/arch/x86/kernel/process_64.c
===================================================================
--- linus.git.orig/arch/x86/kernel/process_64.c 2007-10-19 12:30:44.000000000 -0400
+++ linus.git/arch/x86/kernel/process_64.c 2008-01-07 20:47:34.000000000 -0500
@@ -65,6 +65,7 @@ EXPORT_SYMBOL(boot_option_idle_override)
void (*pm_idle)(void);
EXPORT_SYMBOL(pm_idle);
static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
+static DEFINE_PER_CPU(unsigned int, in_idle);

static ATOMIC_NOTIFIER_HEAD(idle_notifier);

@@ -135,6 +136,10 @@ static void poll_idle (void)
cpu_relax();
}

+static void do_nothing(void *unused)
+{
+}
+
void cpu_idle_wait(void)
{
unsigned int cpu, this_cpu = get_cpu();
@@ -143,15 +148,30 @@ void cpu_idle_wait(void)
set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
put_cpu();

+ for_each_online_cpu(cpu)
+ per_cpu(cpu_idle_state, cpu) = 1;
+ __get_cpu_var(cpu_idle_state) = 0;
+
+ /*
+ * Make cpu_idle_state viewable by other CPUs
+ * and be able to read in_idle of other CPUs.
+ */
+ smp_mb();
+
+ /* Wake up any CPUs that are already in idle */
cpus_clear(map);
for_each_online_cpu(cpu) {
- per_cpu(cpu_idle_state, cpu) = 1;
- cpu_set(cpu, map);
+ if (per_cpu(in_idle, cpu))
+ cpu_set(cpu, map);
}
+ smp_call_function_mask(map, do_nothing, 0, 0);

- __get_cpu_var(cpu_idle_state) = 0;
+ cpus_clear(map);
+ for_each_online_cpu(cpu) {
+ if (per_cpu(cpu_idle_state, cpu))
+ cpu_set(cpu, map);
+ }

- wmb();
do {
ssleep(1);
for_each_online_cpu(cpu) {


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/