summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoland McGrath <roland@redhat.com>2003-02-05 23:39:31 -0800
committerChristoph Hellwig <hch@lab343.munich.sgi.com>2003-02-05 23:39:31 -0800
commit202b74ebfa3aa288b3a306160febf478978ecee4 (patch)
tree12991171b0106b8953886a673f71770056cdaa2a
parent477c16ff9201e948366081cf944518d666093e0b (diff)
[PATCH] Make sys_wait4() more readable
I cleaned up sys_wait4; it was straightforward and I think a definite improvement. While at it, I noticed that one of the races I fixed in the TASK_STOPPED case actually can happen earlier. Between read_unlock and write_lock_irq, another thread could reap the process and make P invalid, so now I do get_task_struct before read_unlock and then the existing race checks catch all scenarios. Aside from the aforementioned race tweak, the code should be the same as in the previous patch (that Ingo and I have tested more thoroughly) modulo being moved into functions and some reformatting and comment changes. Oh, my old patch had one case where it failed to retake the read lock after a race bailout that I just noticed reading over it. That's fixed too. These exit fixes were something I noticed incidentally and spent less time on than the signals changes. Another few passes of eyeballs over them are certainly warranted. (In particular, there are code paths like that one that check for specific races that have probably never been seen in practice, so those code paths have never run once.)
-rw-r--r--kernel/exit.c263
1 files changed, 149 insertions, 114 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index 25281033be8d..1e23538c9a0e 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -814,11 +814,149 @@ static int eligible_child(pid_t pid, int options, task_t *p)
return 1;
}
+/*
+ * Handle sys_wait4 work for one task in state TASK_ZOMBIE. We hold
+ * read_lock(&tasklist_lock) on entry. If we return zero, we still hold
+ * the lock and this task is uninteresting. If we return nonzero, we have
+ * released the lock and the system call should return.
+ */
+static int wait_task_zombie(task_t *p, unsigned int *stat_addr, struct rusage *ru)
+{
+ unsigned long state;
+ int retval;
+
+ /*
+ * Try to move the task's state to DEAD
+ * only one thread is allowed to do this:
+ */
+ state = xchg(&p->state, TASK_DEAD);
+ if (state != TASK_ZOMBIE) {
+ BUG_ON(state != TASK_DEAD);
+ return 0;
+ }
+ if (unlikely(p->exit_signal == -1))
+ /*
+ * This can only happen in a race with a ptraced thread
+ * dying on another processor.
+ */
+ return 0;
+
+ /*
+ * Now we are sure this task is interesting, and no other
+ * thread can reap it because we set its state to TASK_DEAD.
+ */
+ read_unlock(&tasklist_lock);
+
+ retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
+ if (!retval && stat_addr) {
+ if (p->sig->group_exit)
+ retval = put_user(p->sig->group_exit_code, stat_addr);
+ else
+ retval = put_user(p->exit_code, stat_addr);
+ }
+ if (retval) {
+ p->state = TASK_ZOMBIE;
+ return retval;
+ }
+ retval = p->pid;
+ if (p->real_parent != p->parent) {
+ write_lock_irq(&tasklist_lock);
+ /* Double-check with lock held. */
+ if (p->real_parent != p->parent) {
+ __ptrace_unlink(p);
+ do_notify_parent(p, p->exit_signal);
+ p->state = TASK_ZOMBIE;
+ p = NULL;
+ }
+ write_unlock_irq(&tasklist_lock);
+ }
+ if (p != NULL)
+ release_task(p);
+ BUG_ON(!retval);
+ return retval;
+}
+
+/*
+ * Handle sys_wait4 work for one task in state TASK_STOPPED. We hold
+ * read_lock(&tasklist_lock) on entry. If we return zero, we still hold
+ * the lock and this task is uninteresting. If we return nonzero, we have
+ * released the lock and the system call should return.
+ */
+static int wait_task_stopped(task_t *p, int delayed_group_leader,
+ unsigned int *stat_addr, struct rusage *ru)
+{
+ int retval, exit_code;
+
+ if (!p->exit_code)
+ return 0;
+ if (delayed_group_leader && !(p->ptrace & PT_PTRACED) &&
+ p->sig && p->sig->group_stop_count > 0)
+ /*
+ * A group stop is in progress and this is the group leader.
+ * We won't report until all threads have stopped.
+ */
+ return 0;
+
+ /*
+ * Now we are pretty sure this task is interesting.
+ * Make sure it doesn't get reaped out from under us while we
+ * give up the lock and then examine it below. We don't want to
+ * keep holding onto the tasklist_lock while we call getrusage and
+ * possibly take page faults for user memory.
+ */
+ get_task_struct(p);
+ read_unlock(&tasklist_lock);
+ write_lock_irq(&tasklist_lock);
+
+ /*
+ * This uses xchg to be atomic with the thread resuming and setting
+ * it. It must also be done with the write lock held to prevent a
+ * race with the TASK_ZOMBIE case.
+ */
+ exit_code = xchg(&p->exit_code, 0);
+ if (unlikely(p->state > TASK_STOPPED)) {
+ /*
+ * The task resumed and then died. Let the next iteration
+ * catch it in TASK_ZOMBIE. Note that exit_code might
+ * already be zero here if it resumed and did _exit(0).
+ * The task itself is dead and won't touch exit_code again;
+ * other processors in this function are locked out.
+ */
+ p->exit_code = exit_code;
+ exit_code = 0;
+ }
+ if (unlikely(exit_code == 0)) {
+ /*
+ * Another thread in this function got to it first, or it
+ * resumed, or it resumed and then died.
+ */
+ write_unlock_irq(&tasklist_lock);
+ put_task_struct(p);
+ read_lock(&tasklist_lock);
+ return 0;
+ }
+
+ /* move to end of parent's list to avoid starvation */
+ remove_parent(p);
+ add_parent(p, p->parent);
+
+ write_unlock_irq(&tasklist_lock);
+
+ retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
+ if (!retval && stat_addr)
+ retval = put_user((exit_code << 8) | 0x7f, stat_addr);
+ if (!retval)
+ retval = p->pid;
+ put_task_struct(p);
+
+ BUG_ON(!retval);
+ return retval;
+}
+
asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)
{
DECLARE_WAITQUEUE(wait, current);
struct task_struct *tsk;
- unsigned long state;
int flag, retval;
if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL))
@@ -836,8 +974,6 @@ repeat:
int ret;
list_for_each(_p,&tsk->children) {
- int exit_code;
-
p = list_entry(_p,struct task_struct,sibling);
ret = eligible_child(pid, options, p);
@@ -847,125 +983,24 @@ repeat:
switch (p->state) {
case TASK_STOPPED:
- if (!p->exit_code)
- continue;
- if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED))
- continue;
- if (ret == 2 && !(p->ptrace & PT_PTRACED) &&
- p->sig && p->sig->group_stop_count > 0)
- /*
- * A group stop is in progress and
- * we are the group leader. We won't
- * report until all threads have
- * stopped.
- */
- continue;
- read_unlock(&tasklist_lock);
-
- /* move to end of parent's list to avoid starvation */
- write_lock_irq(&tasklist_lock);
- remove_parent(p);
- add_parent(p, p->parent);
-
- /*
- * This uses xchg to be atomic with
- * the thread resuming and setting it.
- * It must also be done with the write
- * lock held to prevent a race with the
- * TASK_ZOMBIE case (below).
- */
- exit_code = xchg(&p->exit_code, 0);
- if (unlikely(p->state > TASK_STOPPED)) {
- /*
- * The task resumed and then died.
- * Let the next iteration catch it
- * in TASK_ZOMBIE. Note that
- * exit_code might already be zero
- * here if it resumed and did
- * _exit(0). The task itself is
- * dead and won't touch exit_code
- * again; other processors in
- * this function are locked out.
- */
- p->exit_code = exit_code;
- exit_code = 0;
- }
- if (unlikely(exit_code == 0)) {
- /*
- * Another thread in this function
- * got to it first, or it resumed,
- * or it resumed and then died.
- */
- write_unlock_irq(&tasklist_lock);
+ if (!(options & WUNTRACED) &&
+ !(p->ptrace & PT_PTRACED))
continue;
- }
- /*
- * Make sure this doesn't get reaped out from
- * under us while we are examining it below.
- * We don't want to keep holding onto the
- * tasklist_lock while we call getrusage and
- * possibly take page faults for user memory.
- */
- get_task_struct(p);
- write_unlock_irq(&tasklist_lock);
- retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
- if (!retval && stat_addr)
- retval = put_user((exit_code << 8) | 0x7f, stat_addr);
- if (!retval)
- retval = p->pid;
- put_task_struct(p);
- goto end_wait4;
+ retval = wait_task_stopped(p, ret == 2,
+ stat_addr, ru);
+ if (retval != 0) /* He released the lock. */
+ goto end_wait4;
+ break;
case TASK_ZOMBIE:
/*
* Eligible but we cannot release it yet:
*/
if (ret == 2)
continue;
- /*
- * Try to move the task's state to DEAD
- * only one thread is allowed to do this:
- */
- state = xchg(&p->state, TASK_DEAD);
- if (state != TASK_ZOMBIE)
- continue;
- if (unlikely(p->exit_signal == -1))
- /*
- * This can only happen in a race with
- * a ptraced thread dying on another
- * processor.
- */
- continue;
- read_unlock(&tasklist_lock);
-
- retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
- if (!retval && stat_addr) {
- if (p->sig->group_exit)
- retval = put_user(p->sig->group_exit_code, stat_addr);
- else
- retval = put_user(p->exit_code, stat_addr);
- }
- if (retval) {
- p->state = TASK_ZOMBIE;
+ retval = wait_task_zombie(p, stat_addr, ru);
+ if (retval != 0) /* He released the lock. */
goto end_wait4;
- }
- retval = p->pid;
- if (p->real_parent != p->parent) {
- write_lock_irq(&tasklist_lock);
- /* Double-check with lock held. */
- if (p->real_parent != p->parent) {
- __ptrace_unlink(p);
- do_notify_parent(
- p, p->exit_signal);
- p->state = TASK_ZOMBIE;
- p = NULL;
- }
- write_unlock_irq(&tasklist_lock);
- }
- if (p != NULL)
- release_task(p);
- goto end_wait4;
- default:
- continue;
+ break;
}
}
if (!flag) {