A Linux boot ramdisk, strictly speaking, an initial RAM filesystem or initramfs, is a compressed cpio
archive. cpio
is an old Unix archive format, similar to TAR and ZIP but easier to decode and so requiring less code in the kernel. You need to configure your kernel with CONFIG_BLK_DEV_INITRD
to support initramfs
.
In fact, there are three different ways to create a boot ramdisk: as a standalone cpio
archive, as a cpio
archive embedded in the kernel image, and as a device table which the kernel build system processes as part of the build. The first option gives the most flexibility because we can mix and match kernels and ramdisks to our hearts content. However, it means that you have two files to deal with instead of one and not all bootloaders have the facility to load a separate ramdisk. I will show you how to build one into the kernel later.
The following sequence of instructions creates the archive, compresses it and adds a U-Boot header ready for loading onto the target:
$ cd ~/rootfs $ find . | cpio -H newc -ov --owner root:root > ../initramfs.cpio $ cd .. $ gzip initramfs.cpio $ mkimage -A arm -O linux -T ramdisk -d initramfs.cpio.gz uRamdisk
Note that we ran cpio
with the option --owner root:root
. This is a quick fix for the file ownership problem mentioned earlier, making everything in the cpio
file UID and GID 0.
The final size of the uRamdisk
file is ~ 2.9 MiB, with no kernel modules. Add to that 4.4 MiB for the kernel zImage
file, and 440 KiB for U-Boot and this gives a total of 7.7 MiB of storage needed to boot this board. We are a little way off the 1.44 MiB floppy that started it all off. If size was a real problem, you could use one of these options:
The simplest thing we can do is to run a shell on the console so that we can interact with the device. We can do that by adding rdinit=/bin/sh
to the kernel command line. Now, you can boot the device.
QEMU has the option -initrd
to load initframfs
into memory, so the full command is now as follows:
$ cd ~/rootfs $ QEMU_AUDIO_DRV=none \ qemu-system-arm -m 256M -nographic -M vexpress-a9 -kernel zImage -append "console=ttyAMA0 rdinit=/bin/sh" -dtb vexpress-v2p-ca9.dtb -initrd initramfs.cpio.gz
To boot the BeagleBone Black, boot to the U-Boot prompt and enter these commands:
fatload mmc 0:1 0x80200000 zImage fatload mmc 0:1 0x80f00000 am335x-boneblack.dtb fatload mmc 0:1 0x81000000 uRamdisk setenv bootargs console=ttyO0,115200 rdinit=/bin/sh bootz 0x80200000 0x81000000 0x80f00000
If all goes well, you will get a root shell prompt on the console.
Note that the ps
command doesn't work: that is because the proc
filesystem has not been mounted yet. Try mounting it and run ps
again.
A refinement to this setup is to write a shell script that contains things that need to be done at boot-up and give that as the parameter to rdinit=
. The script would look like the following snippet:
#!/bin/sh /bin/mount -t proc proc /proc /bin/sh
Using a shell as init
in this way is very handy for quick hacks, for example, when you want to rescue a system with a broken init
program. However, in most cases, you would use an init
program, which we will cover further down.
In some cases, it is preferable to build the ramdisk into the kernel image, for example, if the bootloader cannot handle a ramdisk file. To do this, change the kernel configuration and set CONFIG_INITRAMFS_SOURCE
to the full path of the cpio
archive you created earlier. If you are using menuconfig
, it is in General setup | Initramfs source file(s). Note that it has to be the uncompressed cpio
file ending in .cpio
; not the gzipped version. Then, build the kernel. You should see that it is larger than before.
Booting is the same as before, except that there is no ramdisk file. For QEMU, the command is like this:
$ cd ~/rootfs $ QEMU_AUDIO_DRV=none \ qemu-system-arm -m 256M -nographic -M vexpress-a9 -kernel zImage -append "console=ttyAMA0 rdinit=/bin/sh" -dtb vexpress-v2p-ca9.dtb
For the BeagleBone Black, enter these commands into U-Boot:
fatload mmc 0:1 0x80200000 zImage fatload mmc 0:1 0x80f00000 am335x-boneblack.dtb setenv bootargs console=ttyO0,115200 rdinit=/bin/sh bootz 0x80200000 – 0x80f00000
Of course, you must remember to rebuild the kernel each time you change the contents of the ramdisk and regenerate the .cpio
file.
An interesting way to build the ramdisk into the kernel image is by using a device table to generate a cpio
archive. A device table
is a text file which lists the files, directories, device nodes, and links that go into the archive. The overwhelming advantage is that you can create entries in the cpio
file that are owned by root, or any other UID, without having root privileges yourself. You can even create device nodes. All this is possible because the archive is just a data file. It is only when it is expanded by Linux at boot time that real files and directories get created, using the attributes you have specified.
Here is a device table for our simple rootfs
, but missing most of the symbolic links to busybox
to make it manageable:
dir /proc 0755 0 0 dir /sys 0755 0 0 dir /dev 0755 0 0 nod /dev/console 0600 0 0 c 5 1 nod /dev/null 0666 0 0 c 1 3 nod /dev/ttyO0 0600 0 0 c 252 0 dir /bin 0755 0 0 file /bin/busybox /home/chris/rootfs/bin/busybox 0755 0 0 slink /bin/sh /bin/busybox 0777 0 0 dir /lib 0755 0 0 file /lib/ld-2.19.so /home/chris/rootfs/lib/ld-2.19.so 0755 0 0 slink /lib/ld-linux.so.3 /lib/ld-2.19.so 0777 0 0 file /lib/libc-2.19.so /home/chris/rootfs/lib/libc-2.19.so 0755 0 0 slink /lib/libc.so.6 /lib/libc-2.19.so 0777 0 0 file /lib/libm-2.19.so /home/chris/rootfs/lib/libm-2.19.so 0755 0 0 slink /lib/libm.so.6 /lib/libm-2.19.so 0777 0 0
dir <name> <mode> <uid> <gid>
file <name> <location> <mode> <uid> <gid>
nod <name> <mode> <uid> <gid> <dev_type> <maj> <min>
slink <name> <target> <mode> <uid> <gid>
The kernel provides a tool that reads this file and creates a cpio
archive. The source is in usr/gen_init_cpio.c
. There is a handy script in scripts/gen_initramfs_list.sh
that creates a device table from a given directory, which saves a lot of typing.
To complete, the task, you need to set CONFIG_INITRAMFS_SOURCE
to point to the device table file and then build the kernel. Everything else is the same as before.
There is an older format for a Linux ramdisk, known as initrd
. It was the only format available before Linux 2.6 and is still needed if you are using the mmu-less variant of Linux, uCLinux. It is pretty obscure and I will not cover it here. There is more information in the kernel source, in Documentation/initrd.txt
.