diff options
| author | Roland McGrath <roland@redhat.com> | 2004-04-19 17:20:06 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2004-04-19 17:20:06 -0700 |
| commit | f3276a186a0cb1157d771768bf5e79f020e4c746 (patch) | |
| tree | 8789a1ed80aee4b622dc9abd29a2c78b26537f1a /kernel | |
| parent | b4389817f141203a4cd10ffc7890dd9139df65e7 (diff) | |
[PATCH] fix for potential deadlock after posix-timers change
Ulrich has been working on the glibc code using posix-timers and
stressing it more now than it has before. He ran into an SMP deadlock
on process exit in the case there are pending queued signals from a
timer.
The deadlock arises because in the path through exit_itimers, the
tasklist_lock is already held (for writing). When a timer is being
deleted, sigqueue_free will try to take it (for reading) in the case
where that timer has a pending signal queued on somebody's queue. This
patch avoids the problem by making sure the queues are flushed before
calling exit_itimers, thus ensuring its code path won't try to take
tasklist_lock.
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/signal.c | 21 |
1 files changed, 19 insertions, 2 deletions
diff --git a/kernel/signal.c b/kernel/signal.c index 0232084c1f65..a9181552a76e 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -352,10 +352,8 @@ void __exit_signal(struct task_struct *tsk) if (tsk == sig->curr_target) sig->curr_target = next_thread(tsk); tsk->signal = NULL; - exit_itimers(sig); spin_unlock(&sighand->siglock); flush_sigqueue(&sig->shared_pending); - kmem_cache_free(signal_cachep, sig); } else { /* * If there is any task waiting for the group exit @@ -369,9 +367,28 @@ void __exit_signal(struct task_struct *tsk) sig->curr_target = next_thread(tsk); tsk->signal = NULL; spin_unlock(&sighand->siglock); + sig = NULL; /* Marker for below. */ } clear_tsk_thread_flag(tsk,TIF_SIGPENDING); flush_sigqueue(&tsk->pending); + if (sig) { + /* + * We are cleaning up the signal_struct here. We delayed + * calling exit_itimers until after flush_sigqueue, just in + * case our thread-local pending queue contained a queued + * timer signal that would have been cleared in + * exit_itimers. When that called sigqueue_free, it would + * attempt to re-take the tasklist_lock and deadlock. This + * can never happen if we ensure that all queues the + * timer's signal might be queued on have been flushed + * first. The shared_pending queue, and our own pending + * queue are the only queues the timer could be on, since + * there are no other threads left in the group and timer + * signals are constrained to threads inside the group. + */ + exit_itimers(sig); + kmem_cache_free(signal_cachep, sig); + } } void exit_signal(struct task_struct *tsk) |
