Operating Systems · Boot and Firmware
Unpacking an Initramfs and Switching to the Real Root
How the kernel unpacks a compressed cpio archive into a temporary filesystem, what pivot_root and switch_root change, and why the old root cannot simply be unmounted.
A kernel that mounts a real root filesystem directly needs a driver for that filesystem and for the storage controller underneath it, both compiled in and both working at the moment the mount is attempted. That is a reasonable requirement for a system with a known disk and a known filesystem, and a bad one for a system that might boot from a network, an encrypted volume, a software RAID array, or a filesystem whose driver is a loadable module.
The initramfs exists to defer the decision. The kernel mounts a small, memory-backed filesystem containing just enough user-space code to find and mount the real root, and the program in that archive performs the mount. The kernel then hands over, and the temporary filesystem is discarded or kept as the new root.
How the archive reaches the kernel
The initramfs is a single file: a gzip, xz, zstd, or uncompressed cpio archive, in the newc format defined by the cpio specification’s “new ASCII” variant. The boot loader places it in memory and records its physical address and size in the boot parameters structure. The kernel, during initialization, finds the archive at that address, detects the compression format by its magic bytes, decompresses it if necessary, and parses the cpio stream.
The newc format is a sequence of headers, each followed by a filename and then the file’s data, with every field padded to a four-byte boundary. Each header is 110 bytes of ASCII hexadecimal: magic 070701, inode number, mode, user and group identifiers, link count, modification time, file size, device numbers, and the lengths of the name and data. A trailer entry with the name TRAILER!!! ends the stream. There is no index, no central directory, and no random access: the archive is consumed sequentially from the first byte to the trailer.
That structure is why the kernel can unpack it with a small parser that allocates pages as it goes, and why an archive that is truncated or has a corrupt header cannot be partially recovered. A parser that encounters an unknown magic value stops and reports the archive as invalid, and the kernel then has no initramfs at all.
Where the contents land
The kernel creates an instance of rootfs, a ramfs-based filesystem that cannot be unmounted, and unpacks the archive into it. rootfs is always present, even on a system with no initramfs, because it is the mount point the kernel uses before any real filesystem is mounted. If an initramfs is supplied, its contents populate that filesystem; if not, rootfs is empty and the kernel proceeds to mount the root device named on the command line.
Unpacking preserves the archive’s metadata: mode bits, ownership, and device numbers for the entries that describe device nodes. Symbolic links are created from their target strings. Hard links are handled through the inode number field, which is why the format carries one. Directories are created before their contents, and the archive’s order is expected to satisfy that, though the parser creates missing parents as needed.
The result is a complete filesystem in memory, sized to the archive’s decompressed content. Compression ratios for the mix of binaries such an archive contains are modest, so the resident cost is a multiple of the archive’s size on disk, all of it resident until the files in it are removed. That memory is not reclaimable in the way page cache is: it is file data in a ramfs, and ramfs does not write back or evict. An oversized initramfs is therefore a permanent cost, which is why the archives shipped by distributions are built from a minimal set of binaries and why the trend has been toward smaller archives rather than larger ones. The same constraint applies to the early console, which must be usable before any filesystem at all is mounted, as described in early console output before drivers exist.
The switch
After the kernel has unpacked the archive, it tries to execute /init from the new root. If that file exists and is executable, the kernel runs it as PID 1 and does not attempt to mount a root device at all. The program is now responsible for everything: finding the real root, mounting it, and moving the system onto it.
The traditional mechanism is pivot_root. It takes two directory paths, both on the current root filesystem: the new root and a directory under it that will receive the old root. The call makes the new root the root of every process in the mount namespace and mounts the old root at the second path. It does not change the caller’s working directory, so the caller must chdir to the new root afterwards; a process left with a working directory on the old root keeps that directory reachable and keeps the filesystem busy.
Two constraints make pivot_root awkward. The new root must be a mount point, not merely a directory, so the program must mount the real root somewhere before pivoting to it. And the old root must not be shared with any other mount namespace, which means a program that has already created a namespace for a service cannot pivot afterwards. The call also requires the process to have the appropriate capability, which PID 1 has.
The newer mechanism is switch_root, which is not a system call but a utility that performs a sequence: it changes directory into the new root, removes the contents of the old root, moves the new root’s mount to /, changes the root with chroot, and then execs the real init. The deletion is not incidental. The initramfs occupies memory that will never be freed unless its files are unlinked, since ramfs has no writeback and no eviction, so removing the contents is what releases the pages. What remains is an empty ramfs mounted at the root that the new mount has replaced, holding nothing and reachable by no path.
# the essential sequence performed by an initramfs init program
mount -t ext4 /dev/mapper/root /newroot # real root, already assembled
mount --move /sys /newroot/sys # carry kernel filesystems across
mount --move /proc /newroot/proc
mount --move /dev /newroot/dev
exec switch_root /newroot /sbin/init # deletes initramfs, chroots, execs
The --move operations matter because /proc, /sys, and /dev are already mounted by the initramfs program, and they contain state the real system needs: the device nodes that were created, the kernel’s view of the process table, and the sysfs tree that describes the hardware. Without moving them, the real init would mount fresh instances, and any device node or sysfs attribute discovered during the initramfs phase would be lost.
What breaks during the switch
The classic failure is a real root that is not actually mounted when switch_root runs. The utility execs the new init from the new root, and if the new root is an empty directory, the exec fails and PID 1 exits, which panics the kernel. The error message names the path that could not be executed, and the cause is usually a device that did not appear because a driver module was missing from the archive.
A second failure is a mount that is still busy. pivot_root fails if the old root has a filesystem mounted under it that is in use, and switch_root’s deletion step fails if a file is open or a mount point is still populated. Programs that keep a log file open on the initramfs while switching produce exactly this, and the deletion step reports the offending path.
A third is the loss of the initramfs’s own contents. Anything the program needs after the switch, whether a key file, a configuration fragment, or a firmware blob, must be copied to the new root before the deletion. The deletion is recursive and unconditional, and there is no way to recover a file from it afterwards.
A fourth is the ordering of the move operations. If /proc is moved before the new root’s /proc directory exists, the move fails; if it is moved after switch_root has begun, the path resolution may already refer to the new root. The sequence is: mount the real root, ensure the target directories exist, move the kernel filesystems, then switch.
The real root is often assembled from components that do not exist as a single device: a software RAID array assembled from several disks, a logical volume, or a decrypted container. Assembling those requires reading metadata that lives on the disks themselves, which is why the initramfs contains the tools for it and why the kernel does not attempt it. Once the switch is complete, the system is running entirely from the real root, and the boot has reached the point described in from the first kernel thread to PID 1, with the initramfs program replaced by the real init.
References
- The Linux Kernel documentation: Ramfs, rootfs and initramfs — Linux kernel documentation
- pivot_root(2): Linux manual page — Linux man-pages project
- cpio(5): Linux manual page — Linux man-pages project