kernel/power/process.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
When freezing user space during suspend or hibernation, the freezer
iterates over all tasks and attempts to freeze them via
try_to_freeze_tasks().
However, zombie processes (i.e., tasks in EXIT_ZOMBIE state) are no
longer running and will never enter the refrigerator. Trying to freeze
them is meaningless and causes extra overhead, especially when there are
thousands of zombies created during stress conditions such as fork
storms.
This patch skips zombie processes during the freezing phase.
In our testing with ~30,000 user processes (including many zombies), the
average freeze time during suspend (S3) dropped from ~43 ms to ~16 ms:
- Without the patch: ~43 ms average freeze latency
- With the patch: ~16 ms average freeze latency
- Improvement: ~62%
This confirms that skipping zombies significantly speeds up the freezing
process when the system is under heavy load with many short-lived tasks.
Signed-off-by: Zihuan Zhang <zhangzihuan@kylinos.cn>
Changes in v4:
- Fix incomplete patch title
- Add a comment: exit_state is better than PF_NOFREEZE if we only intend
to skip user processes. TODO added for possible future replacement.
Changes in v3:
- Added performance test
Changes in v2:
- Simplified code, added judgment of dead processes
- Rewrite changelog
---
kernel/power/process.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/kernel/power/process.c b/kernel/power/process.c
index dc0dfc349f22..c1d6c5150033 100644
--- a/kernel/power/process.c
+++ b/kernel/power/process.c
@@ -51,7 +51,15 @@ static int try_to_freeze_tasks(bool user_only)
todo = 0;
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
- if (p == current || !freeze_task(p))
+ /*
+ * Zombie and dead tasks are not running anymore and cannot enter
+ * the __refrigerator(). Skipping them avoids unnecessary freeze attempts.
+ *
+ * TODO: Consider using PF_NOFREEZE instead, which may provide
+ * a more generic exclusion mechanism for other non-freezable tasks.
+ * However, for now, exit_state is sufficient to skip user processes.
+ */
+ if (p == current || p->exit_state || !freeze_task(p))
continue;
todo++;
--
2.25.1
On 07/16, Zihuan Zhang wrote: > > @@ -51,7 +51,15 @@ static int try_to_freeze_tasks(bool user_only) > todo = 0; > read_lock(&tasklist_lock); > for_each_process_thread(g, p) { > - if (p == current || !freeze_task(p)) > + /* > + * Zombie and dead tasks are not running anymore and cannot enter > + * the __refrigerator(). Skipping them avoids unnecessary freeze attempts. > + * > + * TODO: Consider using PF_NOFREEZE instead, which may provide > + * a more generic exclusion mechanism for other non-freezable tasks. > + * However, for now, exit_state is sufficient to skip user processes. I don't really understand the comment... The freeze_task() paths already consider PF_NOFREEZE, although we can check it earlier as Peter suggests. > + */ > + if (p == current || p->exit_state || !freeze_task(p)) > continue; I leave this to you and Rafael, but this change doesn't look safe to me. What if the exiting task does some IO after exit_notify() ? Oleg.
Hi Oleg, 在 2025/7/17 00:38, Oleg Nesterov 写道: > On 07/16, Zihuan Zhang wrote: >> @@ -51,7 +51,15 @@ static int try_to_freeze_tasks(bool user_only) >> todo = 0; >> read_lock(&tasklist_lock); >> for_each_process_thread(g, p) { >> - if (p == current || !freeze_task(p)) >> + /* >> + * Zombie and dead tasks are not running anymore and cannot enter >> + * the __refrigerator(). Skipping them avoids unnecessary freeze attempts. >> + * >> + * TODO: Consider using PF_NOFREEZE instead, which may provide >> + * a more generic exclusion mechanism for other non-freezable tasks. >> + * However, for now, exit_state is sufficient to skip user processes. > I don't really understand the comment... The freeze_task() paths already > consider PF_NOFREEZE, although we can check it earlier as Peter suggests. You’re right — freeze_task() already takes PF_NOFREEZE into account. Our intention here is to skip zombie and dead tasks earlier to avoid calling freeze_task() unnecessarily, especially when the number of such tasks is large. The comment is meant to highlight a possible future direction: while exit_state already allows us to skip all exiting user-space tasks safely, we may later extend the logic to skip certain kernel threads that set PF_NOFREEZE and never clear it (e.g., kthreadd), as suggested by Peter >> + */ >> + if (p == current || p->exit_state || !freeze_task(p)) >> continue; > I leave this to you and Rafael, but this change doesn't look safe to me. > What if the exiting task does some IO after exit_notify() ? Tasks that have passed exit_notify() and entered EXIT_ZOMBIE are no longer schedulable, so they cannot do I/O anymore. Skipping them during freezing should be safe > Oleg. >
Hi Zihuan, On 07/17, Zihuan Zhang wrote: > > >>+ */ > >>+ if (p == current || p->exit_state || !freeze_task(p)) > >> continue; > >I leave this to you and Rafael, but this change doesn't look safe to me. > >What if the exiting task does some IO after exit_notify() ? > > Tasks that have passed exit_notify() and entered EXIT_ZOMBIE are no longer > schedulable, How so? please look at do_exit(). The exiting task is still running until it does its last __schedule() in do_task_dead(). > so they cannot do I/O anymore. Skipping them during freezing > should be safe Oleg.
Hi Oleg, 在 2025/7/17 09:31, Oleg Nesterov 写道: > Hi Zihuan, > > On 07/17, Zihuan Zhang wrote: >>>> + */ >>>> + if (p == current || p->exit_state || !freeze_task(p)) >>>> continue; >>> I leave this to you and Rafael, but this change doesn't look safe to me. >>> What if the exiting task does some IO after exit_notify() ? >> Tasks that have passed exit_notify() and entered EXIT_ZOMBIE are no longer >> schedulable, > How so? please look at do_exit(). The exiting task is still running > until it does its last __schedule() in do_task_dead(). > To verify the potential presence of EXIT_DEAD tasks during the freezing stage, I added some logging in try_to_freeze_tasks() to print out any task with exit_state == EXIT_DEAD. Then I created a fork storm scenario to ensure a large number of tasks are exiting during the freeze window. In practice, even after running hundreds of iterations under heavy load, I wasn’t able to capture any such task being printed. Since the exit phase is very fast, it seems unlikely that an EXIT_DEAD task stays in the process list long enough to be observed during the freeze loop. So I believe it's safe to skip tasks with exit_state in this context. diff --git a/kernel/power/process.c b/kernel/power/process.c index c1d6c5150033..054fad43ed31 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -59,6 +59,8 @@ static int try_to_freeze_tasks(bool user_only) * a more generic exclusion mechanism for other non-freezable tasks. * However, for now, exit_state is sufficient to skip user processes. */ + if (p->exit_state == EXIT_DEAD) + pr_info("current process is going to dead name:%s pid:%d \n", p->comm, p->pid); if (p == current || p->exit_state || !freeze_task(p)) continue; >> so they cannot do I/O anymore. Skipping them during freezing >> should be safe > Oleg. >
On Wed, Jul 16, 2025 at 06:38:55PM +0200, Oleg Nesterov wrote: > On 07/16, Zihuan Zhang wrote: > > > > @@ -51,7 +51,15 @@ static int try_to_freeze_tasks(bool user_only) > > todo = 0; > > read_lock(&tasklist_lock); > > for_each_process_thread(g, p) { > > - if (p == current || !freeze_task(p)) > > + /* > > + * Zombie and dead tasks are not running anymore and cannot enter > > + * the __refrigerator(). Skipping them avoids unnecessary freeze attempts. > > + * > > + * TODO: Consider using PF_NOFREEZE instead, which may provide > > + * a more generic exclusion mechanism for other non-freezable tasks. > > + * However, for now, exit_state is sufficient to skip user processes. > > I don't really understand the comment... The freeze_task() paths already > consider PF_NOFREEZE, although we can check it earlier as Peter suggests. Right; I really don't understand why we should special case ->exit_state. Why not DTRT and optimize NOFREEZE if all this really matters (smalls gains from what ISTR from the previous discussion).
Hi Peter, 在 2025/7/17 02:36, Peter Zijlstra 写道: > On Wed, Jul 16, 2025 at 06:38:55PM +0200, Oleg Nesterov wrote: >> On 07/16, Zihuan Zhang wrote: >>> @@ -51,7 +51,15 @@ static int try_to_freeze_tasks(bool user_only) >>> todo = 0; >>> read_lock(&tasklist_lock); >>> for_each_process_thread(g, p) { >>> - if (p == current || !freeze_task(p)) >>> + /* >>> + * Zombie and dead tasks are not running anymore and cannot enter >>> + * the __refrigerator(). Skipping them avoids unnecessary freeze attempts. >>> + * >>> + * TODO: Consider using PF_NOFREEZE instead, which may provide >>> + * a more generic exclusion mechanism for other non-freezable tasks. >>> + * However, for now, exit_state is sufficient to skip user processes. >> I don't really understand the comment... The freeze_task() paths already >> consider PF_NOFREEZE, although we can check it earlier as Peter suggests. > Right; I really don't understand why we should special case > ->exit_state. Why not DTRT and optimize NOFREEZE if all this really > matters (smalls gains from what ISTR from the previous discussion). The main reason we didn’t rely directly on PF_NOFREEZE is that it’s a mutable flag — in some cases, it can be cleared later, which makes early skipping potentially unsafe. In contrast, exit_state is stable and skipping tasks based on it is safe. Also, the previous version of the patch you shared might allow some paths to bypass lock_system_sleep(), which could break the intended protection.
On 07/17, Zihuan Zhang wrote: > > The main reason we didn’t rely directly on PF_NOFREEZE is that it’s a > mutable flag — in some cases, it can be cleared later, which makes early > skipping potentially unsafe. Afaics userspace tasks can only set PF_NOFREEZE in do_task_dead() and never clear it. Apart from lock_system_sleep(). That is why (I think) Peter rightly suggests to take system_transition_mutex in this function earlier. > In contrast, exit_state is stable and skipping tasks based on it is safe. I don't think it is really safe... Oleg.
© 2016 - 2025 Red Hat, Inc.