init, sched: Fix race between init and kthreadd

Ilya reported that on a very slow machine he could reliably
reproduce a race between forking init and kthreadd. We first
fork init so that it obtains pid-1, however since the scheduler
is already fully running at this point it can preempt and run
the init thread before we spawn and set kthreadd_task.

The init thread can then attempt spawning kthreads without
kthreadd being present which results in an OOPS.

Reported-by: Ilya Loginov <isloginov@gmail.com>
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
LKML-Reference: <1277736661.3561.110.camel@laptop>
Signed-off-by: Ingo Molnar <mingo@elte.hu>

authored by Peter Zijlstra and committed by Ingo Molnar b433c3d4 0d98bb26

+12
+12
init/main.c
··· 422 422 * gcc-3.4 accidentally inlines this function, so use noinline. 423 423 */ 424 424 425 + static __initdata DECLARE_COMPLETION(kthreadd_done); 426 + 425 427 static noinline void __init_refok rest_init(void) 426 428 __releases(kernel_lock) 427 429 { 428 430 int pid; 429 431 430 432 rcu_scheduler_starting(); 433 + /* 434 + * We need to spawn init first so that it obtains pid-1, however 435 + * the init task will end up wanting to create kthreads, which, if 436 + * we schedule it before we create kthreadd, will OOPS. 437 + */ 431 438 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 432 439 numa_default_policy(); 433 440 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 434 441 rcu_read_lock(); 435 442 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); 436 443 rcu_read_unlock(); 444 + complete(&kthreadd_done); 437 445 unlock_kernel(); 438 446 439 447 /* ··· 863 855 864 856 static int __init kernel_init(void * unused) 865 857 { 858 + /* 859 + * Wait until kthreadd is all set-up. 860 + */ 861 + wait_for_completion(&kthreadd_done); 866 862 lock_kernel(); 867 863 868 864 /*