Search by Tags

Initramfs and tmpfs

 

Article updated at 29 Oct 2021
Compare with Revision




Initramfs

Initial RAM file system, abbreviated as initramfs, allows the kernel to run user space applications from a RAM disk built-in into the kernel or passed by the boot loader. Introduced in Linux 2.6 series, initramfs is the successor of initrd. Now, Linux kernels contain a compressed “cpio” archive, which is extracted into memory file system (typically tmpfs) and used as a root filesystem. After extracting, the kernel checks to see if rootfs contains a file "init", and if so it executes it as PID 1. If found, this init process is responsible for bringing the system the rest of the way up, including locating and mounting the real root device (if any). If rootfs does not contain an init program after the embedded cpio archive is extracted into it, the kernel will fall through and try to locate and mount a root partition on its own, and then exec some variant of /sbin/init out of that (Rob Landley, 2005). More information on initramfs can be found at Linux Kernel Documentation.

Traditionally, initramfs is meant to be a small root filesystem only capable of finding and hand over to the main rootfs. The traditional use case is supported by OpenEmbeddeds INITRAMFS_IMAGE variable. It allows to specify an image to be used as initramfs while the main rootfs is still built. There is a image recipe for this purpose, named core_image_minimal_initramfs (please check the recipe and the init script for more details). So if you specify INITRAMFS_IMAGE = "core-image-minimal-initramfs" in a configuration file, then OE will build two rootfs, the first with an init script that mounts the second. Additionally, the variable INITRAMFS_IMAGE_BUNDLE also makes sure that the initramfs gets built into the kernel too.

However, for embedded systems initramfs is often used as the main rootfs on its own. The main difference is that the traditional use case has an init script that mounts the final image, whereas for the embedded case the init script starts the application(s) that the device should run on its own and does not hand over to another rootfs. The rest of the article will focus on this embedded scenario.

Building a minimal initramfs root filesystem

To configure the build environment please refer to the article OpenEmbedded (core).

Building the minimal initramfs root filesystem

A minimal initramfs root filesystem will be generated using OpenEmbedded and then built into the kernel.

First, build the image recipe named initramfs-debug-image.bb which resides inside meta-openembedded layer. Remember to configure the target machine in the local.conf file.

cd ~/oe-core/build bitbake initramfs-debug-image

Building this image results in a cpio compressed root filesystem named (the following is the example initramfs for the Colibri VF61, the name will change for other machines and image versions):

Angstrom-initramfs-debug-image-glibc-ipk-v2015.12-colibri-vf.rootfs.cpio.gz

Located in the following directory:

~/oe-core/deploy/images/<machine>/ 

Building an initramfs root filesystem from any image recipe

By adding the following to your local.conf file OE will provide the resulting rootfs also as in a cpio.gz file which then can be used to create the initramfs.

IMAGE_FSTYPES_append = " cpio.gz"

Then create the image by

bitbake console-tdx-image

Deploying the initramfs

There are two possible ways to deploy the initramfs: into the kernel or as an external file. While having the initramfs into the kernel is simple, having an external initramfs can be useful do to the flexibility of being able to substitute it without recompiling the kernel itself.

If there is a internal and external initramfs, the external initramfs will extracted last and hence will overwrites files from the internal initramfs. That can be used to add or change functionality of a built-in initramfs.

Built-in initramfs

It is possible to compile the initramfs into the kernel.

Compiling initramfs into the kernel

For details about configuring the environment for building kernel from source, please refer to the article Build U-Boot and Linux Kernel from Source Code.

Go into the kernel configuration menu:

make colibfi_<machine>_defconfig
make menuconfig  

Go into general setup and enable the option:

General setup -> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support

And specify "Initramfs source file(s)" with the path to the cpio file generated above.

Now build the new kernel:

make -j8 zImage

The image will be at ~/arch/arm/boot/zImage .

Flashing the new kernel

Download a recent binary image for the Colibri module you are using and create an SD card as described: Colibri VF, Colibri iMX6.

Replace the default kernel image (zImage or uImage) from the SD card generated above (example below for the VF61):

cp ~/arch/arm/boot/zImage /media/path/to/sdcard/colibri_vf/zImage

To flash the image to the module, follow the steps from here.

Deploying initramfs external to the kernel

The file generated by OpenEmbedded is in the format .cpio.gz. The first step is to wrap it so U-Boot is able to use the initramfs:

