power: freeze filesystems during suspend/resume

Now all the pieces are in place to actually allow the power subsystem
to freeze/thaw filesystems during suspend/resume. Filesystems are only
frozen and thawed if the power subsystem does actually own the freeze.

We could bubble up errors and fail suspend/resume if the error isn't
EBUSY (aka it's already frozen) but I don't think that this is worth it.
Filesystem freezing during suspend/resume is best-effort. If the user
has 500 ext4 filesystems mounted and 4 fail to freeze for whatever
reason then we simply skip them.

What we have now is already a big improvement and let's see how we fare
with it before making our lives even harder (and uglier) than we have
to.

We add a new sysctl know /sys/power/freeze_filesystems that will allow
userspace to freeze filesystems during suspend/hibernate. For now it
defaults to off. The thaw logic doesn't require checking whether
freezing is enabled because the power subsystem exclusively owns frozen
filesystems for the duration of suspend/hibernate and is able to skip
filesystems it doesn't need to freeze.

Also it is technically possible that filesystem
filesystem_freeze_enabled is true and power freezes the filesystems but
before freezing all processes another process disables
filesystem_freeze_enabled. If power were to place the filesystems_thaw()
call under filesystems_freeze_enabled it would fail to thaw the
fileystems it frozw. The exclusive holder mechanism makes it possible to
iterate through the list without any concern making sure that no
filesystems are left frozen.

Link: https://lore.kernel.org/r/20250402-work-freeze-v2-3-6719a97b52ac@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2025-04-02 16:07:33 +02:00
parent 0de4c4065c
commit eacfbf7419
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
4 changed files with 57 additions and 1 deletions

View File

@ -778,6 +778,8 @@ int hibernate(void)
goto Restore;
ksys_sync_helper();
if (filesystem_freeze_enabled)
filesystems_freeze();
error = freeze_processes();
if (error)
@ -846,6 +848,7 @@ int hibernate(void)
/* Don't bother checking whether freezer_test_done is true */
freezer_test_done = false;
Exit:
filesystems_thaw();
pm_notifier_call_chain(PM_POST_HIBERNATION);
Restore:
pm_restore_console();
@ -882,6 +885,9 @@ int hibernate_quiet_exec(int (*func)(void *data), void *data)
if (error)
goto restore;
if (filesystem_freeze_enabled)
filesystems_freeze();
error = freeze_processes();
if (error)
goto exit;
@ -941,6 +947,7 @@ int hibernate_quiet_exec(int (*func)(void *data), void *data)
thaw_processes();
exit:
filesystems_thaw();
pm_notifier_call_chain(PM_POST_HIBERNATION);
restore:
@ -1029,19 +1036,26 @@ static int software_resume(void)
if (error)
goto Restore;
if (filesystem_freeze_enabled)
filesystems_freeze();
pm_pr_dbg("Preparing processes for hibernation restore.\n");
error = freeze_processes();
if (error)
if (error) {
filesystems_thaw();
goto Close_Finish;
}
error = freeze_kernel_threads();
if (error) {
thaw_processes();
filesystems_thaw();
goto Close_Finish;
}
error = load_image_and_restore();
thaw_processes();
filesystems_thaw();
Finish:
pm_notifier_call_chain(PM_POST_RESTORE);
Restore:

View File

@ -962,6 +962,34 @@ power_attr(pm_freeze_timeout);
#endif /* CONFIG_FREEZER*/
#if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATION)
bool filesystem_freeze_enabled = false;
static ssize_t freeze_filesystems_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", filesystem_freeze_enabled);
}
static ssize_t freeze_filesystems_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
unsigned long val;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
if (val > 1)
return -EINVAL;
filesystem_freeze_enabled = !!val;
return n;
}
power_attr(freeze_filesystems);
#endif /* CONFIG_SUSPEND || CONFIG_HIBERNATION */
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE
@ -991,6 +1019,9 @@ static struct attribute * g[] = {
#endif
#ifdef CONFIG_FREEZER
&pm_freeze_timeout_attr.attr,
#endif
#if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATION)
&freeze_filesystems_attr.attr,
#endif
NULL,
};

View File

@ -18,6 +18,10 @@ struct swsusp_info {
unsigned long size;
} __aligned(PAGE_SIZE);
#if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATION)
extern bool filesystem_freeze_enabled;
#endif
#ifdef CONFIG_HIBERNATION
/* kernel/power/snapshot.c */
extern void __init hibernate_reserved_size_init(void);

View File

@ -30,6 +30,7 @@
#include <trace/events/power.h>
#include <linux/compiler.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include "power.h"
@ -374,6 +375,8 @@ static int suspend_prepare(suspend_state_t state)
if (error)
goto Restore;
if (filesystem_freeze_enabled)
filesystems_freeze();
trace_suspend_resume(TPS("freeze_processes"), 0, true);
error = suspend_freeze_processes();
trace_suspend_resume(TPS("freeze_processes"), 0, false);
@ -550,6 +553,7 @@ int suspend_devices_and_enter(suspend_state_t state)
static void suspend_finish(void)
{
suspend_thaw_processes();
filesystems_thaw();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
}
@ -588,6 +592,8 @@ static int enter_state(suspend_state_t state)
ksys_sync_helper();
trace_suspend_resume(TPS("sync_filesystems"), 0, false);
}
if (filesystem_freeze_enabled)
filesystems_freeze();
pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]);
pm_suspend_clear_flags();
@ -609,6 +615,7 @@ static int enter_state(suspend_state_t state)
pm_pr_dbg("Finishing wakeup.\n");
suspend_finish();
Unlock:
filesystems_thaw();
mutex_unlock(&system_transition_mutex);
return error;
}