Fast Linux Reboot Using kexec
If you ever tried to analyze what happens during the boot, you might notice that the lion’s share is taken by the firmware that runs even before the bootloader. In some cases, this time can be ridiculously long.
— systemd-analyze plot on my ThinkPad E14 Gen 6 laptop
Out of the 50 seconds that took the computer to start, 31 were taken by firmware 🤯
This particular ThinkPad is especially bad, but even in the better cases, it’s common to see around 15 seconds inside the firmware code. The worst part is that it happens not only during the “cold” boot but any time you restart your computer.
The good thing is that in most scenarios, we don’t need to repeat hardware initialization and can skip the whole firmware step.
kexec_load and kexec_file_load
First, a little bit of theory 🤓
Linux kernel (since 2.6.13 & 3.17 respectively) implements kexec_load and kexec_file_load system calls, that allow to load a new kernel directly from the running kernel:
#include <linux/kexec.h> /* Definition of KEXEC_* constants */
#include <sys/syscall.h> /* Definition of SYS_* constants */
#include <unistd.h>
long syscall(SYS_kexec_load, unsigned long entry,
unsigned long nr_segments, struct kexec_segment *segments,
unsigned long flags);
long syscall(SYS_kexec_file_load, int kernel_fd, int initrd_fd,
unsigned long cmdline_len, const char *cmdline,
unsigned long flags);
— kexec_load(2)
After that we should invoke reboot(2) with the flag LINUX_REBOOT_CMD_KEXEC and voilà , we rebooted avoiding firmware and bootloader stages!
From Theory To Practice
But how to do it in practice? At first glance, systemd offers a command that does what we need:
systemctl kexec
Shut down and reboot the system via kexec
— man systemctl
Indeed, this command does what we need. The bad news is that in most cases this command won’t work out of the box, so you can give up a dream to flex in front of a colleague by casually invoking it on their workstation 😄
But the good news is that it’s possible to fix and if we bother to do some plumbering, it will save us ages wasted on staring at the black screen.
The first obstacle is that systemctl kexec needs kexec binary to be installed:
/sbin/kexec is not available: No such file or directory
kexec is just a simple utility that invokes one of the system calls I mentioned above. It also can (through kexec -e) invoke reboot syscall with the right flag, so in theory kexec is all we need to do fast reboot. Of course, in practice, the right way is to call it through systemd, since systemd knows how to do a graceful shutdown.
On Ubuntu kexec can be installed using apt-get install kexec-tools.
Second, if you are not a happy Arch Linux user (btw, I’m not using Arch 😄), the most probable outcome of invoking systemctl kexec will be:
No boot loader entry suitable as default, refusing to guess.
The reason is pretty curious. systemctl kexec needs to know which kernel to load and with what arguments, but the only way systemctl accepts this information is through the home-grown Bootloader Specification interface. By coincidence, the only bootloader that implements this spec is systemd-boot 🤡
A few others tried, but “NO, NOT LIKE THIS”:
grub on fedora took the boot loader spec and turned it into a monster. It’s nuts. Ignore that. They turned it into a macro language with pattern expansion and so on. It’s totally crazy. And it really sucks they just took our name and turned it into such a monster.
Fortunately, there are several ways to make systemctl kexec work.
Install a systemd-boot
AFAIK, on Arch Linux systemd-boot is a default bootloader, so if you are using Arch, you are lucky and systemctl kexec should work out of the box. You can install systemd-boot on other distros (e.g. Ubuntu) and it will even give some extra benefits (see later) on top of the working systemctl kexec. But if you are not willing to change your bootloader, read further.
Preload Kernel Using /sbin/kexec
Through a Shell Command
The most dumb and straightforward way is to make a shell command that preloads kernel and then invokes systemctl kexec. For example, you can add to ~/.bashrc the following helper:
kreboot() {
sudo -i /bin/bash << EOF
set -e
kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --reuse-cmdline
systemctl kexec
EOF
}
This way you will reboot normally through reboot and fast-reboot throuhg kreboot. It will ask password (built-in reboot doesn’t), but on the other hand systemd kexec also asks password 🤷
Try to Hook Up an Auxiliary Service to kexec.target
This is just an idea 💡 not something that really works. If you are the person who likes to do things in a difficult, but “right” way, you may try to implement the same idea with preloading by making a one-shot service that is wanted by kexec.target and starts before it. Indeed, man systemctl says that systemctl kexec is merely an
… equivalent to systemctl start kexec.target -job-mode=replace-irreversibly –no-block
Unfortunately, systemctl kexec seems to read bootloader specification even before starting any services. But better do not trust strangers from the Internet and try it yourself 😇
Hook Up an Auxiliary Service to reboot.target
Honestly, this way is my favorite. First, it works. Second, it makes reboot command and even Gnome/KDE GUI (!) to reboot using kexec. I found this method in the GitHub issue thread and slightly changed it but the credits still go to GitHub user @iam-TJ.
First, let’s create a one-shot service that will preload our kernel. Create a file /etc/systemd/system/kexec-preload.service with the following content:
[Unit]
Description=Loads current kernel for kexec
Before=umount.target systemd-reboot.service
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/usr/sbin/kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --reuse-cmdline
[Install]
WantedBy=reboot.target
Then enable it:
systemctl enable kexec-preload.service
Second, create an override for systemd-reboot.service that invokes systemctl kexec. Create a file /etc/systemd/system/systemd-reboot.service.d/override.conf with the following content:
[Unit]
SuccessAction=none
[Service]
Type=oneshot
ExecStart=
ExecStart=/usr/bin/systemctl kexec
Done! Now you can reboot and see how fast it is now ✨ In case if it doesn’t work check journalctl -b -1 -r to see that our service actually started.
In my case rebooting this way takes ~13 seconds (about 2 seconds to shut down and about 10 seconds to start) which is a HUGE improvement compared to the 50 seconds that it was before 💀
The obvious downside of this method is that if you ever want to reboot in the “normal” way (to see the bootloader menu, for example) you will need to remove this override (e.g. by systemctl revert systemd-reboot.service) and then reinstall it again. So, maybe having a dumb simple shell script is the best way, after all 🤷
Glimpse into the Brighter Future
Although right now Bootloader Spec is not implemented in such distors as Ubuntu or Fedora (and probably many others), and the whole state of affairs is little bit messy, there is some work happening on this front and a hope that it will be better in the future.
For example, it will be possible to reboot to a specific menu item of a bootloader through systemctl reboot --boot-loader-entry=entry which would be super handy in case of a dual boot.
But for a while, you can use one of the workarounds that we’ve just talked about, or … install Arch systemd-boot 😄