Suspending to Disk in Ubuntu-based Distro with BTRFS filesystem
October 1, 2023
linux, btrfs, swap

Hibernation (also known as suspend to disk) is one of the many features which most Linux distributions do not officially support. Suspending to disk is not an easy feature to implement and involves certain risks. The process starts at the kernel.

For a system to enter the hibernation state, the kernel sleeps all active processes pausing the OS and dumping the RAM to non-volatile memory and finally goes into the power-off state where a complete power loss does not result in the loss of data. On restart, it might be slower to exit the state as the memory content is loaded back into RAM before the computer completely starts.

I am working on Pop OS 22.04 with a custom partition structure using BTRFS filesystem and no encrypted partition. While this entry is focused on Pop OS, other Linux-based OS, Ubuntu and Ubuntu-based distributions might find some of its content relevant.

This entry assumes the reader already has a BTRFS system with no swap partition or file.

Enabling suspend to disk in Pop OS requires the following steps:

  • Disable the default Zram
  • Create a swap sub-volume and swapfile
  • Update kernel stubs
  • Test and enable hibernation

There are some limitations to the implementations of Linux swap in BTRFS 1. A few of these require that the filesystem must be only a single device and have only a single data profile, no copy-on-write, no compression, etc.

Disable the default Zram

Pop OS now has Zram enabled by default. With this module enabled, the kernel can create a RAM device with active disk compression of files and or inactive memory pages. The RAM device can be used for swap or as a disk for storage of temporary files.

For hibernation, Zram is not a suitable option. The module may be used for swap in RAM to speed up operation, however, contents in the RAM are volatile and do not persist after a power loss.

Terminal

# Remove Pop OS Zram settings sudo apt purge pop-default-settings-zram # Reboot the system sudo reboot now

Create swap sub-volume and swapfile

The sub-volume

BTRFS sub-volumes are mountable file trees (not block devices or logical volumes) with support for more than a single file tree on different path levels. The filesystem has a top-level sub-volume with an ID of 5. For the swap sub-volume to be created on this level, the root sub-volume needs to be mounted with the target sub-volume ID 5.

Terminal

# List block devices lsblk -o "NAME,UUID,SIZE,TYPE,MOUNTPOINTS"

text

$ lsblk -o "NAME,UUID,SIZE,TYPE,MOUNTPOINTS" NAME UUID SIZE TYPE MOUNTPOINTS nvme0n1 953.9G disk ├─nvme0n1p1 8132-6E65 2G part /boot/efi ├─nvme0n1p2 128M part ├─nvme0n1p3 4C747256747242AE 736.2G part ├─nvme0n1p4 5A900223900205EB 990M part ├─nvme0n1p5 DE40025D40023CB1 17.8G part ├─nvme0n1p6 42C6987BC6987141 1.4G part ├─nvme0n1p7 269C-2883 4G part /recovery └─nvme0n1p8 21d80ba7-c82e-44f6-9ad6-1c815922ac27 191.3G part /home /

Your drive will have different names and layouts. The current device is NVME drive and uses a PCI driver, hence the device names begin with nvme. SCSI/PATA/SATA/USB devices may have different names similar to sda, sda1, et cetera.

The target partition is nvme0n1p8 which is the BTRFS partition with the root and home sub-volumes mounted.

Terminal

# Mount the root level sudo mount -o subvolid=5,ssd,noatime,space_cache=v2,commit=10,compress=zstd,autodefrag /dev/nvme0n1p8 /mnt # Create a @swap sub-volume sudo btrfs subvolume create /mnt/@swap # List sub-volumes on the root level sudo btrfs subvolume list /mnt

The mount options used above should be adjusted per use case. For instance, depending on the version of BTRFS progs, the ssd may not be required as BTRFS will enable or disable SSD optimisations based on the disk type 2. The swap sub-volume will be created on the root level. Listing of the BTRFS sub-volumes will show the new @swap on level 5.

diff

ID 256 gen 102182 top level 5 path @ ID 257 gen 102182 top level 5 path @home + ID 258 gen 100589 top level 5 path @swap

The swapfile

The size of the swap file is proportional to the size of the system RAM. For suspending to disk, we need a swapfile bigger than the RAM. free -h shows the system memory information.

text

$ free -h total used free shared buff/cache available Mem: 31Gi 11Gi 14Gi 1.6Gi 5.0Gi 16Gi

