xref: /linux-6.15/kernel/freezer.c (revision a5be2d0d)
18174f150SMatt Helsley /*
28174f150SMatt Helsley  * kernel/freezer.c - Function to freeze a process
38174f150SMatt Helsley  *
48174f150SMatt Helsley  * Originally from kernel/power/process.c
58174f150SMatt Helsley  */
68174f150SMatt Helsley 
78174f150SMatt Helsley #include <linux/interrupt.h>
88174f150SMatt Helsley #include <linux/suspend.h>
99984de1aSPaul Gortmaker #include <linux/export.h>
108174f150SMatt Helsley #include <linux/syscalls.h>
118174f150SMatt Helsley #include <linux/freezer.h>
128a32c441STejun Heo #include <linux/kthread.h>
138174f150SMatt Helsley 
148174f150SMatt Helsley /*
158174f150SMatt Helsley  * freezing is complete, mark current process as frozen
168174f150SMatt Helsley  */
178174f150SMatt Helsley static inline void frozen_process(void)
188174f150SMatt Helsley {
198174f150SMatt Helsley 	if (!unlikely(current->flags & PF_NOFREEZE)) {
208174f150SMatt Helsley 		current->flags |= PF_FROZEN;
21ee940d8dSMike Frysinger 		smp_wmb();
228174f150SMatt Helsley 	}
238174f150SMatt Helsley 	clear_freeze_flag(current);
248174f150SMatt Helsley }
258174f150SMatt Helsley 
268174f150SMatt Helsley /* Refrigerator is place where frozen processes are stored :-). */
278a32c441STejun Heo bool __refrigerator(bool check_kthr_stop)
288174f150SMatt Helsley {
298174f150SMatt Helsley 	/* Hmm, should we be allowed to suspend when there are realtime
308174f150SMatt Helsley 	   processes around? */
31a0acae0eSTejun Heo 	bool was_frozen = false;
328174f150SMatt Helsley 	long save;
338174f150SMatt Helsley 
348174f150SMatt Helsley 	task_lock(current);
358174f150SMatt Helsley 	if (freezing(current)) {
368174f150SMatt Helsley 		frozen_process();
378174f150SMatt Helsley 		task_unlock(current);
388174f150SMatt Helsley 	} else {
398174f150SMatt Helsley 		task_unlock(current);
40a0acae0eSTejun Heo 		return was_frozen;
418174f150SMatt Helsley 	}
428174f150SMatt Helsley 	save = current->state;
438174f150SMatt Helsley 	pr_debug("%s entered refrigerator\n", current->comm);
448174f150SMatt Helsley 
458174f150SMatt Helsley 	spin_lock_irq(&current->sighand->siglock);
468174f150SMatt Helsley 	recalc_sigpending(); /* We sent fake signal, clean it up */
478174f150SMatt Helsley 	spin_unlock_irq(&current->sighand->siglock);
488174f150SMatt Helsley 
496301cb95SThomas Gleixner 	/* prevent accounting of that task to load */
506301cb95SThomas Gleixner 	current->flags |= PF_FREEZING;
516301cb95SThomas Gleixner 
528174f150SMatt Helsley 	for (;;) {
538174f150SMatt Helsley 		set_current_state(TASK_UNINTERRUPTIBLE);
548a32c441STejun Heo 		if (!frozen(current) ||
558a32c441STejun Heo 		    (check_kthr_stop && kthread_should_stop()))
568174f150SMatt Helsley 			break;
57a0acae0eSTejun Heo 		was_frozen = true;
588174f150SMatt Helsley 		schedule();
598174f150SMatt Helsley 	}
606301cb95SThomas Gleixner 
616301cb95SThomas Gleixner 	/* Remove the accounting blocker */
626301cb95SThomas Gleixner 	current->flags &= ~PF_FREEZING;
636301cb95SThomas Gleixner 
648174f150SMatt Helsley 	pr_debug("%s left refrigerator\n", current->comm);
6550fb4f7fSTejun Heo 
6650fb4f7fSTejun Heo 	/*
6750fb4f7fSTejun Heo 	 * Restore saved task state before returning.  The mb'd version
6850fb4f7fSTejun Heo 	 * needs to be used; otherwise, it might silently break
6950fb4f7fSTejun Heo 	 * synchronization which depends on ordered task state change.
7050fb4f7fSTejun Heo 	 */
7150fb4f7fSTejun Heo 	set_current_state(save);
72a0acae0eSTejun Heo 
73a0acae0eSTejun Heo 	return was_frozen;
748174f150SMatt Helsley }
75a0acae0eSTejun Heo EXPORT_SYMBOL(__refrigerator);
768174f150SMatt Helsley 
778174f150SMatt Helsley static void fake_signal_wake_up(struct task_struct *p)
788174f150SMatt Helsley {
798174f150SMatt Helsley 	unsigned long flags;
808174f150SMatt Helsley 
818174f150SMatt Helsley 	spin_lock_irqsave(&p->sighand->siglock, flags);
82d6cc7685STejun Heo 	signal_wake_up(p, 0);
838174f150SMatt Helsley 	spin_unlock_irqrestore(&p->sighand->siglock, flags);
848174f150SMatt Helsley }
858174f150SMatt Helsley 
868174f150SMatt Helsley /**
878174f150SMatt Helsley  *	freeze_task - send a freeze request to given task
888174f150SMatt Helsley  *	@p: task to send the request to
898174f150SMatt Helsley  *	@sig_only: if set, the request will only be sent if the task has the
908174f150SMatt Helsley  *		PF_FREEZER_NOSIG flag unset
918174f150SMatt Helsley  *	Return value: 'false', if @sig_only is set and the task has
928174f150SMatt Helsley  *		PF_FREEZER_NOSIG set or the task is frozen, 'true', otherwise
938174f150SMatt Helsley  *
948174f150SMatt Helsley  *	The freeze request is sent by setting the tasks's TIF_FREEZE flag and
958174f150SMatt Helsley  *	either sending a fake signal to it or waking it up, depending on whether
968174f150SMatt Helsley  *	or not it has PF_FREEZER_NOSIG set.  If @sig_only is set and the task
978174f150SMatt Helsley  *	has PF_FREEZER_NOSIG set (ie. it is a typical kernel thread), its
988174f150SMatt Helsley  *	TIF_FREEZE flag will not be set.
998174f150SMatt Helsley  */
1008174f150SMatt Helsley bool freeze_task(struct task_struct *p, bool sig_only)
1018174f150SMatt Helsley {
1028174f150SMatt Helsley 	/*
1038174f150SMatt Helsley 	 * We first check if the task is freezing and next if it has already
1048174f150SMatt Helsley 	 * been frozen to avoid the race with frozen_process() which first marks
1058174f150SMatt Helsley 	 * the task as frozen and next clears its TIF_FREEZE.
1068174f150SMatt Helsley 	 */
1078174f150SMatt Helsley 	if (!freezing(p)) {
108ee940d8dSMike Frysinger 		smp_rmb();
1098174f150SMatt Helsley 		if (frozen(p))
1108174f150SMatt Helsley 			return false;
1118174f150SMatt Helsley 
1128174f150SMatt Helsley 		if (!sig_only || should_send_signal(p))
1138174f150SMatt Helsley 			set_freeze_flag(p);
1148174f150SMatt Helsley 		else
1158174f150SMatt Helsley 			return false;
1168174f150SMatt Helsley 	}
1178174f150SMatt Helsley 
1188174f150SMatt Helsley 	if (should_send_signal(p)) {
1198174f150SMatt Helsley 		fake_signal_wake_up(p);
1208cfe400cSTejun Heo 		/*
1218cfe400cSTejun Heo 		 * fake_signal_wake_up() goes through p's scheduler
1228cfe400cSTejun Heo 		 * lock and guarantees that TASK_STOPPED/TRACED ->
1238cfe400cSTejun Heo 		 * TASK_RUNNING transition can't race with task state
1248cfe400cSTejun Heo 		 * testing in try_to_freeze_tasks().
1258cfe400cSTejun Heo 		 */
1268174f150SMatt Helsley 	} else if (sig_only) {
1278174f150SMatt Helsley 		return false;
1288174f150SMatt Helsley 	} else {
1298174f150SMatt Helsley 		wake_up_state(p, TASK_INTERRUPTIBLE);
1308174f150SMatt Helsley 	}
1318174f150SMatt Helsley 
1328174f150SMatt Helsley 	return true;
1338174f150SMatt Helsley }
1348174f150SMatt Helsley 
1358174f150SMatt Helsley void cancel_freezing(struct task_struct *p)
1368174f150SMatt Helsley {
1378174f150SMatt Helsley 	unsigned long flags;
1388174f150SMatt Helsley 
1398174f150SMatt Helsley 	if (freezing(p)) {
1408174f150SMatt Helsley 		pr_debug("  clean up: %s\n", p->comm);
1418174f150SMatt Helsley 		clear_freeze_flag(p);
1428174f150SMatt Helsley 		spin_lock_irqsave(&p->sighand->siglock, flags);
1438174f150SMatt Helsley 		recalc_sigpending_and_wake(p);
1448174f150SMatt Helsley 		spin_unlock_irqrestore(&p->sighand->siglock, flags);
1458174f150SMatt Helsley 	}
1468174f150SMatt Helsley }
147dc52ddc0SMatt Helsley 
14800c2e63cSLi Zefan /*
149*a5be2d0dSTejun Heo  * Wake up a frozen task
15000c2e63cSLi Zefan  *
15100c2e63cSLi Zefan  * task_lock() is needed to prevent the race with refrigerator() which may
15200c2e63cSLi Zefan  * occur if the freezing of tasks fails.  Namely, without the lock, if the
15300c2e63cSLi Zefan  * freezing of tasks failed, thaw_tasks() might have run before a task in
15400c2e63cSLi Zefan  * refrigerator() could call frozen_process(), in which case the task would be
15500c2e63cSLi Zefan  * frozen and no one would thaw it.
15600c2e63cSLi Zefan  */
157*a5be2d0dSTejun Heo void __thaw_task(struct task_struct *p)
158dc52ddc0SMatt Helsley {
159*a5be2d0dSTejun Heo 	bool was_frozen;
160*a5be2d0dSTejun Heo 
161dc52ddc0SMatt Helsley 	task_lock(p);
162*a5be2d0dSTejun Heo 	was_frozen = frozen(p);
163*a5be2d0dSTejun Heo 	if (was_frozen)
164*a5be2d0dSTejun Heo 		p->flags &= ~PF_FROZEN;
165*a5be2d0dSTejun Heo 	else
166*a5be2d0dSTejun Heo 		clear_freeze_flag(p);
167dc52ddc0SMatt Helsley 	task_unlock(p);
168*a5be2d0dSTejun Heo 
169*a5be2d0dSTejun Heo 	if (was_frozen)
170dc52ddc0SMatt Helsley 		wake_up_process(p);
171dc52ddc0SMatt Helsley }
172