Search:  
Gentoo Wiki

Initramfs

This article is part of the HOWTO series.
Installation Kernel & Hardware Networks Portage Software System X Server Gaming Non-x86 Emulators Misc
This article is part of the Security series.

This article is still a Stub. You can help Gentoo-Wiki by expanding it.

Contents

Introduction

initramfs (or early userspace) is a root filesystem loaded by the kernel at an early stage of the boot process. It allows the user to do different things, that, otherwise would require to hack in the kernel itself.

Kernel configuration

genkernel

The easiest way to get an initramfs image is to use genkernel.

Note: Make sure /usr/src/linux is a symlink to the latest version for the kernel source.

If you have a running, manually configured kernel then genkernel can use the configuration from this kernel. Such a working configuration should be saved in /etc/kernels/kernel-config-x86-2.6.18-gentoo-r6 (for instance). Copy this config file as /usr/src/linux/.config and then run:

genkernel --no-clean --no-splash --install all 

Otherwise, if you don't have a configuration and want to generate one from scratch:

genkernel --mrproper --no-splash --install all

This will run "make mrproper" (cleaning all objects and existing configuration), and compile the kernel and modules from scratch.

Note: It is recommended to suppress bootsplash and Fbsplash as these can cause conflicts in the initramfs

Both of the above commands will install the resulting kernel, modules and initramfs in /boot.

Consult initramfs content

The genkernel initramfs file, initramfs-2.6.18-gentoo-r6, is 5 cpio archives concatenated and the result gzipped. You can use this method based on this tech tip.

Extract initramfs archive into the current directory:

zcat initramfs-2.6.18-gentoo-r6 | (while true; do \
  cpio -i -d -H newc --no-absolute-filenames || exit; done)

Booting a kernel made by genkernel

Here is a model GRUB section, note that it assumes a /boot partition on /dev/hda1 and a root partition on /dev/hda3:

title genkernel 2.6.18-r6
 root (hd0,0)
 kernel /kernel-genkernel-x86-2.6.18-gentoo-r6 real_root=/dev/hda3
 initrd /initramfs-genkernel-x86-2.6.18-gentoo-r6