Based on the output, the total memory on our system is 31Gi, hence a swapfile of 33Gi is enough to accommodate the contents of the RAM at hibernation.

This step of the process requires an updated version of BTRFS, at least version 6.1 and above. The version provides easy commands to make a swapfile and to retrieve the resume_offset value1.

Terminal

sudo btrfs filesystem mkswapfile --size 33G /mnt/@swap/swapfile

Terminal

sudo truncate -s 0 /mnt/@swap/swapfile sudo chattr +C /mnt/@swap/swapfile sudo fallocate -l 33G /mnt/@swap/swapfile sudo chmod 0600 /mnt/@swap/swapfile sudo mkswap /mnt/@swap/swapfile

Enable swap

Update mount entries in /etc/fstab to include the swap sub-volume and the swap file.

Terminal

# Unmount the root level sub-volume sudo umount /mnt # Set a variable to hold the target partition UUID UUID="$(lsblk --noheadings -o UUID /dev/nvme0n1p8)" # Update fstab entries sudo tee -a /etc/fstab << EOF UUID=$UUID /swap btrfs defaults,subvol=@swap,nodatacow,noatime,nospace_cache,compress=no 0 0 /swap/swapfile none swap defaults 0 0 EOF # Mount all entries including the @swap sub-volume sudo mount -av # Activate swapfile sudo swapon /swap/swapfile # Show system memory and swap free -h

The new output of free should include a row with the swap information.

text

$ free -h total used free shared buff/cache available Mem: 31Gi 11Gi 13Gi 1.6Gi 5.8Gi 16Gi Swap: 32Gi 0B 32Gi

Update kernel stubs

With the swapfile now available, it only serves to store inactive memory pages when the system is running out of memory. Adding options which can be passed to the kernel at boot time will ensure that the system is resumed after hibernation.

The resume option specifies with disk partition and resume_offset points to the exact location of the swapfile. The resume_offset is required when using a swapfile, otherwise, only the resume is required for a swap partition.

The following command shows the swapfile offset.

text

$ sudo btrfs inspect-internal map-swapfile /swap/swapfile Physical start: 112232574976 Resume offset: 27400531

Terminal

# Set a variable to hold the offset value OFFSET="$(sudo btrfs inspect-internal map-swapfile -r /swap/swapfile)" # Ensure the target partition UUID is still set echo $UUID # Optional: Remove any resume and resume_offset options if they already exist # Replace <UUID> and <OFFSET> with their values sudo kernelstub -d 'resume=UUID=<UUID>' 'resume_offset=<OFFSET>' # Add options if they are not present sudo kernelstub -a "resume=UUID=$UUID resume_offset=$OFFSET" # Show current configuration sudo kernelstub -p # Update initramfs config echo "resume=UUID=$UUID resume_offset=$OFFSET" | sudo tee /etc/initramfs-tools/conf.d/resume sudo echo $OFFSET > /sys/power/resume_offset # Update existing initramfs sudo update-initramfs -u

Test and enable hibernation

Ensure hibernation works by executing the following command.

Terminal

systemctl hibernate

Check the system logs for error messages. Any of the following commands should provide additional information about the problem.

Terminal

sudo journalctl -r -u systemd-hibernate.service sudo journalctl -r -u hibernate.target sudo systemctl list-dependencies -a hibernate.target

Common error messages include:

  • No such device: Check that you have the correct kernel options. Ensure the resume and resume_offset options are correct.
  • No space left on device: The swapfile or swap partition does not have enough space. Your swap space may be less than the RAM or there are multiple swap spaces with at least one not having enough space.

Add Hibernate and Hybrid Sleep options to the power menu, using Hibernate Status Button GNOME-Shell extension.

Ultimately, add the following policy kit.

Terminal

sudo tee /etc/polkit-1/localauthority/10-vendor.d/com.ubuntu.desktop.pkla << EOF [Enable hibernate in upower] Identity=unix-user:* Action=org.freedesktop.upower.hibernate ResultActive=yes [Enable hibernate in logind] Identity=unix-user:* Action=org.freedesktop.login1.hibernate;org.freedesktop.login1.handle-hibernate-key;org.freedesktop.login1;org.freedesktop.login1.hibernate-multiple-sessions;org.freedesktop.login1.hibernate-ignore-inhibit ResultActive=yes EOF

Footnotes

  1. BTRFS Swapfile 2

  2. BTRFS Mount Options

Reach out

© 2024 Chuma Umenze. Some rights reserved.