mkimage -A arm -O linux -T ramdisk -n "Initial Ram Disk" -d Angstrom-initramfs-debug-image-glibc-ipk-v2015.12-colibri-imx6.rootfs.cpio.gz initramfs.img

Copy the image to an SD card, insert it into the carrier board, power-on and stop U-Boot. Then load the image to the RAM and write it to the module's non-volatile memory:

Colibri VF61

mmc rescan fatload mmc 0:1 ${ramdisk_addr_r} initramfs.img ubi part ubi ubi write ${ramdisk_addr_r} rootfs ${filesize} setenv initramfs_size ${filesize} saveenv

Colibri i.MX6

mmc rescan load mmc 1:1 ${ramdisk_addr_r} initramfs.img fatwrite mmc 0:1 ${ramdisk_addr_r} initramfs.img ${filesize}

Setting U-Boot environment

Some environment variables from U-Boot need to be configured before the module boot to the initramfs image at startup:

Colibri VF61

setenv boot_initfs "run setup; setenv bootargs ${defargs} ${ubiargs} ${setupargs} ${vidargs}; echo Booting from NAND...; ubi part ubi && ubi read ${kernel_addr_r} kernel && ubi read ${fdt_addr_r} dtb && run fdt_fixup && ubi read ${ramdisk_addr_r} rootfs ${initramfs_size} && bootz ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}" setenv bootcmd "run boot_initfs; run ubiboot; run sdboot; run nfsboot" saveenv

Colibri i.MX6

setenv boot_initfs "run load_initramfs_files && bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}" setenv load_initramfs_files "load mmc 0:1 ${fdt_addr_r} ${fdt_file}; load mmc 0:1 ${ramdisk_addr_r} initramfs.img; load mmc 0:1 ${kernel_addr_r} ${boot_file}" setenv bootcmd "run boot_initfs; echo; echo initramfs_failed; run emmcboot ; echo ; echo emmcboot failed ; run nfsboot ; echo ; echo nfsboot failed ; usb start ;setenv stdout serial,vga ; setenv stdin serial,usbkbd" saveenv

Testing the image

Because there is a minimal root filesystem, it is possible to see that the system boots much faster than a default image. The image runs in RAM memory so any data, file or directory is lost on reboot.

The minimal root filesystem has the following structure:

/ # ls bin dev etc init lib proc run sbin sys usr var

It is also possible to bring up a network interface and ping a gateway:

/ # ifconfig eth0 up 192.168.10.45 [ 517.359566] fec 400d1000.ethernet eth0: Freescale FEC PHY driver [Micrel KSZ8041] (mii_bus:phy_addr=400d1000.etherne:00, irq=­1) [ 517.394411] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready / # [ 519.360008] fec 400d1000.ethernet eth0: Link is Up ­ 100Mbps/Full ­ flow control rx/tx [ 519.391570] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

/ # ifconfig eth0 Link encap:Ethernet HWaddr 00:14:2D:49:79:2C
inet addr:192.168.10.45 Bcast:192.168.10.255 Mask:255.255.255.0 inet6 addr: fe80::214:2dff:fe49:792c/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:7 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:578 (578.0 B)

/ # ping 192.168.10.1 / # PING 192.168.10.1 (192.168.10.1): 56 data bytes 64 bytes from 192.168.10.1: seq=0 ttl=64 time=1.359 ms 64 bytes from 192.168.10.1: seq=1 ttl=64 time=0.812 ms 64 bytes from 192.168.10.1: seq=2 ttl=64 time=0.438 ms

Tmpfs

Instead of having a whole root filesystem mounted in RAM, it is also possible to create a tmpfs, dedicating only a section of RAM for data storage. A primary use of tmpfs is application caching directories or work areas. The standard images provided by Toradex make use of it - for instance, /tmp is a tmpfs directory.

Creating a tmpfs directory

Have the module flashed with a default image (no initramfs image), and create a folder to use as a mount point for the tmpfs:

mkdir /mnt/ramdisk

Then use the mount command to create the tmpfs:

mount -t tmpfs -o size=64m tmpfs /mnt/ramdisk

Now it is possible to write data to this directory. Note that all data written to the tmpfs directory will be lost on reboot.

Using fstab, the directory can persist over reboots. Just add the following entry to the /etc/fstab file:

tmpfs /mnt/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=64M 0 0