summaryrefslogtreecommitdiff
path: root/kernel/fork.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@home.osdl.org>2004-01-07 19:46:52 -0800
committerLinus Torvalds <torvalds@home.osdl.org>2004-01-07 19:46:52 -0800
commitf7a1132cffbdf99ef1bd270e9b77f928697780a3 (patch)
tree740431cfcea4dce89c71ec7a57d76b6b05069ab0 /kernel/fork.c
parentf6fd6a01cb2eabd8110c15307dcb9fe5fce019e4 (diff)
Fix subtle fork() race that Ingo noticed.
We must not mark the process TASK_STOPPED early, because that might allow a signal to wake it up before we actually got to the "wake_up_forked_process()" state. Total confusion would happen. Make wake_up_forked_process() verify the new world order.
Diffstat (limited to 'kernel/fork.c')
-rw-r--r--kernel/fork.c12
1 files changed, 10 insertions, 2 deletions
diff --git a/kernel/fork.c b/kernel/fork.c
index 0603f230146b..8cea5daa08a0 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -918,7 +918,14 @@ struct task_struct *copy_process(unsigned long clone_flags,
p->thread_info->preempt_count = 1;
#endif
p->did_exec = 0;
- p->state = TASK_UNINTERRUPTIBLE;
+
+ /*
+ * We mark the process as running here, but have not actually
+ * inserted it onto the runqueue yet. This guarantees that
+ * nobody will actually run it, and a signal or other external
+ * event cannot wake it up and insert it on the runqueue either.
+ */
+ p->state = TASK_RUNNING;
copy_flags(clone_flags, p);
if (clone_flags & CLONE_IDLETASK)
@@ -1209,9 +1216,10 @@ long do_fork(unsigned long clone_flags,
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
- p->state = TASK_STOPPED;
if (!(clone_flags & CLONE_STOPPED))
wake_up_forked_process(p); /* do this last */
+ else
+ p->state = TASK_STOPPED;
++total_forks;
if (unlikely (trace)) {