Initramfs Tutorial | nairobi-embedded


: 15

Intramfs Format

Initramfs images are compressed (typically, gzip) cpio archive files. Valid archive formats for initramfs include newc and crc. See info cpio for a description of these formats. The kernel has support for other types of cpio archive compression other than CONFIG_RD_GZIP e.g CONFIG_RD_BZIP2, CONFIG_RD_LZMA, CONFIG_RD_XZ, CONFIG_RD_LZO and CONFIG_RD_LZ4.

Below are examples of inspecting the contents of a gzip'd cpio initramfs archive and restoring the image from its contents.

Expand and Extract

$ mkdir initramfs-contents && cd initramfs-contents
$ gunzip --stdout  /boot/initrd.img-`uname -r` | cpio -idv
$ ls -CF
bin/  conf/  etc/  init*  lib/  lib64/  run/  sbin/  scripts/
$ du -sh
48M .

Archive and Compress

$ cd initramfs-contents
## make changes if necessary
$ find . | cpio -H newc -o | gzip -9 > ../initrd.img-`uname -r`-custom
$ cd ..
$ ls -CF
initramfs-contents/  initrd.img-3.11.0-15-generic-custom

Initramfs Mechanism


As of Linux 2.6, the kernel mounts rootfs (a special instance of tmpfs, the "improved" version of ramfs) as its first filesystem. The kernel then expands and extracts a compressed cpio archive into it. Kernel images (i.e. vmlinux) always have one of these archives embedded in them. By default, these archives are contain nothing, save for an empty /root directory and the /dev/console file, and so rootfs will also contain these files upon archive extraction.

This default skeleton archive can be replaced by specifying an initramfs to embed in the kernel image during kernel configuration or by supplying an external initramfs during boot (as was done with its initrd predecessor). Which ever the case, the kernel attempts to execute /init from rootfs. If this succeeds, the kernel executes it as PID 1. In this case, the kernel is done booting and /init is then responsible for bringing up the rest of the system.

Put another way, using initramfs just means supplying the kernel with an /init program in a cpio archive.

If the kernel cannot find /init in rootfs, it falls back to the legacy behaviour of parsing the root= option in the boot commandline to find another filesystem to mount on top of rootfs in order to run an init program out of there.


Unlike initrd's /linuxrc, which used pivot_root1 and umount when switching to another root device, initramfs' /init uses switch_root to achieve this. This is because kernel uses rootfs internally as the starting and ending point for searching through the kernel's doubly linked list of active mount points and so rootfs can't be moved or umount'd.

switch_root deletes everything out of rootfs (including itself), does a mount --move that overmounts rootfs with the new root, before finally executing the specified init program:

$ cat /proc/mounts | egrep '( / |rootfs)'
rootfs / rootfs rw 0 0
/dev/disk/by-uuid/68a(...)bdb / ext3 rw,relatime,errors=remount-ro,user_xattr,acl,barrier=1,data=ordered 0 0

Embedded vs. External Initramfs

There are two ways of supplying the kernel with an initramfs:

  • Embedding i.e. replacing the kernel's default built-in skeleton cpio archive with a user-supplied initramfs. In other words, linking an initramfs archive into the Linux image during the kernel build process.

  • Externally i.e. passing a separate initramfs file to the kernel via the bootloader.

Embedded initramfs

The CONFIG_INITRAMFS_SOURCE option can be used to specify a source for the initramfs archive. The resulting archive will automatically get incorporated into the Linux image during kernel build. This option can point to an existing gzip'd cpio archive, a directory containing files to be archived, or a configuration (text) file specification. By default, this option is empty, and the default skeleton archive gets generated and linked into the vmlinux image during kernel build.

External initramfs

