diff options
Diffstat (limited to 'kernel/freezer.c')
| -rw-r--r-- | kernel/freezer.c | 203 | 
1 files changed, 104 insertions, 99 deletions
diff --git a/kernel/freezer.c b/kernel/freezer.c index 7b01de98bb6..9815b8d1eed 100644 --- a/kernel/freezer.c +++ b/kernel/freezer.c @@ -6,104 +6,117 @@  #include <linux/interrupt.h>  #include <linux/suspend.h> -#include <linux/module.h> +#include <linux/export.h>  #include <linux/syscalls.h>  #include <linux/freezer.h> +#include <linux/kthread.h> -/* - * freezing is complete, mark current process as frozen +/* total number of freezing conditions in effect */ +atomic_t system_freezing_cnt = ATOMIC_INIT(0); +EXPORT_SYMBOL(system_freezing_cnt); + +/* indicate whether PM freezing is in effect, protected by pm_mutex */ +bool pm_freezing; +bool pm_nosig_freezing; + +/* protects freezing and frozen transitions */ +static DEFINE_SPINLOCK(freezer_lock); + +/** + * freezing_slow_path - slow path for testing whether a task needs to be frozen + * @p: task to be tested + * + * This function is called by freezing() if system_freezing_cnt isn't zero + * and tests whether @p needs to enter and stay in frozen state.  Can be + * called under any context.  The freezers are responsible for ensuring the + * target tasks see the updated state.   */ -static inline void frozen_process(void) +bool freezing_slow_path(struct task_struct *p)  { -	if (!unlikely(current->flags & PF_NOFREEZE)) { -		current->flags |= PF_FROZEN; -		smp_wmb(); -	} -	clear_freeze_flag(current); +	if (p->flags & PF_NOFREEZE) +		return false; + +	if (pm_nosig_freezing || cgroup_freezing(p)) +		return true; + +	if (pm_freezing && !(p->flags & PF_KTHREAD)) +		return true; + +	return false;  } +EXPORT_SYMBOL(freezing_slow_path);  /* Refrigerator is place where frozen processes are stored :-). */ -void refrigerator(void) +bool __refrigerator(bool check_kthr_stop)  {  	/* Hmm, should we be allowed to suspend when there are realtime  	   processes around? */ -	long save; +	bool was_frozen = false; +	long save = current->state; -	task_lock(current); -	if (freezing(current)) { -		frozen_process(); -		task_unlock(current); -	} else { -		task_unlock(current); -		return; -	} -	save = current->state;  	pr_debug("%s entered refrigerator\n", current->comm); -	spin_lock_irq(¤t->sighand->siglock); -	recalc_sigpending(); /* We sent fake signal, clean it up */ -	spin_unlock_irq(¤t->sighand->siglock); - -	/* prevent accounting of that task to load */ -	current->flags |= PF_FREEZING; -  	for (;;) {  		set_current_state(TASK_UNINTERRUPTIBLE); -		if (!frozen(current)) + +		spin_lock_irq(&freezer_lock); +		current->flags |= PF_FROZEN; +		if (!freezing(current) || +		    (check_kthr_stop && kthread_should_stop())) +			current->flags &= ~PF_FROZEN; +		spin_unlock_irq(&freezer_lock); + +		if (!(current->flags & PF_FROZEN))  			break; +		was_frozen = true;  		schedule();  	} -	/* Remove the accounting blocker */ -	current->flags &= ~PF_FREEZING; -  	pr_debug("%s left refrigerator\n", current->comm); -	__set_current_state(save); + +	/* +	 * Restore saved task state before returning.  The mb'd version +	 * needs to be used; otherwise, it might silently break +	 * synchronization which depends on ordered task state change. +	 */ +	set_current_state(save); + +	return was_frozen;  } -EXPORT_SYMBOL(refrigerator); +EXPORT_SYMBOL(__refrigerator);  static void fake_signal_wake_up(struct task_struct *p)  {  	unsigned long flags; -	spin_lock_irqsave(&p->sighand->siglock, flags); -	signal_wake_up(p, 0); -	spin_unlock_irqrestore(&p->sighand->siglock, flags); +	if (lock_task_sighand(p, &flags)) { +		signal_wake_up(p, 0); +		unlock_task_sighand(p, &flags); +	}  }  /** - *	freeze_task - send a freeze request to given task - *	@p: task to send the request to - *	@sig_only: if set, the request will only be sent if the task has the - *		PF_FREEZER_NOSIG flag unset - *	Return value: 'false', if @sig_only is set and the task has - *		PF_FREEZER_NOSIG set or the task is frozen, 'true', otherwise + * freeze_task - send a freeze request to given task + * @p: task to send the request to + * + * If @p is freezing, the freeze request is sent by setting %TIF_FREEZE + * flag and either sending a fake signal to it or waking it up, depending + * on whether it has %PF_FREEZER_NOSIG set.   * - *	The freeze request is sent by setting 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 PF_FREEZER_NOSIG set.  If @sig_only is set and the task - *	has PF_FREEZER_NOSIG set (ie. it is a typical kernel thread), its - *	TIF_FREEZE flag will not be set. + * RETURNS: + * %false, if @p is not freezing or already frozen; %true, otherwise   */ -bool freeze_task(struct task_struct *p, bool sig_only) +bool freeze_task(struct task_struct *p)  { -	/* -	 * We first check if the task is freezing and next if it has already -	 * been frozen to avoid the race with frozen_process() which first marks -	 * the task as frozen and next clears its TIF_FREEZE. -	 */ -	if (!freezing(p)) { -		smp_rmb(); -		if (frozen(p)) -			return false; +	unsigned long flags; -		if (!sig_only || should_send_signal(p)) -			set_freeze_flag(p); -		else -			return false; +	spin_lock_irqsave(&freezer_lock, flags); +	if (!freezing(p) || frozen(p)) { +		spin_unlock_irqrestore(&freezer_lock, flags); +		return false;  	} -	if (should_send_signal(p)) { +	if (!(p->flags & PF_KTHREAD)) {  		fake_signal_wake_up(p);  		/*  		 * fake_signal_wake_up() goes through p's scheduler @@ -111,56 +124,48 @@ bool freeze_task(struct task_struct *p, bool sig_only)  		 * TASK_RUNNING transition can't race with task state  		 * testing in try_to_freeze_tasks().  		 */ -	} else if (sig_only) { -		return false;  	} else {  		wake_up_state(p, TASK_INTERRUPTIBLE);  	} +	spin_unlock_irqrestore(&freezer_lock, flags);  	return true;  } -void cancel_freezing(struct task_struct *p) +void __thaw_task(struct task_struct *p)  {  	unsigned long flags; -	if (freezing(p)) { -		pr_debug("  clean up: %s\n", p->comm); -		clear_freeze_flag(p); -		spin_lock_irqsave(&p->sighand->siglock, flags); -		recalc_sigpending_and_wake(p); -		spin_unlock_irqrestore(&p->sighand->siglock, flags); -	} -} - -static int __thaw_process(struct task_struct *p) -{ -	if (frozen(p)) { -		p->flags &= ~PF_FROZEN; -		return 1; -	} -	clear_freeze_flag(p); -	return 0; +	/* +	 * Clear freezing and kick @p if FROZEN.  Clearing is guaranteed to +	 * be visible to @p as waking up implies wmb.  Waking up inside +	 * freezer_lock also prevents wakeups from leaking outside +	 * refrigerator. +	 */ +	spin_lock_irqsave(&freezer_lock, flags); +	if (frozen(p)) +		wake_up_process(p); +	spin_unlock_irqrestore(&freezer_lock, flags);  } -/* - * Wake up a frozen process +/** + * set_freezable - make %current freezable   * - * task_lock() is needed to prevent the race with refrigerator() which may - * occur if the freezing of tasks fails.  Namely, without the lock, if the - * freezing of tasks failed, thaw_tasks() might have run before a task in - * refrigerator() could call frozen_process(), in which case the task would be - * frozen and no one would thaw it. + * Mark %current freezable and enter refrigerator if necessary.   */ -int thaw_process(struct task_struct *p) +bool set_freezable(void)  { -	task_lock(p); -	if (__thaw_process(p) == 1) { -		task_unlock(p); -		wake_up_process(p); -		return 1; -	} -	task_unlock(p); -	return 0; +	might_sleep(); + +	/* +	 * Modify flags while holding freezer_lock.  This ensures the +	 * freezer notices that we aren't frozen yet or the freezing +	 * condition is visible to try_to_freeze() below. +	 */ +	spin_lock_irq(&freezer_lock); +	current->flags &= ~PF_NOFREEZE; +	spin_unlock_irq(&freezer_lock); + +	return try_to_freeze();  } -EXPORT_SYMBOL(thaw_process); +EXPORT_SYMBOL(set_freezable);  |