The kernel command line does not need either root=/dev/ram0 (and ramdisk doesn't need to be enabled in the kernel configuration) or init=/linuxrc. The initramfs system doesn't use backing store, like /dev/ram0, and will look for /init to run after setting up the root fs.

There are a number of other kernel command line parameters which can be passed to the /init script, one of the most useful is "debug" which, at the end of the setup part will open a shell to allow you to check things.

netconsole is another useful debugging aid which will send all the boot messages to another machine where they can be saved for a post mortem.

A guided tour around the init script

The init script brings in other scripts

  1. /etc/initrd.defaults: the basis for this file is /usr/share/genkernel/<arch>/initrd.defaults if <arch> contains one or /usr/share/genkernel/generic/initrd.defaults. At present this "generic" one is the only one.
    It contains the PATH environment variable to be used when running /init, another variable to make udev the default device manager, a number of variables to use colour in the script output, KV_2_6_OR_GREATER, which is set to "yes" for 2.6.x kernels, HWOPTS and MY_HWOPTS
  2. /etc/initrd.scripts: the basis for this file is /usr/share/genkernel/<arch>/initrd.scripts if <arch> contains one or /usr/share/genkernel/generic/initrd.scripts. At present this "generic" one is the only one.
    This is a collection of functions to be used by the init script. It starts by bringing in /etc/initrd.defaults (again!).
  3. /etc/initrd.splash: this file comes from /usr/share/splashutils/initrd.splash which belongs to media-gfx/splashutils providing the framebuffer splash utilities. If you run genkernel with --no-splash there will be no /etc/initrd.splash.

/etc/initrd.defaults contains two lists of module classes, the full one in HWOPTS and MY_HWOPTS which is the set which will be used by init's modules_load.

The full list of module classes is:

usb firewire keymap cache evms2 sata lvm2 dmraid slowusb fs

and the set which will be used by default is

usb firewire sata dmraid fs net

keymap is to allow the user to select a keymap and will be added to MY_HWOPTS if genkernel is run with --do-keymap-auto.

The list of real modules will be found in /usr/share/genkernel/x86/modules_load (change x86 to suit your arch).

Interesting functions in initrd.scripts are

parse_opt 
which chops the key from the argument, e.g. "real_root=/dev/hda3" becomes "/dev/hda3"
modules_load 
which loads the list of modules passed as arguments
modules_scan 
called by modules_load to check for modules to load, skipping any marked as "no-load"
findcdmount 
of interest to LiveCD/LiveDVD creators
cache_cd_contents 
again for LiveCD/LiveDVD creators
mount_sysfs 
on 2.6 kernels, mount /sys. If this fails then don't use udev
union_insert_dir 
for unionfs
findnfsmount 
if you have root fs on NFS
check_loop 
for LiveCD/LiveDVD creators
run_shell 
runs /bin/ash
runUdev 
starts udev making sure everything is set up for it first, runs a loop checking to see if the queue of udev events is empty, leaves the loop after 30 iterations or when the queue is empty and then kills udev before exiting
runmdev 
starts mdev, mdev is a busybox udev replacement
start_dev_mgr 
starts mdev if possible otherwise devfsd
bootstrapCD 
for LiveCD/LiveDVD creators
cmdline_hwopts 
check the kernel command line for doscsi/noscsi type arguments: add the module class xxxx in any doxxxx arguments to MY_HWOPTS or remove it for a noxxxx argument.
load_modules 
loads the module categories in MY_HWOPTS if there are any modules to load
setup_keymap 
allows the user to select a keymap if genkernel has been run with --do-keymap-auto
chooseKeymap 
helper function for setup_keymap
startVolumes 
to do with dmraid, lvm2 and evms; genkernel needs to be run with --dmraid, --lvm2 or --evms2
startLUKS 
if you like your root filesystem encrypted
setup_md_device 
for RAID1/mirrored disk setup

After bringing in /etc/initrd.defaults and /etc/initrd.scripts, the next interesting bit is mounting /proc and remounting root read-write. It seems odd that the root fs on initramfs should be mounted readonly and a surprise that initramfs can be readonly — perhaps it will all become clear later on.

The next important section is skipped if you have init=/linuxrc on the kernel command line — it isn't needed and this is a good reason for not doing it.

The first part (if the script is running as /init) is the installation of busybox. busybox can be configured (see later) to create all the symlinks to its applets by calling /bin/busybox --install -s.

The next bit unlinks /linuxrc and then, if lvm is included, two symlinks to /bin/lvm are created.

Now the genkernel/initramfs options on the kernel command line are parsed: there are a lot more than can be found on the genkernel manpage as they serve the LiveDVD/LiveCD as well;

real_root 
the root partition if initramfs is not being used in embedded mode
subdir 
the subdirectory of a LiveDVD/LiveCD to enter to boot it after it has been mounted
real_init 
options to be passed to /sbin/init on the root partition
init_opts 
options to this (/init) script
cdroot 
loop 
LiveDVD/LiveCD loop device
looptype 
LiveDVD/LiveCD loop device options
devfs 
deprecated?
udev 
use udev, does not do anything if the script is running as /init
unionfs 
dolvm2 
set a flag to use LVM2
dodmraid 
set a flag to use DMRAID
doevms2 
set a flag to use EVMS2
debug 
causes modules_scan in initrd.defaults to display the module class it is scanning for and runs a shell when all the initialization is complete.
scandelay 
sleep for a few seconds (default 10) to allow time for USB devices to initialize
doload 
module classes to load
nodetect 
set a flag to skip loading module classes not included in a doload argument
noload 
module classes not to load
CONSOLE 
redirect output to a specific tty
lvmraid 
a list of RAID devices (/dev/md)
part 
/dev/md partitions
ip 
for use with a root fs on NFS
nfsroot 

The Firewire specific detect_sbp2_devices is run.

cmdline_hwopts is run to collect and process any doscsi/noscsi type arguments.

The modules, If there are any (no /lib/modules means no modules to load), are loaded. If there was a nodetect argument, only the module classes in a doload argument will be loaded. If there is no doload argument, no modules will be loaded.

mount_sysfs is run to to mount /sys; sdelay is run to allow time for USB devices to initialize; start_dev_mgr is run to start mdev or devfs — by default it starts busybox's mdev; setup_md_device creates the md device nodes if they don't exist; startVolumes sets up dmraid, lvm2 or evms2 if they were selected; unionfs is set up;

Now, if debug was on the kernel command line, a shell, (/bin/ash, is started (rundebugshell).

When processing continues it does a Suspend2 resume if Suspend2 has been installed.

The next bit sets up the devices and tmpfs for a LiveDVD/LiveCD and boots it. It will try again after a pause for new devices to appear if it failed. If it fails again, it gives up.

setup_keymap is run next: if genkernel has been run with --do-keymap-auto, the user can select a keymap.

It now tries to find the root partition and mount it in a double while loop. The inside loop is to check the real_root argument which can be specified on the command line by:

real_root=LABEL=label real_root=UUID=uuid 
Note: to use either of these arguments, genkernel must have been run with the argument "--disklabel". With the "--disklabel" argument, genkernel will install blkid from e2fsprogs in the initramfs image and blkid will be run to find the partition with the given LABEL or UUID.
real_root=shell 
puts the user in a shell: on leaving the shell the search is continued but real_root has no value and will behave as in the next entry
real_root= 
the user is asked to specify a boot device or "shell": a root device will be checked by the other entries here, "shell" will go to the preceding entry
real_root=/dev/hdx real_root=/dev/sdx 
if a test shows that the device given is a block device it will be accepted and processing will break out of the inner loop and start the next section
real_root=/dev/nfs 
processing will break out of the inner loop and start the next section
crypt_root=/dev/hdx crypt_root=/dev/sdx 
Note: to use either of these arguments, genkernel must have been run with the "--luks" argument. With the --luks" argument, genkernel will install /sbin/cryptsetup and it will be used to open the LUKS device as /dev/mapper/root. If it fails or /sbin/cryptsetup is not installed, it will go through the loop again ending up at the empty real_root entry. Before version 3.4.4 this argument was "real_root=luks:/dev/hdx" or "real_root=luks:/dev/sdx".

Processing will only get to this step if a root partition has been found or it has been given as /dev/nfs.

If it is a LiveDVD/LiveCD then it will break out of the outside loop.

Otherwise, if real_root=/dev/nfs, it will run findnfsmount to get the rootserver and rootpath from the kernel dmesg and then mount the NFS root. It can cope with mounting a CD image over NFS too.

If it is a local block device, it mounts it on $NEW_ROOT which is set to /newroot in initrd.defaults. If this fails it's back round the loop again with an empty real_root which will end up asking for a boot device or offering a shell.

Next, if the root fs is a DVD/CD, it is mounted and its contents cached.

There is another section which is only executed if the script is run using init=/linuxrc on the kernel command line and it is clearly legacy initrd, not initramfs.

If the script has been called as /init then the mounted root fs is checked to see that it has a /dev/console and /dev/tty1 — if absent they are created; /sys and /proc are unmounted with an error message if either one fails and then a list of all the files in the initramfs is created except for busybox. busybox is then used to chroot into the mounted root fs and exec /sbin/init with the arguments (if any) passed on the kernel command line in real_init=.

If the exec fails, the user is put into a shell.

Modifying the init script

(Work in progress)

To use a modified init script you should call genkernel as:

genkernel --linuxrc=/path/to/new/init --no-mrproper --no-clean --install \
--kernel-config=/boot/config-2.6.19-gentoo-r2 \
--kerneldir=/usr/src/linux-2.6.19-gentoo-r2 --no-splash \
initrd

This can be shortened by changing some of the settings in /etc/genkernel.conf

MENUCONFIG="no"
CLEAN="no"
MRPROPER="no"
BOOTSPLASH="no"

then the command becomes

genkernel --linuxrc=/path/to/new/init --install \
--kernname=init0 \
--kernel-config=/boot/config-2.6.19-gentoo-r2 \
--kerneldir=/usr/src/linux-2.6.19-gentoo-r2 initrd

The "--kernname=" argument has been added so that the unmodified initramfs (initramfs-genkernel-x86-2.6.19-gentoo-r2) will not be overwritten by this testing version. This version will be called initramfs-init0-x86-2.6.19-gentoo-r2.

Note that "/path/to/new/init" must be an absolute pathname, not a relative one.

To start off with, a very simple init script which does the minimum of initialization and then starts ash — the busybox shell — for you to look around and try things out. This is busybox-1.1.3+gentoo-x86 available in genkernel's initramfs by default.

File: init.0
#!/bin/sh
# Copyright  Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# Minimal init script: init.0

. /etc/initrd.defaults
. /etc/initrd.scripts

# Clean input/output
exec >${CONSOLE} <${CONSOLE} 2>&1

mount -t proc proc /proc >/dev/null 2>&1

# Start a shell to look around: /bin/ash is an installed symlink to busybox
/bin/ash

To give you an idea of what you can do, here is ash's announcement

Code: ash commands
Built-in commands:
-------------------
        . : alias bg break cd chdir continue eval exec exit export false
        fg hash help jobs kill let local pwd read readonly return set
        shift times trap true type ulimit umask unalias unset wait

and these are the functions (applets) built in to busybox

Code: busybox applets
Currently defined functions:
        [, [[, ash, basename, bb, busybox, cat, chgrp, chmod, chown, chroot,
        chvt, clear, cp, cut, date, dd, df, dirname, dmesg, du, dumpkmap,
        echo, env, false, fgrep, find, free, freeramdisk, grep, gunzip,
        gzip, halt, head, id, ifconfig, init, kill, killall, linuxrc,
        ln, loadfont, loadkmap, losetup, ls, lsmod, mdev, mdstart, mesg,
        mkdir, mknod, mktemp, modprobe, more, mount, mv, ping, pivot_root,
        poweroff, ps, pwd, rdate, readlink, reboot, reset, rm, rmdir,
        rmmod, sed, sh, sleep, sort, swapoff, swapon, sync, tail, tar,
        test, touch, true, tty, udhcpc, umount, uname, uniq, uptime, which,
        whoami, xargs, yes, zcat

This version of busybox has been replaced by a new stable version, 1.2.2.1, and there is 1.3.1 as well. While a lot can be done with busybox-1.1.3+gentoo, the applets have been selected to support genkernel's purpose which is to mount the root fs — whether it be RAID, mounted over NFS or encrypted — and it seems worthwhile using version 1.2.2.1 compiled statically against uClibc to allow a wider range of uses.

The genkernel scripts contain a lot of code which will probably not be used so it is worth winnowing initrd.defaults, initrd.scripts and, finally, init to make it easier to see what's going on and provide a skeleton on which to build other applications for initramfs.

This means that genkernel will no longer be used to create the image though parts of its cpio archive files will still come in handy: initramfs-base-layout.cpio.gz contains all the directories and subdirectories to be used and initramfs-aux.cpio.gz contains the lib/keymaps directory — its contents will be essential when choosing a keymap at runtime. The new Busybox executable will replace initramfs-busybox-1.1.3+gentoo.cpio.gz; initramfs-insmod-0.9.15-pre4.cpio.gz will not be needed as the Busybox "modprobe" and "insmod" from its "Linux Module Utilities" section will replace them; initramfs-blkid-1.38.cpio.gz will be replaced by the Busybox "findfs" applet from "Linux Ext2 FS Progs" and initramfs-modules-2.6.19-gentoo-r2.cpio.gz will be superceded by copying in the whole /lib/modules/2.6.19-gentoo-r2 tree.

The code in initrd.scripts which fits the purpose of the two example inits which follow will be copied into their text, only the PATH is required from initrd.defaults.

An init script to mount a root_fs by name, label or uuid

Here is a script to create an initramfs on the above model:

Code: Creating a root filesystem cpio archive
#!/bin/bash
ROOTFS=/tmp/root_fs
NEWROOT=newroot # the mount point for the root fs
MUSTBETHERE=temp # the ROOTFS directory must have this to be the true one
CPIO=/usr/share/genkernel/pkg/x86/cpio
UDHCPC_SCRIPT=/root/tim-rikers.udhcpc.script

unpack() {
    zcat $1 | cpio --extract --make-directories --absolute-filenames -H newc
}

if [ -z "$1" ]; then
    echo 'Usage: '$0' init-file'
    exit 1
fi

INIT=$1
if [ -z "$2" ]; then
    IMG=/root/initramfs-initbb-x86-2.6.18-gentoo-r6-dyn-bb1221
else
    IMG=/root/initramfs-$2-x86-2.6.18-gentoo-r6-dyn-bb1221
fi
echo The image will be called
echo "   $IMG"

if [ ! -d ${ROOTFS} ]; then
    echo No ${ROOTFS}, creating it
    mkdir ${ROOTFS}
else
    echo Cleaning ${ROOTFS}
    pushd ${ROOTFS}
    if [ -d ${MUSTBETHERE} ]; then # make sure this is the right place
	echo In $(pwd)...
	rm -rf *
	echo ...cleaned
	ls -l .
	popd
    else
# There wasn't a MUSTBETHERE so no cleaning was done
	echo "The directory ${MUSTBETHERE} doesn't exist, wrong directory?"
	exit 1
    fi
fi

pushd ${ROOTFS}

#    Create the root_fs base layout
mkdir bin dev etc ${NEWROOT} proc sys sbin temp usr usr/sbin usr/bin
ln -s ../lib lib64 # Incomprehensible magick?
cd dev
mknod -m 660 console c 5 1
mknod -m 666 null c 1 3
mknod -m 600 tty1 c 4 1
mkdir -p lib/keymaps
/bin/tar -C lib/keymaps xzf /usr/share/genkernel/generic/keymaps.tar.gz

#    Replace init with a custom one: make it executable
cp -pv ${INIT} init
chmod 755 init
#    Copy the busybox binary to bin in ROOTFS
bzcat /root/busybox-1.2.2.1-i686.bz2 > bin/busybox
#    Make sure busybox is executable
chmod +x bin/busybox
#    Create a hard link bin/sh to bin/busybox so init can be executed.
pushd bin
ln busybox sh
#    mount, ash, uname and cut are also needed.
ln busybox mount
ln busybox ash
ln busybox uname
ln busybox cut
#    add echo and [
ln busybox echo
ln busybox [
popd
#    Copy the udhcpc script to bin
cp -pv ${UDHCPC_SCRIPT} bin/udhcpc.script
#    Make it executable
chmod +x bin/udhcpc.script
#    Copy the whole /lib/modules/2.6.18-r6 tree into lib
mkdir lib/modules
pushd lib/modules
rsync -av /lib/modules/2.6.18-gentoo-r6 .
popd
#    Create the initramfs cpio archive 
#   (from Documentation/filesystems/ramfs-rootfs-initramfs.txt)
find . | cpio -o -H newc | gzip > ${IMG}
echo The image is
ls -l ${IMG}
popd

The full pathname of the init file to be used is the first argument and there is an optional second argument to replace the word between "initramfs" and "x86" in the initramfs image name. The settings for "ROOT_FS" and "UDHCPC_SCRIPT" can be changed or made into further arguments to the script.

The udhcpc script used above was posted to the Busybox bug tracker (http://bugs.busybox.net/view.php?id=548) by Tim Riker and Devin Bayer. genkernel's /usr/share/genkernel/generic/udhcpc.scripts is tuned for a single purpose: to set up a network connection to allow an NFS root filesystem to be mounted — Tim Riker's script sets up /etc/resolv.conf and routing as well as getting an IP for the network interface.

Here is an init script which will mount a root filesystem given by device name (real_root=/dev/hda3 for example), label (real_root=LABEL=2006.1-root) or uuid (real_root=UUID=7687080a-45b9-40c5-8b41-399e05236bce)

Code: An init script to mount a root_fs by name, label or uuid
#!/bin/sh
# Copyright  Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# Custom init script for busybox-1.2.2.1: init-mount-root_fs
# initrd.defaults and initrd.scripts removed with some of the contents
# copied in here.

# From initrd.defaults
NEW_ROOT="/newroot"
NEW_INIT="/sbin/init"
CONSOLE="/dev/console"

# From initrd.scripts
strlen() {
    if [ -z "$1" ]
    then
	echo "usage: strlen <variable_name>"
	die
    fi
    eval echo "\${#${1}}"
}

parse_opt() {
    case "$1" in
	*\=*)
	    local key_name="$(echo "$1" | cut -f1 -d=)"
	    local key_len=$(strlen key_name)
	    local value_start=$((key_len+2))
	    echo "$1" | cut -c ${value_start}-
	    ;;
    esac
}

mount_sysfs() {
    mount -t sysfs /sys /sys
    ret=$?
    if [ "$ret" -ne '0' ]
    then
        # sysfs mount failed .. udev wont work
	echo sysfs mount failed.
    fi
}

chooseKeymap() {
    echo "Loading keymaps"
    cat /lib/keymaps/keymapList
    read -t 10 -p '<< Load keymap (Enter for default): ' keymap
    if [ -e /lib/keymaps/${keymap}.map ]
    then
	echo "Loading the ''${keymap}'' keymap"
	loadkmap < /lib/keymaps/${keymap}.map
    elif [ "$keymap" = '' ]
    then
	echo
	echo "Keeping default keymap"
    else
	echo "Sorry, but keymap ''${keymap}'' is invalid!"
	chooseKeymap
    fi
}

quiet_kmsg() {
    echo '0' > /proc/sys/kernel/printk
}

verbose_kmsg() {
    echo '6' > /proc/sys/kernel/printk
}

# Clean input/output
exec >${CONSOLE} <${CONSOLE} 2>&1

mount -t proc proc /proc >/dev/null 2>&1

# Install symlinks to all the busybox applets
busybox --install -s

verbose_kmsg
mount_sysfs

# Load the modules (except sd_mod)
MOD_LIST="ahci sata_via ide-cd ide-disk via82cxxx"
MOD_LIST="${MOD_LIST} via-rhine 8139too 3c59x"
MOD_LIST="${MOD_LIST} reiserfs ext3 ext2 xfs jfs"
for m in ${MOD_LIST}  
do
    [ "$m" = "ide-disk" -o "$m" = "via82cxxx" ] && echo 'Loading '$m
    modprobe -vv $m
done

# Run BusyBox's mdev to create the devices
# echo "Listing of /dev before running mdev: "; cd /dev; ls -l; cd /
mdev -s
# echo "Listing of /dev after running mdev: "; cd /dev; ls -l; cd /

CMDLINE="$(cat /proc/cmdline)"
# Scan CMDLINE for a real_root= argument
REAL_ROOT=''
for x in ${CMDLINE}
do
	case "${x}" in
		real_root\=*)
			REAL_ROOT=`parse_opt "${x}"`
		;;
	esac
done

# The real_root argument can be a device name, a LABEL or a UUID
if [ -z "${REAL_ROOT}" ]; then
   echo "Root command line: ${CMDLINE}"
   echo "No real_root= argument given, starting ash"
   /bin/ash
   exit 0
fi

if [ "$(echo ${REAL_ROOT}|cut -c -5)" = "/dev/" ]; then
    echo ${REAL_ROOT} is a device name
# Make sure the device exists
    if [ ! -b ${REAL_ROOT} ]; then
	echo The block device ${REAL_ROOT} does not exist
	echo /proc/partitions is
	cat /proc/partitions
	retval=1
    else
	mount -r ${REAL_ROOT} ${NEW_ROOT}
	retval=$?
    fi
elif [ "$(echo ${REAL_ROOT}|cut -c -6)" = "LABEL=" ]; then
    echo ${REAL_ROOT} is a LABEL
    DEV=$(findfs ${REAL_ROOT})
    if [ -z "${DEV}" ]; then
	echo No partition with ${REAL_ROOT} has been found
	retval=1
    else
	mount -r ${DEV} ${NEW_ROOT}
	retval=$?
    fi
elif [ "$(echo ${REAL_ROOT}|cut -c -5)" = "UUID=" ]; then
    echo ${REAL_ROOT} is a UUID
    DEV=$(findfs ${REAL_ROOT})
    if [ -z "${DEV}" ]; then
	echo No partition with ${REAL_ROOT} has been found
	retval=1
    else
	mount -r ${DEV} ${NEW_ROOT}
	retval=$?
    fi
else
    echo ${REAL_ROOT} is unrecognized.
    retval=2
fi
if [ "${retval}" -gt '0' ]; then
    # Start a shell
    if [ "${retval}" -eq '1' ]; then
	echo "Mounting the root fs failed, starting ash"
    fi
    /bin/ash
    exit 0
fi
exec switch_root -c ${CONSOLE} ${NEW_ROOT} ${NEW_INIT}

echo "exec switch_root failed, debugging time again..."
/bin/ash

The list of modules is, of course, specific to the guinea-pig machine. Note that you can get the label, uuid and filesystem type using "blkid" from e2fsprogs.

An init script to set up an embedded system

This will use BusyBox's init and a modified version of the inittab in examples/inittab in the BusyBox source.

It has a network connection set up by udhcpc, a web server, a console and three ttys — you can switch between the console and the ttys using Ctrl-Alt-F1 to Ctrl-Alt-F4. It also uses the genkernel code to allow a keymap to be selected.

As everything revolves around inittab, it will be discussed first:

Code: inittab
# /etc/inittab init(8) configuration for BusyBox
#
# Copyright (C)  by Erik Andersen <andersen@codepoet.org>

# Boot-time system configuration/initialization script.
# This is run first except when booting in single-user mode.
#
::sysinit:/etc/init.d/rcS

# Start the web server:
::once:/usr/sbin/httpd -u www -h /www -c /etc/httpd.conf -r "Web Server Authentication"
# /bin/sh invocations on selected ttys
#
# Note below that we prefix the shell commands with a "-" to indicate to the
# shell that it is supposed to be a login shell.  Normally this is handled by
# login, but since we are bypassing login in this case, BusyBox lets you do
# this yourself...
#
# Start an "askfirst" shell on the console (whatever that may be)
::askfirst:-/bin/sh
# Start an "askfirst" shell on /dev/tty2-4
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh

# Stuff to do when restarting the init process
::restart:/sbin/init

# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

The file /etc/init.d/rcS is most of the code from the init in the preceding section under a different name:

Code: The system initialization script: /etc/init.d/rcS
#!/bin/sh
# Copyright  Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# System initialization script for busybox-1.4.1: /etc/init.d/rcS
# Based on genkernel's init script with the sourcing of initrd.defaults
# and initrd.scripts removed but with some of the contents copied in
# here.

# From initrd.defaults
CONSOLE="/dev/console"

# From initrd.scripts: for use in parsing options
strlen() {
    if [ -z "$1" ]
    then
	echo "usage: strlen <variable_name>"
	die
    fi
    eval echo "\${#${1}}"
}

parse_opt() {
    case "$1" in
	*\=*)
	    local key_name="$(echo "$1" | cut -f1 -d=)"
	    local key_len=$(strlen key_name)
	    local value_start=$((key_len+2))
	    echo "$1" | cut -c ${value_start}-
	    ;;
    esac
}

mount_sysfs() {
    mount -t sysfs sysfs /sys
    ret=$?
    if [ "$ret" -ne '0' ]
    then
        # sysfs mount failed .. udev wont work
	echo sysfs mount failed.
    fi
}

chooseKeymap() {
    echo "Loading keymaps"
    cat /lib/keymaps/keymapList
    read -t 10 -p '<< Load keymap (Enter for default): ' keymap
    if [ -e /lib/keymaps/${keymap}.map ]
    then
	echo "Loading the ''${keymap}'' keymap"
	loadkmap < /lib/keymaps/${keymap}.map
    elif [ "$keymap" = '' ]
    then
	echo
	echo "Keeping default keymap"
    else
	echo "Sorry, but keymap ''${keymap}'' is invalid!"
	chooseKeymap
    fi
}

quiet_kmsg() {
    echo '0' > /proc/sys/kernel/printk
}

verbose_kmsg() {
    echo '6' > /proc/sys/kernel/printk
}

# Clean input/output
exec >${CONSOLE} <${CONSOLE} 2>&1

mount -t proc proc /proc >/dev/null 2>&1

# Install symlinks to all the busybox applets
busybox --install -s

verbose_kmsg
mount_sysfs

# Load the modules
MOD_LIST="ahci sata_via ide-cd ide-disk via82cxxx"
MOD_LIST="${MOD_LIST} via-rhine 8139too 3c59x"
MOD_LIST="${MOD_LIST} reiserfs ext3 ext2 xfs jfs"
echo -n "Loading modules... "
for m in ${MOD_LIST}  
do
    echo -n "$m "
    modprobe $m >/dev/null 2>&1
done
echo "done."

# Run BusyBox's mdev to create the devices
mdev -s

# Change the permissions on /dev/null so httpd can use it
chmod o+rw /dev/null

# Create an empty /etc/group and then the httpd user's group
touch /etc/group
addgroup -g 65533 nogroup
# Create an empty /etc/passwd and then the httpd user
adduser -h /www -g "httpd user" -s /bin/false -G nogroup -D www
#    Create the web server admin directory and the CGI directory
mkdir /www/adm /www/cgi-bin
#    Copy in the example web page
cp /etc/httpd/emb-index.html www/index.html
#    Copy in the admin web page
cp /etc/httpd/emb-adm-index.html www/adm/index.html
#    Copy in the CGI script
cp /etc/httpd/emb-index.cgi www/cgi-bin/index.cgi
#    Copy in the httpd config file
cp /etc/httpd/emb-httpd.conf /etc/httpd.conf
# Make all owned by the httpd user
chown -R www:0 /www

# Set up lo
ifconfig lo 127.0.0.1 netmask 255.0.0.0

# Run udhcpc to set up networking on eth0
udhcpc -H embedded -i eth0 -s /bin/udhcpc.script

# Select a keymap
chooseKeymap

There are some additions here to the code in the preceding init: a user and group for httpd to run under has been created and a few, small, example web pages have been copied into the web server tree under /www. genkernel's initramfs-base-layout.cpio.gz has set the permissions on /dev/null to "0660" which doesn't allow httpd to use it; "other" has been given read-write permissions too.

The files used by httpd are:

Code: Server configuration file: /etc/httpd.conf
a:192.168.1.0/255.255.255.128
a:127.0.0.1
d:*
/cgi-bin:broke:Tuesday
/adm:admin:8.Aug-2006

The lines starting with "a" (also "A" or "allow") allow connections with the IP addresses given, lines starting with "d" (also "D" or "deny") deny connections. Parts of the server tree can be made inaccessible without a username and password: "/cgi-bin:broke:Tuesday" requires the username "broke" and password "Tuesday" to access any URL starting "/cgi-bin".

More information is in the comments at the head of networking/httpd.c in the BusyBox source.

The other files are:

Code: index.cgi
#!/bin/sh
# Derived from networking/httpd_index_cgi_example

# This CGI creates directory index.
# Put it into cgi-bin/index.cgi and chmod 0755.
#
# Problems:
# * Unsafe wrt weird filenames with <>"'& etc...
# * Not efficient: calls stat (program, not syscall) for each file
# * Probably requires bash
#
# If you want speed and safety, you need to code it in C

# Must start with '/'
test "$(echo ${QUERY_STRING} | cut -c -1)" = "/" || exit 1
# /../ is not allowed
test "${QUERY_STRING%/../*}" = "$QUERY_STRING" || exit 1
test "${QUERY_STRING%/..}" = "$QUERY_STRING" || exit 1

# Outta cgi-bin...
cd .. 2>/dev/null || exit 1
# Strip leading '/', go to target dir
cd "$(echo ${QUERY_STRING}|cut -c 2-)" 2>/dev/null || exit 1

f=`dirname "$QUERY_STRING"`
test "$f" = "/" && f=""

echo -e "HTTP/1.0 200 OK\r"
echo -e "Content-type: text/html\r"
echo -e "\r"
echo -e "<html><head><title>Index of $QUERY_STRING</title></head>\r"
echo -e "\x3cbody\x3e\x3ch1\x3eIndex of $QUERY_STRING\x3c/h1\x3e\x3cpre\x3e\r"
echo -e "<table width=100%>\r"
echo -e "<col><col><col width=0*>\r"
echo -e "<tr><th>Name<th align=right>Last modified<th align=right>Size\r"
echo -e "<tr><td><a href='$f/'>..</a><td><td>\r"

IFS='#'
for f in *; do
    # Guard against empty dirs...
    test -e "$f" && stat -c "%F#%s#%z" "$f" | {
	read type size cdt junk
	dir=''
	test "$type" = "directory" && dir='/'
	# no fractional seconds, prevent wrapping around space
	cdt=$(echo $cdt | sed 's/\.[0-9]\+$//; s/ /\ /')
	echo -e "<tr><td><a href=$f$dir>$f</a><td align=right>$cdt<td align=right>$size\r"
    }
done
# To workaround Wiki formatting, the brokets around pre in the following line have been
# replaced by escape sequences
echo -e "</table>\x3c/pre\x3e<hr></body></html>\r"
Code: /www/adm/index.html
<html>
<head>
<title>Admin Web page</title>
</head>
<body>
This web page can only be accessed with the username <i>admin</i> and
the admin password.
</body>
</html>
Code: /www/index.html
<html>
<head>
<title>Example Web page</title>
</head>
<body>
This web page is open to all without a username and password
</body>
</html>

The next step in inittab is to start httpd: this is done using "once" as httpd will spawn a child process and then stop. The examples/inittab in the BusyBox source shows how to use /sbin/getty on a tty and putting /sbin/getty on a serial line or a modem line.

The script to create an initramfs to mount a root filesystem described in the previous section has been modified to create this embedded system:

Code: Creating an embedded system cpio archive
#!/bin/bash
ROOTFS=/tmp/root_fs
MUSTBETHERE=temp # the ROOTFS directory must have this to be the true one
CPIO=/usr/share/genkernel/pkg/x86/cpio
HTTP_PAGE=/root/emb-index.html
HTTP_ADM_PAGE=/root/emb-adm-index.html
HTTP_CGI_SCRIPT=/root/emb-index.cgi
HTTPD_CONF=/root/emb-httpd.conf
UDHCPC_SCRIPT=/root/tim-rikers.udhcpc.script
INIT_SCRIPT=/root/emb-rcS
INITTAB=/root/emb-inittab
BUSYBOX_DEFAULT=/root/busybox-1.2.2.1-i686
INIT_TAG_DEFAULT=bb1221

echo 'Usage: '$0' [busybox-binary] [initramfs tag]'
echo '  busybox default binary : '${BUSYBOX_DEFAULT}
echo '  initramfs default tag: '${INIT_TAG_DEFAULT}

unpack() {
    zcat $1 | cpio --extract --make-directories --absolute-filenames -H newc
}

if [ -z "$1" ]; then
    BUSYBOX=${BUSYBOX_DEFAULT}
else
    if [ -r "$1" ]; then
	BUSYBOX=$1
    else
	echo "$1 cannot be read, exiting..."
	exit 1
    fi
fi

if [ -z "$2" ]; then
    IMG=/root/initramfs-initemb-x86-2.6.18-gentoo-r6-dyn-${INIT_LABEL_DEFAULT}
else
    IMG=/root/initramfs-initemb-x86-2.6.18-gentoo-r6-dyn-$2
fi
echo Using the busybox binary ${BUSYBOX}
echo The image will be called
echo "   $IMG"

if [ ! -d ${ROOTFS} ]; then
    echo No ${ROOTFS}, creating it
    mkdir ${ROOTFS}
else
    echo Cleaning ${ROOTFS}
    pushd ${ROOTFS}
    if [ -d ${MUSTBETHERE} ]; then # make sure this is the right place
	echo In $(pwd)...
	rm -rf *
	echo ...cleaned
	ls -l .
	popd
    else
# There wasn't a MUSTBETHERE so no cleaning was done
	echo "The directory ${MUSTBETHERE} doesn't exist, wrong directory?"
	exit 1
    fi
fi

pushd ${ROOTFS}

#    Create the root_fs base layout
mkdir bin dev etc ${NEWROOT} proc sys sbin temp usr usr/sbin usr/bin
ln -s ../lib lib64 # Incomprehensible magick?
cd dev
mknod -m 660 console c 5 1
mknod -m 666 null c 1 3
mknod -m 600 tty1 c 4 1
mkdir -p lib/keymaps
/bin/tar -C lib/keymaps xzf /usr/share/genkernel/generic/keymaps.tar.gz

#    Create a directory to store the httpd files for rcS to install
mkdir etc/httpd
#    Copy it all in
for h in ${HTTP_PAGE} ${HTTP_ADM_PAGE} ${HTTP_CGI_SCRIPT} ${HTTPD_CONF}
do
    cp -pv $h  etc/httpd/$(basename $h)
done

#    Copy the busybox binary to bin in ROOTFS
#    Check again that the BUSYBOX argument can be read
if [ ! -r "${BUSYBOX}" ]; then
    echo "${BUSYBOX} cannot be read, exiting..."
    exit 1
fi
cp -pv ${BUSYBOX} bin/busybox
#    Make sure busybox is owned by user root, group root
chown root:root bin/busybox
#    Make sure busybox is executable and suid
chmod 4755 bin/busybox
#    Create a hard link bin/sh to bin/busybox to allow rcS to execute
pushd bin
ln -s busybox sh
#    mount, ash, uname and cut are also needed.
ln -s busybox mount
ln -s busybox ash
ln -s busybox uname
ln -s busybox cut
#    add echo and [
ln -s busybox echo
ln -s busybox [
popd # Leave /bin, back to ${ROOTFS}
#    Replace /init with a symlink to bin/busybox
ln -s bin/busybox init
#    Make sbin/init another symlink to bin/busybox
pushd sbin
ln -s ../bin/busybox init
popd # Leave /sbin, back to ${ROOTFS}

#    Copy the udhcpc script to bin
cp -pv ${UDHCPC_SCRIPT} bin/udhcpc.script
#    Make it executable
chmod +x bin/udhcpc.script
#    Copy the whole /lib/modules/2.6.18-r6 tree into lib
mkdir lib/modules
rsync -av /lib/modules/2.6.18-gentoo-r6 lib/modules
#    Copy rcS to /etc/init.d/rcS and make it executable
[ -d etc/init.d ] || mkdir etc/init.d
cp -pv ${INIT_SCRIPT} etc/init.d/rcS
chmod +x etc/init.d/rcS
# Copy inittab
cp -pv ${INITTAB} etc/inittab

#    Create the initramfs cpio archive 
#   (from Documentation/filesystems/ramfs-rootfs-initramfs.txt)
find . | cpio -o -H newc | gzip > ${IMG}
echo The image is
ls -l ${IMG}
popd

The additions are the files for the web server and two arguments to the script which allow the BusyBox binary and a tag for the cpio archive to be chosen. Using a different tag allows several initramfs files for different BusyBox binaries to be kept in /boot. The web server files are stored in /etc/httpd — they will be copied to the appropriate place by /etc/init.d/rcS.

It is difficult to set up the BusyBox configuration, there are a lot of changes and additions and using "make menuconfig" to do this is tiresome and error prone. Here is a script to do this, it borrows the shell function "busybox_config_option" from the BusyBox ebuild:

Code: A script to configure BusyBox
#!/bin/bash
# busybox_config_option is copied from sys-apps/busybox/busybox-1.2.2.1.ebuild

busybox_config_option() {
        case $1 in
                y) sed -i -e "s:# CONFIG_$2 is not set:CONFIG_$2=y:" .config;;
                n) sed -i -e "s:CONFIG_$2=y:# CONFIG_$2 is not set:" .config;;
                Y) echo "CONFIG_$2=y" >> .config;;
                N) echo "CONFIG_$2=n" >> .config;;
	    # To set an option to a number
                *) sed -i -e "s:# CONFIG_$2 is not set:CONFIG_$2=$1:" .config;;
        esac
}