An external initramfs is a separate compressed cpio archive that is not linked into the kernel image. Using an external initramfs presents the possibility of including non-GPL code in the archive since it is not linked with the GPL licensed Linux kernel binary. An external initramfs is employed in a manner that is similar with its initrd predecessor:

  • CONFIG_BLK_DEV_INITRD need be enabled in the kernel. The kernel will correctly autodetect the image type i.e. initramfs instead of initrd. In the case of initramfs, the kernel will expand and extract the external compressed cpio archive into rootfs before trying to run /init.

  • The kernel image and the initramfs archive are loaded separately into memory by a bootloader.

Recall that kernel image still contains the default skeleton initramfs embedded in it. The external initramfs archive is extracted into rootfs after the kernel's built-in archive. This means that files in the former will overwrite any conflicting files in the latter. This mechanism can be used to supplement an embedded initramfs - update or customize rootfs without the need to recompile the kernel. Check out the explanation in Section Helloworld Example: Additional Notes for a trivial example of this mechanism in action. For code inspection, see init/initramfs.c:populate_rootfs( )2.

Building initramfs

For illustrative purposes, the following Linux sources are configured for test with QEMU-PC3:

$ wget -c
$ tar xJf linux-3.11.tar.xz
$ LINUXDIR=${WORKDIR}/linux-3.11
$ make x86_64_defconfig
$ make [-jN]
  ZOFFSET arch/x86/boot/zoffset.h
  OBJCOPY arch/x86/boot/vmlinux.bin
  AS      arch/x86/boot/header.o
  LD      arch/x86/boot/setup.elf
  OBJCOPY arch/x86/boot/setup.bin
  BUILD   arch/x86/boot/bzImage
