freezer: do not send signals to kernel threads

The freezer should not send signals to kernel threads, since that may lead to
subtle problems.  In particular, commit
b74d0deb96 has changed recalc_sigpending_tsk()
so that it doesn't clear TIF_SIGPENDING.  For this reason, if the freezer
continues to send fake signals to kernel threads and the freezing of kernel
threads fails, some of them may be running with TIF_SIGPENDING set forever.

Accordingly, recalc_sigpending_tsk() shouldn't set the task's TIF_SIGPENDING
flag if TIF_FREEZE is set.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Cc: Nigel Cunningham <nigel@nigel.suspend2.net>
Cc: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Rafael J. Wysocki 2007-10-18 03:04:46 -07:00 committed by Linus Torvalds
parent e42837bcd3
commit d5d8c5976d
3 changed files with 93 additions and 46 deletions

View file

@ -19,12 +19,13 @@ we only consider hibernation, but the description also applies to suspend).
Namely, as the first step of the hibernation procedure the function
freeze_processes() (defined in kernel/power/process.c) is called. It executes
try_to_freeze_tasks() that sets TIF_FREEZE for all of the freezable tasks and
sends a fake signal to each of them. A task that receives such a signal and has
TIF_FREEZE set, should react to it by calling the refrigerator() function
(defined in kernel/power/process.c), which sets the task's PF_FROZEN flag,
changes its state to TASK_UNINTERRUPTIBLE and makes it loop until PF_FROZEN is
cleared for it. Then, we say that the task is 'frozen' and therefore the set of
functions handling this mechanism is called 'the freezer' (these functions are
either wakes them up, if they are kernel threads, or sends fake signals to them,
if they are user space processes. A task that has TIF_FREEZE set, should react
to it by calling the function called refrigerator() (defined in
kernel/power/process.c), which sets the task's PF_FROZEN flag, changes its state
to TASK_UNINTERRUPTIBLE and makes it loop until PF_FROZEN is cleared for it.
Then, we say that the task is 'frozen' and therefore the set of functions
handling this mechanism is referred to as 'the freezer' (these functions are
defined in kernel/power/process.c and include/linux/freezer.h). User space
processes are generally frozen before kernel threads.
@ -35,21 +36,27 @@ task enter refrigerator() if the flag is set.
For user space processes try_to_freeze() is called automatically from the
signal-handling code, but the freezable kernel threads need to call it
explicitly in suitable places. The code to do this may look like the following:
explicitly in suitable places or use the wait_event_freezable() or
wait_event_freezable_timeout() macros (defined in include/linux/freezer.h)
that combine interruptible sleep with checking if TIF_FREEZE is set and calling
try_to_freeze(). The main loop of a freezable kernel thread may look like the
following one:
set_freezable();
do {
hub_events();
wait_event_interruptible(khubd_wait,
!list_empty(&hub_event_list));
try_to_freeze();
} while (!signal_pending(current));
wait_event_freezable(khubd_wait,
!list_empty(&hub_event_list) ||
kthread_should_stop());
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
(from drivers/usb/core/hub.c::hub_thread()).
If a freezable kernel thread fails to call try_to_freeze() after the freezer has
set TIF_FREEZE for it, the freezing of tasks will fail and the entire
hibernation operation will be cancelled. For this reason, freezable kernel
threads must call try_to_freeze() somewhere.
threads must call try_to_freeze() somewhere or use one of the
wait_event_freezable() and wait_event_freezable_timeout() macros.
After the system memory state has been restored from a hibernation image and
devices have been reinitialized, the function thaw_processes() is called in

View file

@ -75,21 +75,79 @@ void refrigerator(void)
__set_current_state(save);
}
static void freeze_task(struct task_struct *p)
static void fake_signal_wake_up(struct task_struct *p, int resume)
{
unsigned long flags;
if (!freezing(p)) {
spin_lock_irqsave(&p->sighand->siglock, flags);
signal_wake_up(p, resume);
spin_unlock_irqrestore(&p->sighand->siglock, flags);
}
static void send_fake_signal(struct task_struct *p)
{
if (p->state == TASK_STOPPED)
force_sig_specific(SIGSTOP, p);
fake_signal_wake_up(p, p->state == TASK_STOPPED);
}
static int has_mm(struct task_struct *p)
{
return (p->mm && !(p->flags & PF_BORROWED_MM));
}
/**
* freeze_task - send a freeze request to given task
* @p: task to send the request to
* @with_mm_only: if set, the request will only be sent if the task has its
* own mm
* Return value: 0, if @with_mm_only is set and the task has no mm of its
* own or the task is frozen, 1, otherwise
*
* The freeze request is sent by seting the tasks's TIF_FREEZE flag and
* either sending a fake signal to it or waking it up, depending on whether
* or not it has its own mm (ie. it is a user land task). If @with_mm_only
* is set and the task has no mm of its own (ie. it is a kernel thread),
* its TIF_FREEZE flag should not be set.
*
* The task_lock() is necessary to prevent races with exit_mm() or
* use_mm()/unuse_mm() from occuring.
*/
static int freeze_task(struct task_struct *p, int with_mm_only)
{
int ret = 1;
task_lock(p);
if (freezing(p)) {
if (has_mm(p)) {
if (!signal_pending(p))
fake_signal_wake_up(p, 0);
} else {
if (with_mm_only)
ret = 0;
else
wake_up_state(p, TASK_INTERRUPTIBLE);
}
} else {
rmb();
if (!frozen(p)) {
set_freeze_flag(p);
if (p->state == TASK_STOPPED)
force_sig_specific(SIGSTOP, p);
spin_lock_irqsave(&p->sighand->siglock, flags);
signal_wake_up(p, p->state == TASK_STOPPED);
spin_unlock_irqrestore(&p->sighand->siglock, flags);
if (frozen(p)) {
ret = 0;
} else {
if (has_mm(p)) {
set_freeze_flag(p);
send_fake_signal(p);
} else {
if (with_mm_only) {
ret = 0;
} else {
set_freeze_flag(p);
wake_up_state(p, TASK_INTERRUPTIBLE);
}
}
}
}
task_unlock(p);
return ret;
}
static void cancel_freezing(struct task_struct *p)
@ -119,31 +177,14 @@ static int try_to_freeze_tasks(int freeze_user_space)
if (frozen(p) || !freezeable(p))
continue;
if (freeze_user_space) {
if (p->state == TASK_TRACED &&
frozen(p->parent)) {
cancel_freezing(p);
continue;
}
/*
* Kernel threads should not have TIF_FREEZE set
* at this point, so we must ensure that either
* p->mm is not NULL *and* PF_BORROWED_MM is
* unset, or TIF_FRREZE is left unset.
* The task_lock() is necessary to prevent races
* with exit_mm() or use_mm()/unuse_mm() from
* occuring.
*/
task_lock(p);
if (!p->mm || (p->flags & PF_BORROWED_MM)) {
task_unlock(p);
continue;
}
freeze_task(p);
task_unlock(p);
} else {
freeze_task(p);
if (p->state == TASK_TRACED && frozen(p->parent)) {
cancel_freezing(p);
continue;
}
if (!freeze_task(p, freeze_user_space))
continue;
if (!freezer_should_skip(p))
todo++;
} while_each_thread(g, p);

View file

@ -99,7 +99,6 @@ static inline int has_pending_signals(sigset_t *signal, sigset_t *blocked)
static int recalc_sigpending_tsk(struct task_struct *t)
{
if (t->signal->group_stop_count > 0 ||
(freezing(t)) ||
PENDING(&t->pending, &t->blocked) ||
PENDING(&t->signal->shared_pending, &t->blocked)) {
set_tsk_thread_flag(t, TIF_SIGPENDING);