die () {
    echo $@
    exit 1
}

if [ -z "$1" ]; then
    CFG_DIR=${HOME}
else
    CFG_DIR=$1
fi
echo The defconfig and the working .config will be saved to ${CFG_DIR}

[ -r Makefile ] || die "No Makefile, wrong directory?"
VERARR=( $(sed -n 's/^VERSION \+= \+\([0-9]\+\)/\1./p; s/^PATCHLEVEL \+= \+\([0-9]\+\)/\1./p; s/^SUBLEVEL \+= \+\([0-9]\+\)/\1/p' < Makefile) )

for n in 0 1 2
do
    echo VERARR[$n] ${VERARR[$n]}
done
VERSTR="${VERARR[0]}${VERARR[1]}${VERARR[2]}"
echo Version string \"${VERSTR}\"

make defconfig
cp -pv .config ${CFG_DIR}/busybox-1.4.1-defconfig

busybox_config_option n INCLUDE_SUSv2
busybox_config_option n LOCALE_SUPPORT
busybox_config_option n FEATURE_SUID_CONFIG
busybox_config_option n FEATURE_SUID_CONFIG_QUIET
busybox_config_option y STATIC
busybox_config_option y DEBUG_YANK_SUSv2
busybox_config_option n TASKSET
busybox_config_option n ETHER_WAKE
busybox_config_option y FEATURE_HTTPD_SETUID
busybox_config_option y FEATURE_SH_IS_ASH
busybox_config_option n FEATURE_SH_IS_NONE
busybox_config_option y ASH_JOB_CONTROL
busybox_config_option y ASH_READ_NCHARS
busybox_config_option y ASH_READ_TIMEOUT
busybox_config_option y ASH_ALIAS
busybox_config_option y ASH_MATH_SUPPORT
busybox_config_option y ASH_GETOPTS
busybox_config_option y ASH_BUILTIN_ECHO
busybox_config_option y ASH_BUILTIN_TEST
busybox_config_option y ASH_OPTIMIZE_FOR_SIZE
busybox_config_option y ASH_RANDOM_SUPPORT
busybox_config_option y FEATURE_COMMAND_EDITING
busybox_config_option y FEATURE_COMMAND_TAB_COMPLETION
busybox_config_option y FEATURE_SH_FANCY_PROMPT
busybox_config_option y RUNSV
busybox_config_option y RUNSVDIR
busybox_config_option y SV
busybox_config_option y SVLOGD