Setup is 15548 bytes (padded to 15872 bytes).
System is 5311 kB
CRC 4d77e15a
Kernel: arch/x86/boot/bzImage is ready  (#1)

Helloworld Example

A good first step is to get a statically linked helloworld program to run as /init. This example is taken from Documentation/ramfs-rootfs-initramfs.txt. Recall that using an initramfs just means supplying the kernel with an /init program for rootfs.

$ cat > hello.c << EOF
> #include <stdio.h>
> #include <unistd.h>
> int main(int argc, char **argv)
> {
>   printf("Dunia, vipi?\n");
>   sleep(99999999);
>   return 0;
> }

$ gcc -Wall -static hello.c -o hello

External initramfs test


$ cp hello init && echo init | cpio -o -H newc | gzip > test.cpio.gz

$ qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd ${WORKDIR}/test.cpio.gz -append "console=ttyS0" -nographic
[    0.441792] Freeing unused kernel memory: 1040K (ffffffff81cc7000 - ffffffff81dcb000)
[    0.443152] Write protecting the kernel read-only data: 12288k
[    0.445163] Freeing unused kernel memory: 344K (ffff8800017aa000 - ffff880001800000)
[    0.451379] Freeing unused kernel memory: 1584K (ffff880001a74000 - ffff880001c00000)
Dunia, vipi?

To quit QEMU, type CTRL+a then c to enter the QEMU monitor console, then type quit:

QEMU 1.0 monitor - type 'help' for more information
(qemu) quit

Refer to QEMU HMI: Nographic Mode for tips on using the QEMU monitor console in nographic mode.

Embedded initramfs test

The files under usr/ and scripts/ take care of building the default skeleton initramfs as well as embedding a user supplied archive configuration. Before kernel build:

$ ls usr/
gen_init_cpio.c  initramfs_data.S  Kconfig  Makefile

After the kernel build steps shown above (default initramfs settings):

$ cat .config | grep CONFIG_INITRAMFS_SOURCE    

$ ls usr/
built-in.o     gen_init_cpio.c      initramfs_data.o  Kconfig   modules.builtin
gen_init_cpio  initramfs_data.cpio  initramfs_data.S  Makefile  modules.order

where usr/initramfs_data.cpio is the default initramfs cpio archive.

There are a number of ways of embedding an initramfs. CONFIG_INITRAMFS_SOURCE may point to an existing gziped cpio archive, or a directory containing files to be archived, or a configuration file.

The configuration file is a simple text file that describes an initramfs archive directory. usr/gen_init_cpio is used to generate a cpio archive from the configuration file. If a directory is specified instead, then usr/Makefile in conjuction with scripts/ will first generate a configuration file from it.

When building the default archive, or an archive from a configuration file, the kernel will not rely on external cpio tools. If, on the other hand, an existing gzip'd cpio archive is specified against CONFIG_INITRAMFS_SOURCE, then the user will require external cpio utilities to build the archive.

The first two methods described below will require superuser priviledges in some of the steps. Method 3 is provides a way around this "quirk".

Method 1: Embedding an existing cpio.gz archive
$ mkdir initramfs_tmp
$ INITRAMFSTMP=${WORKDIR}/initramfs_tmp 

### using default cpio archive as a template 
### A quirk; "sudo" required for "mknod /dev/console c 5 1" 
$ sudo sh -c "cat ${LINUXDIR}/usr/initramfs_data.cpio | cpio -idv"
1 block

$ cp -a ${WORKDIR}/hello init

$ ls -l
total 876
drwxr-xr-x 2 root root  dev
-rwxrwxr-x 1 siro siro  init
drwx------ 2 root root  root


  • $INITRAMFSTMP/init should have the eXecutable bit set.
  • /dev/console is required, otherwise the boot process will complain with:

    $ qemu-system-x86_64 -kernel arch/x86/boot/bzImage -append "console=ttyS0" -nographic   
    [    0.379340] Warning: unable to open an initial console.

    and the Dunia, vipi? message from /init will not appear on the display.

Now build and embed the gzip'd cpio archive into the Linux image:

$ sudo sh -c "find . | cpio -H newc -o | gzip > ${WORKDIR}/test.cpio.gz"

$ export WORKDIR

### run "make menuconfig" and edit "CONFIG_INITRAMFS_SOURCE" such that
$ cat .config | grep CONFIG_INITRAMFS_SOURCE

$ make [-jN]


$ qemu-system-x86_64 -kernel arch/x86/boot/bzImage -append "console=ttyS0" -nographic
[    0.432769] Freeing unused kernel memory: 1384K (ffffffff81cc7000 - ffffffff81e21000)
[    0.433680] Write protecting the kernel read-only data: 12288k
[    0.435818] Freeing unused kernel memory: 344K (ffff8800017aa000 - ffff880001800000)
[    0.442340] Freeing unused kernel memory: 1584K (ffff880001a74000 - ffff880001c00000)
Dunia, vipi?
Method 2: Supplying a directory of files for embedded archive build
$ sudo rm -r $INITRAMFSTMP/*
$ sudo sh -c "cat ${LINUXDIR}/usr/initramfs_data.cpio | cpio -idv"
$ ls
dev  root
$ cp -a ${WORKDIR}/hello init

$ ls -l

drwxr-xr-x 2 root root   4096 dev
-rwxrwxr-x 1 siro siro 883935 init
drwx------ 2 root root   4096 root

$ sudo chmod 777 -R *

$ ls -l
drwxrwxrwx 2 root root   4096 dev
-rwxrwxrwx 1 siro siro 883935 init
drwxrwxrwx 2 root root   4096 root

Changing permissions is done here otherwise the kernel build process below will complain with:

$ make
CHK     include/generated/compile.h
find: `${INITRAMFSTMP}/root': Permission denied
make[1]: *** [usr/initramfs_data.cpio] Error 1
make: *** [usr] Error 2



### run "make menuconfig" and set CONFIG_INITRAMFS_SOURCE such that
$ cat .config | grep CONFIG_INITRAMFS_SOURCE

$ make
CHK     include/generated/compile.h
GEN     usr/initramfs_data.cpio
AS      usr/initramfs_data.o
LD      usr/built-in.o

In otherwords, the archive is built as ${LINUXDIR}/usr/initramfs_data.cpio, replacing any pre-existing (default) archive.


$ qemu-system-x86_64 -kernel arch/x86/boot/bzImage -append "console=ttyS0" -nographic 
[    0.435910] Freeing unused kernel memory: 1900K (ffffffff81cc7000 - ffffffff81ea2000)
[    0.437275] Write protecting the kernel read-only data: 12288k
[    0.439274] Freeing unused kernel memory: 344K (ffff8800017aa000 - ffff880001800000)
[    0.445181] Freeing unused kernel memory: 1584K (ffff880001a74000 - ffff880001c00000)
Dunia, vipi?
Method 3: Using a Configuration File

This is the most flexible method. It provides a way around the "quirks" of Methods 1 and 2, i.e. the need for superuser priviledges in some of the steps.

The configuration file is a standard text file containing one entry per line. Each line starts with a keyword indicating what type of entry it describes. For the helloworld example:


$ sudo rm -rf *

$ cat > initramfs_config.txt <<EOF
> dir /dev 755 0 0
> nod /dev/console 644 0 0 c 5 1
> file /init ${INITRAMFSTMP}/init 755 0 0
$ cp ${WORKDIR}/hello init

$ ls -l
-rwxrwxr-x 1 siro siro 883935 init
-rw-rw-r-- 1 siro siro     71 initramfs_config.txt


  • eXecutable permissions must be set for /init in initramfs_config.txt.

  • Specifying the dir key word in initramfs_config.txt is a "necessity". Run usr/gen_init_cpio with no arguments for a full description of the file's format. Consult this link for more usage examples and a description of available keywords.



### run "make menuconfig" and set "CONFIG_INITRAMFS_SOURCE" such that
$ cat .config | grep CONFIG_INITRAMFS_SOURCE

$ make
  CHK     include/generated/compile.h
  GEN     usr/initramfs_data.cpio
  AS      usr/initramfs_data.o
  LD      usr/built-in.o

$ qemu-system-x86_64 -kernel arch/x86/boot/bzImage -append "console=ttyS0" -nographic
[    0.432501] Write protecting the kernel read-only data: 12288k
[    0.434455] Freeing unused kernel memory: 344K (ffff8800017aa000 - ffff880001800000)
[    0.439878] Freeing unused kernel memory: 1584K (ffff880001a74000 - ffff880001c00000)
Dunia, vipi?

Additional Notes

Note that with the external hello world initramfs, it was only required to archive a binary named init. However, with the embedded configurations, inclusion of device file, /dev/console, was also necessary in order to provide a system console. This is because when an external initramfs is used, files in its archive are added to rootfs, which already contains a /dev/console file from the kernel's default and built-in skeleton usr/initramfs_data.cpio archive. This scenario also serves as a trivial example of how an external initramfs can suppliment/extend an embedded initramfs. See Section External Initramfs.

Also note that for embedded initramfs configurations, it may be advisable to first delete usr/initramfs_data.cpio after making changes to CONFIG_INITRAMFS_SOURCE and before re-running make.

Building a custom initramfs

A custom and usable initramfs is a complete self-contained root filesystem for Linux. Distributions use an initramfs as a way of maintaining a small generic kernel image - leaving the complexities that arise from the diversity of end-user system configurations/requirements to the initramfs. These include issues such as mounting the final root filesystems which may be encrypted, across a network network (e.g. NFSroot), on LVM logical volumes, etc. Some embedded system configurations even use an initramfs as the final, memory resident root filesytem. In this respect, building a usable initramfs is a broad subject that addresses building a minimal root filesystem with all the essential utilities, and other topics such as cross-compilation and C library alternatives. A detailed coverage of the subject is, therefore, beyond the scope of this tutorial.

The example in this section is included mainly as an exercise. But it may also serve as a template for custom initramfs setups. Refer to Section Further Reading for pointers to building initramfs images for more complex scenarios.


The Busybox package is used here to provide the essential utiltities for the initramfs based root filesystem. To simplify illustration, this package is built with the distro's native toolchain and statically linked:


$ wget -c

$ tar xjf busybox-1.21.1.tar.bz2

$ BUSYBOX=${WORKDIR}/busybox-1.21.1


$ make defconfig

Now, run make menuconfig such that

# CONFIG_DESKTOP is not set
# CONFIG_MKTEMP is not set
# CONFIG_INETD is not set
# CONFIG_IPCALC is not set
# CONFIG_NSLOOKUP is not set

before building the Busybox utility:

$ make [-jN]
$ make install

$ tree -L 1 _install/
├── bin
├── linuxrc -> bin/busybox
└── sbin

$ file _install/bin/busybox 
_install/bin/busybox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24

Build the external initramfs:

$ rm -rf ${INITRAMFSTMP}/*

$ (cd _install && tar cf - .) | (cd $INITRAMFSTMP && tar xvpf - )


$ rm linuxrc

$ mkdir etc

$ echo "swara" > ${INITRAMFSTMP}/etc/hostname

$ touch ${INITRAMFSTMP}/etc/mdev.conf

$ mkdir etc/init.d && touch etc/init.d/rcS && chmod 0755 etc/init.d/rcS

$ touch init

$ chmod 755 init

At this point, edit ${INITRAMFSTMP}/init such that:

$ cat init


#Mount the root device if it exists
mountroot( )

    if [ -z $root ]; then
        echo "Did not specify \"root=\" option."

    if [ "${readonly}" = "y" ]; then

    echo -n "Attempting to mount ${root} on ${rootmnt}... " 
    mount ${roflag} ${rootfstype:+-t ${rootfstype}} ${rootflags} ${root} ${rootmnt} 2>/dev/null
    if [ "${mountstatus}" = "0" ]; then
        echo "Succeeded mounting ${root}"
        echo "Failed to mount ${root}"

    if [[ -x "${rootmnt}/${init}" ]]; then
        umount /sys /proc
        exec switch_root ${rootmnt} "${init}"

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys

ln -sf /proc/mounts /etc/mtab

#Prevent kernel messages from reaching the screen
echo 0 > /proc/sys/kernel/printk

#Clear the screen

#Create device nodes
[ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1
[ -e /dev/null ] || mknod /dev/null c 1 3
mdev -s

[ -e /dev/pts ] || mkdir /dev/pts
mount -t devpts -o noexec,nosuid,mode=0620 devpts /dev/pts


if [ -f "/etc/hostname" ] ; then
    /bin/hostname -F /etc/hostname > /dev/null 2>&1

#Parse command line options
for i in $(cat /proc/cmdline); do
    case $i in
            rootflags="-o ${i#rootflags=}"

# Mount final root filesystem - if any

# Otherwise, stick in initramfs environment as final root filesystem
echo " "
echo "Falling to initramfs"
exec /sbin/init

See Documentation/kernel-parameters.txt for a list of legal Linux commandline parameters.

Finally, generate the gzip'd cpio archive:

$ find . | cpio -H newc -o | gzip  > ${WORKDIR}/initrd.img.gz


Initial test

$ qemu-system-x86_64 -enable-kvm -kernel arch/x86/boot/bzImage -initrd ${WORKDIR}/initrd.img.gz -append "console=ttyS0" -nographic
Did not specify "root=" option.

Falling to initramfs

Please press Enter to activate this console.
/ # ls -CF
bin/  dev/  etc/  init* proc/ root/ sbin/ sys/  tmp/  var/

/ # uname -a
Linux swara 3.11.0 (...) x86_64 GNU/Linux

A Buildroot compiled root filesystem on a ext3 formatted raw disk image was used here. The disk image was partitioned in two with the root filesystem installed in the second partition.


$ qemu-system-x86_64 -enable-kvm -kernel arch/x86/boot/bzImage -initrd ${WORKDIR}/initrd.img.gz -drive file=${SOMEDIR}/brdisk-img.raw -append "root=/dev/sda2 console=ttyS0 rw" -nographic
Attempting to mount /dev/sda2 on /root... Succeeded mounting /dev/sda2
INIT: version 2.88 booting
Starting logging: OK
Starting rsyslog daemon: OK
Starting portmap: done
Starting atd: OK
INIT: Entering runlevel: 1

Welcome to Buildroot
buildroot login: root
[root@buildroot ~]# cat /proc/cmdline 
root=/dev/sda2 console=ttyS0 rw

[root@buildroot ~]# mount | grep ' / '
rootfs on / type rootfs (rw)
/dev/sda2 on / type ext3 (rw,relatime,data=ordered)

This case study just skimmed the surface of building a custom and usable initramfs. Note that for a truly versatile initramfs, logic to find/detect the final root filesystem and its filesystem type, provisions to allow specifying a root filesystem partition via UUID or partition LABEL, support for encrypted or root filesystem over a network, etc should be included in /init. In addition, the presence of the corresponding kernel modules will be required in the initramfs. Notice that with x86_64_defconfig, CONFIG_FS_EXT4_USE_FOR_EXT23 is built-in, and so no additional external modules were required in the setup above. See Section Further Reading for pointers to further reading on the subject

To make life easier for end-users, distributions include automated utiltities to facilitate building external initramfs for their systems. The next section includes an example of using one such utility with Debian/Ubuntu.

Distro's: Preparing an external initramfs with mkinitramfs(8)

Distributions usually provide a framework to (re-)generate the initramfs image during distro installation or kernel updates. For Debian based distro's, see update-initramfs(8) and mkinitramfs(8).

The following instructions generate an NFS-root capable initramfs for the current kernel image. They have been tested on Ubuntu 12.04 and 13.04:


$ cp -a /etc/initramfs-tools initramfs-tools-nfs

## edit 'initramfs-tools-nfs/initramfs.conf' such that
$ cat initramfs-tools-nfs/initramfs.conf

$ mkinitramfs -d initramfs-tools-nfs -o initrd.img-`uname -r`-nfs `uname -r`

$ ls
initramfs-tools-nfs  initrd.img-3.11.0-15-generic-nfs

Note that with this configuration, it is assumed that the NFS host and mount information will be supplied by the DHCP server (i.e. NFSROOT=auto). Otherwise, consult initramfs.conf(5) for alternative settings.

Running the following variant of the mkinitramfs(8) commandline keeps the temporary directory used to compile the initarmfs image in the present working directory:

$ TMPDIR=$WORKDIR mkinitramfs -d initramfs-tools-nfs -o initrd.img-`uname -r`-nfs `uname -r` -k
Working files in /home/lumumba/initrd_stuff/mkinitramfs_cCThet and overlay in /home/lumumba/initrd_stuff/mkinitramfs-OL_B75XMT

$ ls -CF
initramfs-tools-nfs/              mkinitramfs_cCThet/
initrd.img-3.11.0-15-generic-nfs  mkinitramfs-OL_B75XMT

$ ls -CF mkinitramfs_cCThet/
bin/  conf/  etc/  init*  lib/  lib64/  run/  sbin/  scripts/

Consult mkinitramfs(8) for more. See Example config for diskless clients with QEMU nodes for information on configuring diskless clients. Also check out initramfs-tools(8) for an introduction to writing scripts for mkinitramfs(8).

Futher Reading

  • This link covers several issues involved in building a custom and usable initramfs.


  • Documentation/filesystems/ramfs-rootfs-initramfs.txt
  • Documentation/early-userspace/README.txt
  • Documentation/early-userspace/buffer-format.txt

1. pivot_root swaps the mount points for the root filesystem with some other mounted filesystem [go back]

2. At least with Linux 3.11 [go back]

3. Consult this page for instructions on obtaining and installing the QEMU machine emulator [go back]