In the previous two articles, we covered the advantages of initramfs and the various ways to package a root filesystem into initramfs. This article gives some tips on creating root filesystems that take advantage of initramfs.
Creating a root filesystem for Linux is a bigger topic than one article can hope to cover, and this is obviously not the only way to do it. (If you've never created a root filesystem for Linux before, there are several existing documents to help you get started.) An initramfs is just a small self-contained root filesystem for Linux, often just a temporary one that hands off control after performing a few specific tasks.
The two main ways to create small filesystems are to start from scratch and add just what you need, or to start with a large working system and trim it down. This article picks a bit from both camps.
Last time we packaged a statically linked hello world program. Most real programs are dynamically linked, and won't run without their shared libraries. The ldd command can list a program's shared libraries, but since shared libraries can require other shared libraries, identifying all needed libraries can be time consuming.
If you can get a large system working, you can use the following shell script to copy all the executables listed on the command line, and all the shared libraries they link to, into a new directory. Then you can chroot into that directory to make sure they run.[1]
#!/bin/sh function mkchroot { [ $# -lt 2 ] && return dest=$1 shift for i in "$@" do # Get an absolute path for the file [ "${i:0:1}" == "/" ] || i=$(which $i) # Skip files that already exist at target. [ -f "$dest/$i" ] && continue if [ -e "$i" ] then # Create destination path d=`echo "$i" | grep -o '.*/'` && mkdir -p "$dest/$d" && # Copy file cat "$i" > "$dest/$i" && chmod +x "$dest/$i" else echo "Not found: $i" fi # Recursively copy shared libraries' shared libraries. mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') done } mkchroot "$@"
Its first argument is the new directory, and the rest of its arguments are executables to copy. You can try it out like so:
mkchroot subdir /bin/sh /bin/ls sudo chroot subdir /bin/sh ls -l exit
In addition to executables to run, even a minimal root filesystem needs device nodes. The /dev directory contains special files that allow programs to talk to the hardware.[2]
The mdev program in busybox 1.1.2 uses the sysfs filesystem in the 2.6 kernel to autodetect the available hardware, and create the appropriate device nodes. Like a very small and simple version of "udev", it reads from /sys and writes to /dev, and you can use it from your init scripts like this:
mkdir /sys /dev mount -t sysfs /sys /sys mount -t tmpfs /dev /dev # optional step mdev -s
If you'd like hotplug support, you can tell the kernel to run mdev to create or delete a device node very time it receives a hotplug event:
echo /sbin/mdev > /proc/sys/kernel/hotplug
By default, mdev creates each device node owned by root, with permissions 660. If you'd like to specify different permissions, you can create an optional /etc/mdev.conf file containing lines like this:
console 0:0 777 tty.* 0:0 660 hda[0-3] 0:3 644
Each line of mdev.conf starts with a regular expression specifying which device nodes to match, followed by the numeric uid:gid the device(s) should belong to, and then octal file permissions.
A common use of initramfs is to find and mount another root filesystem. Since rootfs can't be unmounted, the way to switch to a different root filesystem is with switch_root command. This command is available in current versions of busybox, in several distributions' boot utility packages, and as the "run-init" command the klibc package on kernel.org.
What switch_root does is delete all the files out of rootfs (to free up the memory) and then chroot into a new filesystem and exec a new init process out of the new filesystem.
The following shell script fragment demonstrates how to use switch_root:
# First, find and mount the new filesystem. mkdir /newroot mount /dev/whatever /newroot # Unmount everything else you've attached to rootfs. (Moving the filesystems # into newroot is something useful to do with them.) mount --move /sys /newroot/sys mount --move /proc /newroot/proc mount --move /dev /newroot/dev # Now switch to the new filesystem, and run /sbin/init out of it. Don't # forget the "exec" here, because you want the new init program to inherit # PID 1. exec switch_root /newroot /sbin/init
The kernel command line option "rdinit" tells the kernel to run a program other than /init out of initramfs. For example, "rdinit=/bin/sh" runs a command shell if you have one in there. If your initramfs isn't doing what you expect, try running a command shell to see what's up.
Footnote 1: This technique is just a start. It won't find required configuration files, paths and environment variables, device nodes, required empty directories like /tmp, and it won't find libraries accessed via dlopen() (such as glibc's libnss files). But BusyBox and uClibc don't have many external dependencies, and you can debug by comparing the larger working system to see what else is missing. If all else fails, the "strace" utility can give you an idea what's wrong. But in general, Busybox and uClibc have a minimum of external dependencies, and shouldn't give you too many surprises.
A truly minimal system can often do without seemingly vital files like /etc/passwd. If you never need any users other than root, and never have look up a user's name or password, it just doesn't come up. That sort of thing can go in the final filesystem.
Footnote 2: The 2.6.16 kernel will automatically open a default console for init, using the "console=" kernel command line option, even if the /dev directory in initramfs hasn't got the requested device node.