make oldconfig

cp -pv .config ${CFG_DIR}/busybox-1.4.1-config

Unfortunately, it does not appear possible to set the option "CONFIG_FEATURE_COMMAND_HISTORY" which takes a number: when "make oldconfig" is run, it will stop at this option for you to select a number — pressing Enter will choose the default, 15.

The script expects to be run in the top of the BusyBox source tree, that is, in the same directory as the top level BusyBox "Makefile".

To leave this embedded system, you can use "Ctrl-Alt-Del" which reboots — see the inittab entry for "ctrlaltdel" — or issue the commands "reboot" to reboot or "poweroff" to shutdown and power off the machine. The command "halt" halts the machine but doesn't power it off.

Other kernels

Linux Kernel Configuration: Enable initial ramdisk support
Device drivers --->
    Block devices --->
        <*> RAM disk support
        [*]    Initial RAM disk (initrd) support

In modern kernel releases, initrd is located elsewhere:

Linux Kernel Configuration: Enable initial ramdisk support
General setup --->
    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support

Writing the /init script (work in progress)

Typically, you would include busybox or klibc in the initramfs, both of which provide a cut-down version of bash, namely ash. This allows you to write the /init file as a shell script. However, this is not mandatory and /init can practically be any program written in any language you chose.

I call it init.sh and include it later in the initramfs.

