summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/exit.c7
-rw-r--r--kernel/fork.c2
-rw-r--r--kernel/signal.c115
3 files changed, 72 insertions, 52 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index 4969d831d362..a3014674d725 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1260,16 +1260,17 @@ static int wait_task_continued(task_t *p, int noreap,
if (unlikely(!p->signal))
return 0;
- if (p->signal->stop_state >= 0)
+ if (!(p->signal->flags & SIGNAL_STOP_CONTINUED))
return 0;
spin_lock_irq(&p->sighand->siglock);
- if (p->signal->stop_state >= 0) { /* Re-check with the lock held. */
+ /* Re-check with the lock held. */
+ if (!(p->signal->flags & SIGNAL_STOP_CONTINUED)) {
spin_unlock_irq(&p->sighand->siglock);
return 0;
}
if (!noreap)
- p->signal->stop_state = 0;
+ p->signal->flags &= ~SIGNAL_STOP_CONTINUED;
spin_unlock_irq(&p->sighand->siglock);
pid = p->pid;
diff --git a/kernel/fork.c b/kernel/fork.c
index df53562ced51..f92f424d87f4 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -733,11 +733,11 @@ static inline int copy_signal(unsigned long clone_flags, struct task_struct * ts
return -ENOMEM;
atomic_set(&sig->count, 1);
atomic_set(&sig->live, 1);
+ sig->flags = 0;
sig->group_exit = 0;
sig->group_exit_code = 0;
sig->group_exit_task = NULL;
sig->group_stop_count = 0;
- sig->stop_state = 0;
sig->curr_target = NULL;
init_sigpending(&sig->shared_pending);
INIT_LIST_HEAD(&sig->posix_timers);
diff --git a/kernel/signal.c b/kernel/signal.c
index cc50f2504365..928b856861b0 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -153,11 +153,6 @@ static kmem_cache_t *sigqueue_cachep;
(!T(signr, SIG_KERNEL_IGNORE_MASK|SIG_KERNEL_STOP_MASK) && \
(t)->sighand->action[(signr)-1].sa.sa_handler == SIG_DFL)
-#define sig_avoid_stop_race() \
- (sigtestsetmask(&current->pending.signal, M(SIGCONT) | M(SIGKILL)) || \
- sigtestsetmask(&current->signal->shared_pending.signal, \
- M(SIGCONT) | M(SIGKILL)))
-
static int sig_ignored(struct task_struct *t, int sig)
{
void __user * handler;
@@ -551,6 +546,21 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
if (!signr)
signr = __dequeue_signal(&tsk->signal->shared_pending,
mask, info);
+ if (signr && unlikely(sig_kernel_stop(signr))) {
+ /*
+ * Set a marker that we have dequeued a stop signal. Our
+ * caller might release the siglock and then the pending
+ * stop signal it is about to process is no longer in the
+ * pending bitmasks, but must still be cleared by a SIGCONT
+ * (and overruled by a SIGKILL). So those cases clear this
+ * shared flag after we've set it. Note that this flag may
+ * remain set after the signal we return is ignored or
+ * handled. That doesn't matter because its only purpose
+ * is to alert stop-signal processing code when another
+ * processor has come along and cleared the flag.
+ */
+ tsk->signal->flags |= SIGNAL_STOP_DEQUEUED;
+ }
if ( signr &&
((info->si_code & __SI_MASK) == __SI_TIMER) &&
info->si_sys_private){
@@ -680,7 +690,7 @@ static void handle_stop_signal(int sig, struct task_struct *p)
* the SIGCHLD was pending on entry to this kill.
*/
p->signal->group_stop_count = 0;
- p->signal->stop_state = 1;
+ p->signal->flags = SIGNAL_STOP_CONTINUED;
spin_unlock(&p->sighand->siglock);
if (p->ptrace & PT_PTRACED)
do_notify_parent_cldstop(p, p->parent,
@@ -722,12 +732,12 @@ static void handle_stop_signal(int sig, struct task_struct *p)
t = next_thread(t);
} while (t != p);
- if (p->signal->stop_state > 0) {
+ if (p->signal->flags & SIGNAL_STOP_STOPPED) {
/*
* We were in fact stopped, and are now continued.
* Notify the parent with CLD_CONTINUED.
*/
- p->signal->stop_state = -1;
+ p->signal->flags = SIGNAL_STOP_CONTINUED;
p->signal->group_exit_code = 0;
spin_unlock(&p->sighand->siglock);
if (p->ptrace & PT_PTRACED)
@@ -739,7 +749,20 @@ static void handle_stop_signal(int sig, struct task_struct *p)
p->group_leader->real_parent,
CLD_CONTINUED);
spin_lock(&p->sighand->siglock);
+ } else {
+ /*
+ * We are not stopped, but there could be a stop
+ * signal in the middle of being processed after
+ * being removed from the queue. Clear that too.
+ */
+ p->signal->flags = 0;
}
+ } else if (sig == SIGKILL) {
+ /*
+ * Make sure that any pending stop signal already dequeued
+ * is undone by the wakeup for SIGKILL.
+ */
+ p->signal->flags = 0;
}
}
@@ -969,6 +992,7 @@ __group_complete_signal(int sig, struct task_struct *p)
p->signal->group_exit = 1;
p->signal->group_exit_code = sig;
p->signal->group_stop_count = 0;
+ p->signal->flags = 0;
t = p;
do {
sigaddset(&t->pending.signal, SIGKILL);
@@ -1056,6 +1080,7 @@ void zap_other_threads(struct task_struct *p)
struct task_struct *t;
p->signal->group_stop_count = 0;
+ p->signal->flags = 0;
if (thread_group_empty(p))
return;
@@ -1641,15 +1666,18 @@ finish_stop(int stop_count)
/*
* This performs the stopping for SIGSTOP and other stop signals.
* We have to stop all threads in the thread group.
+ * Returns nonzero if we've actually stopped and released the siglock.
+ * Returns zero if we didn't stop and still hold the siglock.
*/
-static void
+static int
do_signal_stop(int signr)
{
struct signal_struct *sig = current->signal;
struct sighand_struct *sighand = current->sighand;
int stop_count = -1;
- /* spin_lock_irq(&sighand->siglock) is now done in caller */
+ if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED))
+ return 0;
if (sig->group_stop_count > 0) {
/*
@@ -1661,7 +1689,7 @@ do_signal_stop(int signr)
current->exit_code = signr;
set_current_state(TASK_STOPPED);
if (stop_count == 0)
- sig->stop_state = 1;
+ sig->flags = SIGNAL_STOP_STOPPED;
spin_unlock_irq(&sighand->siglock);
}
else if (thread_group_empty(current)) {
@@ -1670,7 +1698,7 @@ do_signal_stop(int signr)
*/
current->exit_code = current->signal->group_exit_code = signr;
set_current_state(TASK_STOPPED);
- sig->stop_state = 1;
+ sig->flags = SIGNAL_STOP_STOPPED;
spin_unlock_irq(&sighand->siglock);
}
else {
@@ -1691,25 +1719,16 @@ do_signal_stop(int signr)
read_lock(&tasklist_lock);
spin_lock_irq(&sighand->siglock);
- if (unlikely(sig->group_exit)) {
+ if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED)) {
/*
- * There is a group exit in progress now.
- * We'll just ignore the stop and process the
- * associated fatal signal.
+ * Another stop or continue happened while we
+ * didn't have the lock. We can just swallow this
+ * signal now. If we raced with a SIGCONT, that
+ * should have just cleared it now. If we raced
+ * with another processor delivering a stop signal,
+ * then the SIGCONT that wakes us up should clear it.
*/
- spin_unlock_irq(&sighand->siglock);
- read_unlock(&tasklist_lock);
- return;
- }
-
- if (unlikely(sig_avoid_stop_race())) {
- /*
- * Either a SIGCONT or a SIGKILL signal was
- * posted in the siglock-not-held window.
- */
- spin_unlock_irq(&sighand->siglock);
- read_unlock(&tasklist_lock);
- return;
+ return 0;
}
if (sig->group_stop_count == 0) {
@@ -1737,13 +1756,14 @@ do_signal_stop(int signr)
current->exit_code = signr;
set_current_state(TASK_STOPPED);
if (stop_count == 0)
- sig->stop_state = 1;
+ sig->flags = SIGNAL_STOP_STOPPED;
spin_unlock_irq(&sighand->siglock);
read_unlock(&tasklist_lock);
}
finish_stop(stop_count);
+ return 1;
}
/*
@@ -1779,7 +1799,7 @@ static inline int handle_group_stop(void)
*/
stop_count = --current->signal->group_stop_count;
if (stop_count == 0)
- current->signal->stop_state = 1;
+ current->signal->flags = SIGNAL_STOP_STOPPED;
current->exit_code = current->signal->group_exit_code;
set_current_state(TASK_STOPPED);
spin_unlock_irq(&current->sighand->siglock);
@@ -1873,28 +1893,27 @@ relock:
* This allows an intervening SIGCONT to be posted.
* We need to check for that and bail out if necessary.
*/
- if (signr == SIGSTOP) {
- do_signal_stop(signr); /* releases siglock */
- goto relock;
- }
- spin_unlock_irq(&current->sighand->siglock);
+ if (signr != SIGSTOP) {
+ spin_unlock_irq(&current->sighand->siglock);
- /* signals can be posted during this window */
+ /* signals can be posted during this window */
- if (is_orphaned_pgrp(process_group(current)))
- goto relock;
+ if (is_orphaned_pgrp(process_group(current)))
+ goto relock;
- spin_lock_irq(&current->sighand->siglock);
- if (unlikely(sig_avoid_stop_race())) {
- /*
- * Either a SIGCONT or a SIGKILL signal was
- * posted in the siglock-not-held window.
- */
- continue;
+ spin_lock_irq(&current->sighand->siglock);
}
- do_signal_stop(signr); /* releases siglock */
- goto relock;
+ if (likely(do_signal_stop(signr))) {
+ /* It released the siglock. */
+ goto relock;
+ }
+
+ /*
+ * We didn't actually stop, due to a race
+ * with SIGCONT or something like that.
+ */
+ continue;
}
spin_unlock_irq(&current->sighand->siglock);