分类: LINUX
2009-12-10 10:24:23
The initial RAM disk (initrd) is an initial root file system that is mounted prior to when the real root file system is available. The initrd is bound to the kernel and loaded as part of the kernel boot procedure. The kernel then mounts this initrd as part of the two-stage boot process to load the modules to make the real file systems available and get at the real root file system.
The initrd contains a minimal set of directories and executables to achieve
this, such as the insmod
tool to install kernel modules
into the kernel.
In the case of desktop or server Linux systems, the initrd is a transient file system. Its lifetime is short, only serving as a bridge to the real root file system. In embedded systems with no mutable storage, the initrd is the permanent root file system. This article explores both of these contexts.
The initrd image contains the necessary executables and system files to support the second-stage boot of a Linux system.
Depending on which version of Linux you're running, the method for
creating the initial RAM disk can vary. Prior to Fedora Core 3, the initrd is
constructed using the loop device.
The loop device is a device driver that allows you to mount a file as a
block device and then interpret the file system it represents. The loop device
may not be present in your kernel, but you can enable it through the kernel's
configuration tool (make menuconfig
) by selecting
Device Drivers > Block Devices > Loopback Device Support.
You can inspect the loop device as follows
(your initrd file name will vary):
|
You can now inspect the /mnt/initrd subdirectory for the contents of the initrd. Note that even if your initrd image file does not end with the .gz suffix, it's a compressed file, and you can add the .gz suffix to gunzip it.
Beginning with Fedora Core 3, the default initrd image is a compressed cpio archive file. Instead of mounting the file as a compressed image using the loop device, you can use a cpio archive. To inspect the contents of a cpio archive, use the following commands:
|
The result is a small root file system, as shown in Listing 3. The small, but
necessary, set of applications are present in the ./bin directory, including
nash
(not a shell, a script interpreter),
insmod
for loading kernel modules, and
lvm
(logical volume manager tools).
|
Of interest in Listing 3 is the init file at the root. This file, like the traditional Linux boot process, is invoked when the initrd image is decompressed into the RAM disk. We'll explore this later in the article.
Let's now go back to the beginning to formally understand how the initrd
image is constructed in the first place. For a traditional Linux system,
the initrd image is created during the Linux build process. Numerous tools,
such as mkinitrd
, can be used to automatically build
an initrd with the necessary libraries and modules for bridging to the real root
file system. The mkinitrd
utility is actually a
shell script, so you can see exactly how it achieves its result. There's also
the YAIRD
(Yet Another Mkinitrd) utility, which permits customization of every aspect of the initrd construction.
Manually building a custom initial RAM disk
Because there is no hard drive in many embedded systems based on Linux, the initrd also serves as the permanent root file system. Listing 4 shows how to create an initrd image. I'm using a standard Linux desktop so you can follow along without an embedded target. Other than cross-compilation, the concepts (as they apply to initrd construction) are the same for an embedded target.
|
To create an initrd, begin by creating an empty file,
using /dev/zero
(a stream of zeroes) as input
writing to the ramdisk.img file. The resulting
file is 4MB in size (4000 1K blocks). Then use the
mke2fs
command to create an ext2 (second extended)
file system using the empty file. Now that this file is an ext2 file system,
mount the file to /mnt/initrd using the loop device.
At the mount point, you now have a directory that represents an ext2 file system
that you can populate for your initrd. Much of the rest of the script provides
this functionality.
The next step is creating the necessary subdirectories that make up your root file system: /bin, /sys, /dev, and /proc. Only a handful are needed (for example, no libraries are present), but they contain quite a bit of functionality.
To make your root file system useful, use BusyBox. This utility is a single image that contains many individual utilities commonly found in Linux systems (such as ash, awk, sed, insmod, and so on). The advantage of BusyBox is that it packs many utilities into one while sharing their common elements, resulting in a much smaller image. This is ideal for embedded systems. Copy the BusyBox image from its source directory into your root in the /bin directory. A number of symbolic links are then created that all point to the BusyBox utility. BusyBox figures out which utility was invoked and performs that functionality. A small set of links are created in this directory to support your init script (with each command link pointing to BusyBox).
The next step is the creation of a small number of special device files. I
copy these directly from my current /dev
subdirectory, using the -a
option (archive) to
preserve their attributes.
The penultimate step is to generate the linuxrc
file. After the kernel mounts the RAM disk, it searches for an
init
file to execute. If an init
file is not
found, the kernel invokes the linuxrc file as its startup script. You
do the basic setup of the environment in this file, such as mounting
the /proc file system. In addition to /proc, I also mount the /sys file
system and emit a message to the console. Finally, I invoke ash
(a Bourne Shell clone) so I can interact with the root file system. The linuxrc file is then made executable using chmod
.
Finally, your root file system is complete. It's unmounted and then
compressed using gzip
. The resulting file
(ramdisk.img.gz) is copied to the /boot subdirectory so it can be loaded via
GNU GRUB.
To build the initial RAM disk, you simply invoke
mkird
, and the image is automatically created and copied to /boot.
Testing the custom initial RAM disk
Your new initrd image is in /boot, so the next
step is to test it with your default kernel. You can now restart your Linux
system. When GRUB appears, press the C key to enable the command-line
utility within GRUB. You can now interact with GRUB to define the specific
kernel and initrd image to load. The kernel
command allows you to define the kernel file, and the
initrd
command allows you to specify the particular
initrd image file. When these are defined, use the
boot
command to boot the kernel, as shown in Listing 5.
|
After the kernel
starts, it checks to see if an initrd image is available
(more on this later), and then loads and mounts it as the root file
system.
You can see the end of this particular Linux startup in Listing 6. When
started, the ash shell is available to enter commands. In
this example, I explore the root file system and interrogate a virtual
proc
file system entry. I also demonstrate that you can write to the file
system
by touching a file (thus creating it). Note here that the first process
created is linuxrc
(commonly
init
).
|
Now that you've seen how to build and use a custom initial RAM disk, this section explores how the kernel identifies and mounts the initrd as its root file system. I walk through some of the major functions in the boot chain and explain what's happening.
The boot loader, such as GRUB, identifies the kernel that is to be loaded and copies this kernel image and any associated initrd into memory. You can find much of this functionality in the ./init subdirectory under your Linux kernel source directory.
After the kernel and initrd images are decompressed and copied into
memory, the kernel is invoked. Various initialization is performed and,
eventually, you find yourself in init/main.c:init()
(subdir/file:function). This function performs a large amount of subsystem
initialization. A call is made here to
init/do_mounts.c:prepare_namespace()
, which is used
to prepare the namespace (mount the dev file system, RAID, or md, devices, and,
finally, the initrd). Loading the initrd is done through a call to
init/do_mounts_initrd.c:initrd_load()
.
The initrd_load()
function calls
init/do_mounts_rd.c:rd_load_image()
, which determines
the RAM disk image to load through a call to
init/do_mounts_rd.c:identify_ramdisk_image()
. This
function checks the magic number of the image to determine if it's a minux,
etc2, romfs, cramfs, or gzip format. Upon return to
initrd_load_image
, a call is made to
init/do_mounts_rd:crd_load()
. This function
allocates space for the RAM disk, calculates the cyclic redundancy check (CRC), and then uncompresses and
loads the RAM disk image into memory. At this point, you have the initrd image
in a block device suitable for mounting.
Mounting the block device now as root begins with a call to
init/do_mounts.c:mount_root()
. The root device is
created, and then a call is made to
init/do_mounts.c:mount_block_root()
. From here,
init/do_mounts.c:do_mount_root()
is called, which
calls fs/namespace.c:sys_mount()
to actually mount
the root file system and then chdir
to it. This is
where you see the familiar message shown in Listing 6: VFS: Mounted root (ext2 file system).
Finally, you return to the init
function and call
init/main.c:run_init_process
. This results in a
call to execve
to start the init process (in this
case /linuxrc
). The linuxrc can be an executable
or a script (as long as a script interpreter is available for it).
The hierarchy of functions called is shown in Listing 7. Not all functions that are involved in copying and mounting the initial RAM disk are shown here, but this gives you a rough overview of the overall flow.
|
Much like embedded booting scenarios, a local disk (floppy or CD-ROM) isn't necessary to boot a kernel and ramdisk root filesystem. The Dynamic Host Configuration Protocol (or DHCP) can be used to identify network parameters such as IP address and subnet mask. The Trivial File Transfer Protocol (or TFTP) can then be used to transfer the kernel image and the initial ramdisk image to the local device. Once transferred, the Linux kernel can be booted and initrd mounted, as is done in a local image boot.
When you're building an embedded system and want the smallest initrd image possible, there are a few tips to consider. The first is to use BusyBox (demonstrated in this article). BusyBox takes several megabytes of utilities and shrinks them down to several hundred kilobytes.
In this example, the BusyBox image is statically linked so that no libraries are required. However, if you need the standard C library (for your custom binaries), there are other options beyond the massive glibc. The first small library is uClibc, which is a minimized version of the standard C library for space-constrained systems. Another library that's ideal for space-constrained environments is dietlib. Keep in mind that you'll need to recompile the binaries that you want in your embedded system using these libraries, so some additional work is required (but worth it).
The initial RAM disk was originally created to support bridging the kernel to the ultimate root file system through a transient root file system. The initrd is also useful as a non-persistent root file system mounted in a RAM disk for embedded Linux systems.