Code: init.sh
#!/bin/sh
#
# Mathias Laurin
# (c) 2006, Mathias Laurin, GPLv2, Tokyo
# NO WARRANTY
#
# mandatory argument: root= 
# optional arguments: init= shell verbose resume2
#
# The modules needed to boot are expected to be listed in 
# /etc/modules/boot, one per line

EDIT=
INIT="/sbin/init"
ARGS_ROOT_MOUNT=
NEWROOT="/newroot"
REALROOT=
SUSPEND=
VERBOSE=

verbose() {
	[ $VERBOSE ] && echo " * $@"
}

warning() {
	echo "WARNING: $@"
}

die() {
	verbose "Dying"
	[ $# -ne 0 ] && echo "$@"

	# Enable job control for the shell
	[ ! -c /dev/tty ] && \
	mknod /dev/tty c 5 0 && chown 0:tty /dev/tty

	# Try to find a minimal shell
	exec /bin/sh || \
	exec /bin/busybox
	
	echo "FATAL ERROR: Could not even die elegantly"
	echo "trying to force a reboot in 5 seconds"
	sleep 5
	reboot -f
}

do_bootsplash() {
	return 0
}

do_dev() {
	verbose "Populating /dev from sys"

	mdev -s
	# handle hotplug events
	echo /sbin/mdev > /proc/sys/kernel/hotplug

	return 0
}

do_lvm() {
	verbose "Enable LVM2 support"
	
	/sbin/lvm vgscan --mknodes --ignorelockingfailure
	/sbin/lvm vgchange -a y --ignorelockingfailure

	sleep 1

	return 0
}

do_parse_kernel() {
	#verbose "Parse kernel line"

	local CMD
	for CMD in $(cat /proc/cmdline)
	do
		case "$CMD" in
			init=*)
				INIT=${CMD#init=}
				;;
			shell)
				EDIT="yes"
				;;
			resume|resume2)
				SUSPEND="yes"
				;;
			ro|rw)
				ARGS_ROOT_MOUNT="-o $CMD"
				;;
			root=*)
				REALROOT=${CMD#root=}
				;;
			v|verbose)
				VERBOSE="yes"
				;;
		esac
	done

	return 0
}

