Preface/“What & Why?”
A “zero-swap” hibernation partition, as I’m calling it (is there an established name for this?), is a swap partition dedicated to hibernate & resume services.
I think some distinction is merited since, by default, any sufficiently large and activated (swapon <swap-partition>
) swap space permits hibernation. Even kernel parameter vm.swappiness = 0
doesn’t prevent using the swap partition as extra RAM; it just tells the system to avoid doing so until all available RAM is depleted.
That brings us to an example situation for why this might be interesting:
Suppose you’re doing some video encode/trans-code (or something else that exhausts memory, maybe you’re running a couple VMs) in the background and running a browser with a bunch of tabs as you’re reading into something, maybe typing something up too. Your memory usage is near its limit, and you start another program–suddenly, you see the time, get a call, something alerts you, and you’ve got to leave. You close your laptop (which you’ve set up to trigger hibernation, essentially freezing everything in place exactly as it was so you can get back to it later) and toss it in your bag on your way out.
That last program you started before departure caused a spillover from main memory into the swap space you meant to use for hibernation, meaning the total amount of memory in use exceeds that available for hibernation, and the hibernation trigger fails. When you return to work, your battery is dead, and your session is lost. If the swap were somehow reserved for hibernation, that last program would’ve failed to start (of course that depends on the specifics of the system, but you get the idea), and hibernation would’ve succeeded, preserving your session, and letting you know you were pushing the memory envelope.
While not the most common scenario, I’ve gone through it myself on a not-so-great day. So I did a little research and developed a pretty simple solution that I’ll lay out here in case it interests others.
Notice for your time and mental health
This guide assumes you already have a swap partition set up that you can hibernate to.
It doesn’t cover a set up for basic hibernation.
It covers how to restrict the usage of that swap partition to just the hibernate/resume functionality.
The 4 Modifications
There are only 4 modifications to systemd
automation services needed to make this work.
0. vm.swappiness
(optional)
At the bottom of the file /etc/sysctl.conf
, add the line
vm.swappiness=0
I recommend this for the basic set up described here, but you can have it both ways–a regular swap partition or file and a dedicated hibernation partition, but that’s a bit more convoluted, and this guide is aimed at people who feel they have enough main memory, and just want to ensure hibernation works when they need it to.
1. Disable the swap partition during system initialization
Create a new systemd
service file in the directory /etc/systemd/system
, I’ll refer to it as /etc/systemd/system/hibernation--swapoff_during_initialization.service
, and in it put:
[Unit]
Description=swapoff hibernation partition on system initialization
Before=multi-user.target
[Service]
Type=oneshot
ExecStart=/sbin/swapoff -U <hibernation_partition_UUID>
[Install]
RequiredBy=multi-user.target
You can get the UUID of the hibernation partition with the command:
blkid /dev/path/to/partition # This is the command, below is the UUID
/dev/path/to/partition: UUID="01234567-89ab-cdef-0000-000000000000" TYPE="swap"
So in this case, the file would read:
[Unit]
Description=swapoff hibernation partition on system initialization
Before=multi-user.target
[Service]
Type=oneshot
ExecStart=/sbin/swapoff -U 01234567-89ab-cdef-0000-000000000000
[Install]
RequiredBy=multi-user.target
Then enable the service:
sudo systemctl enable hibernation--swapoff_during_initialization.service
2. Bypass the login daemon’s hibernation attempt filter
By default, systemd-logind
will check to see that there is enough active swap space to perform hibernation before allowing a hibernation attempt.
Normally, this is a good thing, but it prevents us from trying to hibernate with “zero” swap space, which is what we’re trying to do–We want the system to believe that there is NO swap space available, EXCEPT when we try to hibernate. Otherwise, it would use the partition like any other swap memory.
To bypass systemd-logind
, we need to modify its service file at
/etc/systemd/system/systemd-logind.service
,
and add the line
Environment=SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1
(Thanks to @arvidjaar
for pointing out this “big hammer” at Failed to hibernate system via logind: Not enough swap space for hibernation · Issue #15354 · systemd/systemd · GitHub)
3. Re-enable swap on the hibernation partition just prior to hibernation
Create another new systemd
service file in the directory /etc/systemd/system
, I’ll call this one /etc/systemd/system/hibernation--swapon_before_sleep.service
:
[Unit]
Description=swapon hibernation partition before system sleep
Before=sleep.target
[Service]
Type=oneshot
ExecStart=/sbin/swapon -U 01234567-89ab-cdef-0000-000000000000
[Install]
RequiredBy=sleep.target
Then enable it:
sudo systemctl enable hibernation--swapon_before_sleep.service
4. Re-disable swap on the hibernation partition on resume
As mentioned in the same above linked github issue (Failed to hibernate system via logind: Not enough swap space for hibernation · Issue #15354 · systemd/systemd · GitHub), there isn’t a wakeup.target
and After=sleep.target
is non-functional, similar to After=shutdown.target
–after the system shuts down, there’s nothing left to do, and no memory of what should be done–the system is fresh. A similar state of affairs seems to hold for sleep.target
, so instead we turn to /usr/lib/systemd/system-sleep
, a directory for pre- and post- sleep.target
hooks (which trigger on hibernate/resume).
In my testing, the pre-sleep hook here didn’t trigger until after hibernation entry progressed further along, meaning it doesn’t work effectively as the pre-hibernate swapon
trigger. That’s why we made the dedicated systemd
service file, and there doesn’t appear to be a convenient way to handle resume via service files, so here we are.
Anyway, this configuration file will just handle re-disabling swap when the system resumes from hibernation. In /usr/lib/systemd/system-sleep/hibernation_resume
, put:
#!/bin/sh
case ${1} in
post) swapoff -U 01234567-89ab-cdef-0000-000000000000;;
esac
Reboot and test
With the modifications made and new services enabled, it’s time to reboot and test the system:
reboot
Once you re-login and get to a shell, run
free -h
You should see in the Swap row 0B
for total, used, and free.
In my case (I have 32GB installed, 1GB is reserved):
free -h
total used free shared buff/cache available
Mem: 31Gi 2.7Gi 26Gi 431Mi 2.6Gi 28Gi
Swap: 0B 0B 0B
You can also check the lsblk
command output to see that your partition doesn’t specify [SWAP]
as a mount point. Here’s mine both before and after the reboot for example:
BEFORE:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 465.8G 0 disk
├─nvme0n1p1 259:1 0 512M 0 part /boot/efi
├─nvme0n1p2 259:2 0 512M 0 part /boot
└─nvme0n1p3 259:3 0 464.8G 0 part
└─nvme0n1p3_crypt 254:0 0 464.7G 0 crypt
├─lvm2_vg-hibernation_swap_space 254:1 0 32G 0 lvm [SWAP]
└─lvm2_vg-root_file_system 254:2 0 432.7G 0 lvm /
AFTER:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 465.8G 0 disk
├─nvme0n1p1 259:1 0 512M 0 part /boot/efi
├─nvme0n1p2 259:2 0 512M 0 part /boot
└─nvme0n1p3 259:3 0 464.8G 0 part
└─nvme0n1p3_crypt 254:0 0 464.7G 0 crypt
├─lvm2_vg-hibernation_swap_space 254:1 0 32G 0 lvm
└─lvm2_vg-root_file_system 254:2 0 432.7G 0 lvm /
This is to verify the 1st modification, disabling swap during initialization.
Next, do the same after initiating hibernate.
sudo systemctl hibernate
Normally this will write the contents of RAM to your NVM storage and poweroff the machine, unless you have an unusual configuration.
Assuming it powered off, power it back on, go through the boot menu and login as applicable. If your screen and windows look the same as before running hibernate
, that’s a good sign everything is working.
Re-run the free -h
and lsblk
commands. You should see the same thing as before:
0B
in the Swap row for total, used, and free, and that [SWAP]
isn’t listed as a mount point for the swap partition. If so, then everything went well.
If you have other methods of initiating hibernate set up, like pressing a power button or closing your laptop lid, definitely test those out too and make sure they work.
A good final test for successful hibernation in general, not just a “zero-swap” system, is to start a YouTube video, move to the half-way point in the timeline, then initiate hibernate and power back on. If you’re back at the same place in the video, your set up is working well.
– 0hana