Linux – Hibernation without swap enabled

hibernatelinuxssdswapsystemd

My laptop has plenty of RAM and a SSD. To not wear out the SSD, I do not want to have swap be used ever. If a process ever uses so much RAM that it has to start using swap, then it clearly is misbehaving and should be killed by the kernel. But I also want to be able to hibernate or hybrid-sleep my laptop. It currently seems to be very hard to get such a setup working. Here are some caveats:

  • even setting swappiness to 0 will let swap be used if an application requests too much memory. There seems to be no way to have swap enabled but stop the kernel from using it as swap space (and thus reduce its utility to space for the hibernation image)
  • disabling the swap (temporarily) will lead to commands like systemctl hibernate error out with Failed to hibernate system via logind: Sleep verb not supported
  • I want upower to be able to put my system into hibernation/hybrid-sleep (so writing a wrapper script which enables swap and then launches systemctl hibernate is not sufficient)

I am now looking for the right way to achieve this. One way to do it would probably be to generally disable my swap partition and then to swapon at the right point via systemd (and swapoff after the system woke up). But I'm at a loss how to achieve this.

Another way would be to use a swap file but that seems to add extra complication compared to using a swap partition. Not the least because I'm using full disk encryption and uswsusp seems to be just another layer on top of what I can already have with my existing swap partition. But maybe I'm missing something and using a swap file is indeed overall easier for this purpose.

Best Answer

  1. Disabling swap when you already know that your system can get to the point when it won't have enough memory is a bad idea. Right at the moment when kernel doesn't have any memory to allocate your system will probably be very-very unresponsive or just hang so you'll have to reboot it. Hangs may cause data losses, sometimes filesystem corruption. Depends on filesystem it can be fixed automatically (ext* family, fat family, btrfs and some others), but not every FS support this feature. I bet you probably won't like to run fsck in single user mode every time you have a corruption...

  2. Kernel is not that stupid to just write everything it wants to your SSD, it behaves differently compared to HDDs and tries to make the least possible writes using things such as TRIM. There's not that much harm to your drive as you may think.

  3. Hibernation is a built-in kernel subsystem that operates on a low level. It still has to have a separate partition with size ≥ RAM size allowing it to just make a full snapshot of you memory. Here you can read how linux manages hibernation. If there's no place to permanently store memory image there's no way hibernation works. You can't store data in the air. No permanent physical storage = no hibernation. So that's also the answer to your question: you can't use hibernation without swap on linux.

So, my point is that you of course can live without swap and enable it only for hibernation, but only if you're sure that 99.9% of the time system is on it has enough memory for everything. If that's not your case you must use swap.

Now to the possible solution:

When you run systemctl hibernate systemd starts a service called systemd-hibernate.service. Unit file of which is usually located it /usr/lib/systemd/system/ directory

On Debian it looks like this:

#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Hibernate
Documentation=man:systemd-suspend.service(8)
DefaultDependencies=no
Requires=sleep.target
After=sleep.target

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-sleep hibernate

As you can see there are Requires and After options. We can write another unit that will enable swap and add it's name to these fields. We'll call it swap-on.service and place it in /etc/systemd/system/ directory

Our file will look something like this:

# Unit filed is not required in our case so you can skip it
[Unit]
Description=Enable swap partition

[Service]
# This line means that service will be executed just once and immediately stop when
# swapon enables swap on /dev/sda3
Type=oneshot

# Change /dev/sda3 with device named after your swap partition 
# like /dev/sdb3 or /dev/nvme0n1p3
# Use lsblk to determine your swap partition (partition with [SWAP] as mountpoint)
# Of course you can think of a more complex solution like running external script
# that determines your swap partition automatically every time service is executed
# but for now I'll go with simple /dev/sda3
ExecStart=/sbin/swapon /dev/sda3

Now we should modify our system's systemd-hibernate.service:

#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Hibernate
Documentation=man:systemd-suspend.service(8)
DefaultDependencies=no
Requires=sleep.target swap-on.service <--
After=sleep.target swap-on.service <--

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-sleep hibernate

We're duplicating swap-on.service in After to make sure that systemd starts hibernation after the swap is on and not vice versa and in Required to make sure that systemd will not try to hibernate if swap-on.service failed.

Now when we run systemctl hibernate systemd runs our modified systemd-hibernate.service which runs swap-on.service that enables swap. When the swap is enabled and both Required and After fields are satisfied systemd can finally make our system hibernate.

To make systemd disable swap after resuming from hibernation we'll need no make another service file called swap-off.service which will do the same thing as our swap-on.service, place it in the same directory as the "on" service (/etc/systemd/system/). The only difference is that this service will run swapoff instead of swapon:

[Unit]
Description=Disable swap partition

[Service]
Type=oneshot

#  again, change /dev/sda3 to your swap partition /dev file
ExecStart=/sbin/swapoff /dev/sda3 <--

Next step is to modify service file called systemd-hibernate-resume@.service located in the same /usr/lib/systemd/system directory:

[Unit]
Description=Resume from hibernation using device %f
Documentation=man:systemd-hibernate-resume@.service(8)
DefaultDependencies=no
BindsTo=%i.device
Wants=local-fs-pre.target
After=%i.device
Before=local-fs-pre.target
ConditionPathExists=/etc/initrd-release

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-hibernate-resume %f

We need to modify Before field now. Add swap-off.service next to the local-fs-pre.target:

...
After=%i.device
Before=local-fs-pre.target swap-off.service <--
ConditionPathExists=/etc/initrd-release
...

Now when you resume from hibernation systemd will run swap-off.service after the swap image is loaded in memory (in fact a bit later, but that doesn't matter for us)

Keep in mind that this is a fully theoretical solution, I didn't try it yet, but based on what I know about systemd this should work. Since UPower asks systemd to hibernate which as I explained runs systemd-hibernate.service this is probably what you're looking for