do_realroot() {
	verbose "Mounting $REALROOT on $NEWROOT"

	# findfs
	# fsck.???

	[ ! -d $NEWROOT ] && mkdir $NEWROOT
	mount $REALROOT $NEWROOT $ARGS_ROOT_MOUNT

	return $?
}

do_suspend() {
	return 0
}

do_switch() {
	verbose "Switching / to $NEWROOT"

	# Better make sure these exist before switching
	[ ! -c "$NEWROOT/dev/console" ] && mknod "$NEWROOT/dev/console" c 5 1
	[ ! -c "$NEWROOT/dev/null" ] && mknod "$NEWROOT/dev/null" c 1 3

	echo > /proc/sys/kernel/hotplug
	
	umount /dev  || umount -l /dev
	umount /sys  || umount -l /sys
	umount /proc || umount -l /proc

	exec switch_root $NEWROOT $INIT
}

modprobe_group() {
	local GROUP="$1"
	local MOD
	verbose "Insert $GROUP modules"

	if [ -f "/etc/modules/$GROUP" ]
	then
		for MOD in $(cat "/etc/modules/$GROUP")
		do
			modprobe $MOD 1>/dev/null
		done
	else
		warning "File /etc/modules/$GROUP not found"
	fi

	return 0
}

main() {
	# mount proc ASAP
	/bin/busybox mount -t proc proc /proc

	# install busybox applets
	/bin/busybox --install -s
	umask 0077

	# finish mounting
	mount -t sysfs sysfs /sys
	mount -t tmpfs tmpfs /dev

	# parse kernel command line
	do_parse_kernel

	# load boot modules
	modprobe_group boot

	# populate dev from sys
	do_dev
	
	# splashscreen
	do_bootsplash

	# suspend2
	[ $SUSPEND ] && do_suspend

	# LVM2
	[ -x /sbin/lvm ] && do_lvm

	do_realroot
	
	# Switch unless a shell was requested
	[ "$EDIT" ] || do_switch
}

main
die


Note: You should not use pivot_root but either switch_root (busybox) or run-init (klibc). What this program does is absolutely non trivial, it cleans your rootfs (the initramfs), chroots to the real root and executes the real init process with process ID (PID) 1. That means it has to remove programs before executing them.

Another thing worth noticing is that init needs to be executed with PID 1 to behave like init and not telinit so you need the exec statement before switch_root (see man init).

But then note that, since it gets PID 1, if it fails, you will end with a kernel panic.

Building the initramfs

Base filesystem

The format of the makefile for initramfs is given by calling /usr/src/linux/usr/gen_init_cpio without arguments

File: gen_init_cpio
Usage:  
        gen_init_cpio <cpio_list>

<cpio_list> is a file containing newline separated entries that
describe the files to be included in the initramfs archive:

# a comment
file <name> <location> <mode> <uid> <gid>
dir <name> <mode> <uid> <gid>
nod <name> <mode> <uid> <gid> <dev_type> <maj> <min>
slink <name> <target> <mode> <uid> <gid>
pipe <name> <mode> <uid> <gid>
sock <name> <mode> <uid> <gid>

<name>      name of the file/dir/nod/etc in the archive
<location>  location of the file in the current filesystem
<target>    link target
<mode>      mode/permissions of the file
<uid>       user id (0=root)
<gid>       group id (0=root)
<dev_type>  device type (b=block, c=character)
<maj>       major number of nod
<min>       minor number of nod
File: A simple initramfs
dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1
dir /root 0700 0 0
dir /sbin 0755 0 0
file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0

There are basically two ways to make the initramfs,

The second method is a bit more complicated but more flexible as it allows to use a different compiler, like uclibc and generate smaller binaries.

Generate from files present on the system

My own makefile is now

File: makefile
# initramfs makefile

# /dev directory
dir /dev            755 0 0
nod /dev/console    644 0 0 c 5 1
nod /dev/null       644 0 0 c 1 3

# Empty dirs
dir   /root         0700 0 0
dir   /proc         0755 0 0
dir   /sys          0755 0 0
dir   /tmp          1755 0 0

# Init
file  /init    ./init.sh      755 0 0

# /bin directory
dir   /bin                                755 0 0
file  /bin/busybox  /bin/busybox.static   755 0 0
slink /bin/sh       /bin/busybox          755 0 0

# /lib/modules directory (will populate later)
dir   /lib           0755 0 0
dir   /lib/modules   0755 0 0

# /sbin directory
dir   /sbin                        0750 0 0
file  /sbin/lvm       /sbin/lvm     750 0 0

# boot splash
#file  /sbin/splash_helper      /sbin/splash_helper      755 0 0
#file  /sbin/splash_util.static /sbin/splash_util.static 755 0 0

#/etc directory
dir   /etc                               0756 0 0
dir   /etc/lvm                            755 0 0
file  /etc/lvm/lvm.conf /etc/lvm/lvm.conf 644 0 0

I leave dealing with the multicall binaries (busybox and lvm) to the init script. But I make the sh -> busybox symlink for safety.

Automatic generation of the makefile

If you chose the other approach, build a filesystem in a chroot (e.g. TinyGentoo) and generate the makefile with /usr/src/linux/scripts/gen_initramfs_list.sh.

/bin/sh /usr/src/linux/scripts/gen_initramfs_list.sh /path/to/chroot > makefile

Dealing with the modules (modular kernel)

This script makes a list of all modules installed for every kernel installed. The resulting file is in a format suitable for gen_init_cpio

File: gen_modules_list.sh
#!/bin/sh
#
# (c) 2006 Mathias Laurin, GPLv2 (no warranty)
# v0.2 -  - Tokyo
#
# It is too dangerous to take the kernel version as argument or to 
# use `uname -r` for guessing it. That would just break everything 
# on kernel upgrades.

GEN_INIT="/usr/src/linux/usr/gen_init_cpio"

usage () {
cat << EOF
gen_modules_list.sh [--help]

make a list of the modules compiled for every kernel in a format 
suitable for $GEN_INIT.

EOF
exit
}

header () {
cat << EOF
# This file generated by gen_modules_list.sh
# (c) 2006 Mathias Laurin <mathias_laurin AT users.sourceforge.com>
# GPL v2  -  No responsibility taken if it breaks your system!
#
# Provide the list of modules present on your system in a way
# suitable for usr/gen_init_cpio from the kernel tree.
#
EOF
}

[ $# -ne 0 ] && usage

MODULES_DIR="/lib/modules"

for KERNEL in $(ls $MODULES_DIR)
do
	OUTPUT="modules.txt-$KERNEL"
	COUNTER=0

	# Clean file and write header
	header > $OUTPUT
	for n in $(find $MODULES_DIR/$KERNEL)
	do
		[ -d $n ] && echo "dir $n 700 0 0"
		[ -f $n ] && {
			echo "file $n $n 600 0 0"
			# Test extension to count modules
			[ "${n##*.}" = "ko" ] && : $((COUNTER++))
		}
	done >> $OUTPUT

	cat << EOF

$COUNTER modules found for $KERNEL, compile with:
  $GEN_INIT $OUTPUT > modules.img-$KERNEL"

EOF

done

Generate the initramfs

No matter what method(s) you have chosen, you should now have makefiles to feed to /usr/src/linux/usr/gen_init_cpio. You are ready to build the initramfs.

Code: install.sh
#!/bin/sh
#
# (c) 2006, Mathias Laurin, Tokyo
# GPLv2       -       NO WARRANTY
#
# Install new initramfs image after
# [[HOWTO Initramfs]]
#
# Call with a kernel version, like
#  install.sh 2.6.18-suspend2-r1
# if no arguments are supplied, will build
# for the running kernel

die() { 
	local MSG=$*
	[ "$MSG" ] && echo "$MSG"
	exit 
}

tease() {
	local n=0
	while [ $n -lt $1 ]
	do
		echo -n "."
		sleep 1
		: $((n++))
	done
	echo
}

running_kernel() {
	local TIME=$1
	echo "Image build on the running kernel $KERNEL, "
	echo -n "Ctrl+C to cancel or wait $TIME seconds to continue"
	tease $TIME
}

KERNEL="${1:-`uname -r`}"
BASE="base.img-$KERNEL"
MODIN="modules.txt-$KERNEL"
MODIMG="modules.img-$KERNEL"
INIT="initramfs.img-$KERNEL"

[ $# -eq 0 ] && running_kernel 15

gen_init_cpio makefile > $BASE || die "Failed to create $BASE"
gen_init_cpio $MODIN > $MODIMG || die "Failed to create $MODIMG"
cat $BASE $MODIMG > $INIT

mount /boot || die "Mounting boot failed, update fstab"
[ -e "/boot/$INIT" ] && cp "/boot/$INIT" "/boot/$INIT.old"
cp $INIT /boot
ln -sf $INIT /boot/initramfs.img
umount /boot


Or by hand,

Code: Build the image
  • build the base filesystem
/usr/src/linux/usr/gen_init_cpio makefile > base.img-$KERNEL
  • and pack the modules
/usr/src/linux/usr/gen_init_cpio modules.txt-$KERNEL > modules.img-$KERNEL
  • finally make the initramfs image by concatenating the base and the modules together
cat base.img-$KERNEL modules.img-$KERNEL > initramfs.img-$KERNEL
Warning: Be extremely careful to include the modules from the right kernel! If you are not upgrading the kernel and are going to reboot into the same one, you can use
KERNEL=`uname -r` 
if not, build the correct ones or else you take the risk that you will not be able to boot. You have been warned.

Bootloader

Finally, you can copy the new initramfs image to your /boot partition

Code: cp the image
as root:
mount /boot
cp initramfs.img-$KERNEL /boot
ln -sf /boot/initramfs.img-$KERNEL /boot/initramfs.img

and add an entry to your bootloader:

File: grub.conf
...

title=Gentoo with initramfs
root (hd0,0)
kernel /vmlinuz
initrd /initramfs.img

...

Alternative: build the image into the kernel

This article is still a Stub. You can help Gentoo-Wiki by expanding it.

Kernels from the 2.6 series, starting at n include an empty initramfs that the kernel mounts at every boot, you can check it with grep rootfs /proc/mounts. It is possible to replace this empty image with your custom one.

this procedure is described in Documentation/filesystems/ramfs-rootfs-initramfs.txt in the linux kernel source tree. It is now the preferred (and easiest) method of setting up an initramfs.


Just point the kernel (General Setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support) to your initramfs makefile. The kernel build system will do everything else for you :-)

In this makefile, all non-absolute paths with the 'file'-command are interpreted relative to the kernel-source directory. Thus, it is perhaps a good idea to change this

Code:
# Init
file  /init    ./init.sh      755 0 0

into

Code:
# Init
file  /init    /home/foo/init.sh      755 0 0

and store your init-script in your home directory.

If you want to use your localized keyboard layout in a rescue shell, you have to create it first:

Code: generating a keymap file
dumpkeys > default_keymap

# Now use BusyBox's dumpkmap applet to obtain the keymap from the
# current loaded keymap.
# dumpkeymap needs a symlink from /dev/vc/0 to /dev/console
mkdir /dev/vc
ln -s /dev/console /dev/vc/0
busybox dumpkmap > keymap.bin

Note that this link has to be present in the initramfs, too and mdev does not create it, so you have to add it manually.

After setting up the initramfs makefile and init-script in the right places, during a kernel compile, the initramfs image will be created and linked *into* the kernel. Thus, no editing of grub or some other bootloader is required, and the kernel can be treated like a normal one.

some useful links, which illustrate this method in detail (mostly a repetition of what is here already): [2] [3]


References

Local wiki

Files

External

Retrieved from "http://www.gentoo-wiki.info/Initramfs"

Last modified: Fri, 29 Aug 2008 08:22:00 +0000 Hits: 44,513