[Lesca译文]initrd 概览:什么是initrd, initrd原理, 创建initrd, 手动创建initrd

作者: lesca 分类: ARM,Concept,Kernel,Tutorials,Ubuntu 发布时间: 2011-03-02 20:34

原作者:M. Tim Jones (mtj@mtjones.com), Emulex公司工程、顾问
原文链接: IBM DeveloperWorks – Linux initial RAM disk (initrd) overview

摘要: Linux® initial RAM disk (initrd) 是一个临时的根文件系统(root file system),它在系统引导期间被挂载,以提供“双阶段引导”过程的支持。initrd 包含各种可执行文件和驱动以允许真正的根文件系统的加载。在这之后 initrd 会被解挂载并且释放它所占用的内存。在许多嵌入式的Linux系统中,initrd是它们的最终根文件系统。本文介绍了Linux 2.6的initial RAM disk,并将探讨它在内核中的创建与使用。

什么是 initial RAM disk?

initial RAM disk (initrd) 在真正的根文件系统被加载前加载,它是一个初始的根文件系统。initrd与内核绑定在一起,并且作为内核引导过程的一部分而被加载。内核将initrd作双阶段引导过程的一部分加载,然后加载模块并使真正的根文件系统可用。
initrd 包含了最小化的目录与可执行文件(例如insmod以安装内核模块)的集合来实现此过程。
就桌面及服务器Linux系统而言,initrd是瞬态的文件系统,它的生命期很短,只作为连接真实根文件系统的桥梁而存在。而在没有可擦写存储器的嵌入式系统中,它是永久的根文件系统。本文将讲讲述这两个部分。

initrd 剖析

initrd 镜像包含必要的可执行文件和系统文件以支持Linux系统第二阶段的引导。根据你所使用的Linux版本不同,创建initrd RAM disk 的方法也不尽相同。先于Fedora Core 3的版本中,initrd是由一种叫做loop device的设备驱动构建,它允许你将一个文件挂载为块设备,并且通知文件系统。loop device也许不会在你的内核中出现,但是你能够从内核配置工具make menuconfig中启用它:Device Drivers > Block Devices > Loopback Device Support。你可以通过如下方式查看loop device设备(你的initrd文件名可能会不同):
Listing 1. 检查initrd(限FC3之前的版本)

# mkdir temp ; cd temp
# cp /boot/initrd.img.gz .
# gunzip initrd.img.gz
# mount -t ext -o loop initrd.img /mnt/initrd
# ls -la /mnt/initrd
#

现在你就可以查看/mnt/initrd子目录中initrd的内容了。注意:即使你的initrd镜像文件不以.gz扩展名结尾(这是个压缩文件),你也可以添加上去,然后用gunzip解压缩。
从Fedora Core 3开始,默认的initrd镜像是一个压缩过的cpio存档文件。因此,你应该用cpio工具,而不是将镜像解压缩后挂载为loop device,来查看其中的内容。要查看cpio文档内容,应该使用以下命令:
Listing 2. 检查initrd(限FC3在内的之后版本)

# mkdir temp ; cd temp
# cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
# gunzip initrd-2.6.14.2.img.gz
# cpio -i --make-directories < initrd-2.6.14.2.img
#

其结果是一个迷你的根文件系统,如Listing 3所示。虽然小,但却非常重要。工具集在./bin/目录中,包括nash(不是shell,而是脚本解释器),insmod(用来载入内核模块),还有lvm(logical volume manager tools,逻辑卷管理工具)。
Listing 3. 默认Linux initrd目录结构

# ls -la
#
drwxr-xr-x  10 root root    4096 May 7 02:48 .
drwxr-x---  15 root root    4096 May 7 00:54 ..
drwxr-xr-x  2  root root    4096 May 7 02:48 bin
drwxr-xr-x  2  root root    4096 May 7 02:48 dev
drwxr-xr-x  4  root root    4096 May 7 02:48 etc
-rwxr-xr-x  1  root root     812 May 7 02:48 init
-rw-r--r--  1  root root 1723392 May 7 02:45 initrd-2.6.14.2.img
drwxr-xr-x  2  root root    4096 May 7 02:48 lib
drwxr-xr-x  2  root root    4096 May 7 02:48 loopfs
drwxr-xr-x  2  root root    4096 May 7 02:48 proc
lrwxrwxrwx  1  root root       3 May 7 02:48 sbin -> bin
drwxr-xr-x  2  root root    4096 May 7 02:48 sys
drwxr-xr-x  2  root root    4096 May 7 02:48 sysroot
#

有趣的是,在Listing 3中,init文件在根目录里。这个文件会像传统的Linux引导进程,在initrd镜像解压缩进RAM Disk后运行。本文稍后就会介绍。

创建initrd的工具

cpio命令

使用cpio命令,你就可以控制cpio文件。Cpio也是一种文件格式,它简单地将具有头部的各个文件连结在一起。cpio文件格式同时允许ASCII与二进制文件。为了便于移植,使用ASCII;为了减少文件大小,使用二进制版本。

现在,我们先回到开始正式理解initrd镜像是如何构建的地方。对于传统的Linux系统,initrd镜像是在build过程中创建的。很多工具,像mkinitrd,也能自动地创建拥有必要可执行文件、模块的initrd镜像。mkinitrd工具事实上是一个shell脚本,所以你可以确切地知道它到底是如何生成结果的。还有YAIRD(Yet Another Mkinitrd)工具,它允许自定义initrd结构的各个方面。

手动构建一个自定义的initial RAM disk

由于在基于Linux系统的嵌入式系统中没有可用的硬件驱动,initrd也用作永久的根文件系统。Listing 4给出了如何创建initrd镜像的过程。
我使用的标准的桌面版本的Linux,所以即使你没有嵌入式目标机,你也可以跟着我做。即使对于嵌入式目标机,initrd的概念也不会不同,除非你使用的是交叉编译平台。
Listing 4. 使用mkird创建自定义的initrd

#!/bin/bash

# 清理...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz

# Ramdisk 常量
RDSIZE=4000
BLKSIZE=1024

# 创建一个空 ramdisk 镜像
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE

# 使它成为可挂载的ext2文件系统
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE

# 挂载它,以便迁移文件
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0

# 迁移文件系统 (子目录)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc

# 抓取 busybox 并创建符号链接
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd

# 抓取必要的设备文件
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev

# 连结sbin与bin
pushd /mnt/initrd
ln -s bin sbin
popd

# 创建 init 文件
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF

chmod +x /mnt/initrd/linuxrc

# 完成...
umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz

要创建initrd,我们首先用设备/dev/zero创建一个空文件,命名为ramdisk.img,最后它将会是一个4MB大小的文件(4000×1K)。然后用mke2fs命令将那个文件创建为ext2(second extended)文件系统文件,再将它以loop设备的类型挂载到/mnt/initrd。在这个挂载点上,你能看到一个具有ext2文件系统的目录,并将你的initrd迁移到其中。余下的脚本也就是实现这样的功能。

一个initrd Linux 发行版

有个有趣的开源项目——Minimax,它是一个做在initrd中的linux发行版。它只有32MB,使用BusyBox和uClibc。别看它小,它使用的是2.6版本的Linux内核,并集成了大量有用工具。

接下来,我们创建必要的构建根文件系统的子目录:/bin, /sys, /dev以及/proc。虽然我们只用到一部分目录,但是他们包含主要的功能。
为了使你的根文件系统有用,我们使用BusyBox。它是一个包含许多Linux工具(如awk, sed, insmod等)的镜像。使用BusyBox的优点是,它将许多常用工具打包进一个单一的镜像,这使文件更小。对嵌入式系统来说,这是求之不得的。于是,我们将BusyBox镜像复制到根文件系统下的/bin目录,并建立一系列指向BusyBox的符号链接。BusyBox会知道应该启动哪个工具。这些符号链接会为你的init脚本提供支持。

ext2文件系统的替代品

虽然ext2是通用的linux文件系统格式,但是用其他文件系统也许能减少initrd镜像的大小以及最终挂载的文件系统大小。例如romfs(ROM文件系统)、cramfs(压缩ROM文件系统)以及squashfs(高度压缩只读文件系统)。如果你希望立即将数据写入文件系统,ext2比较好。最后e2compr是支持在线压缩的ext2文件系统驱动的扩展。

下一步是创建特殊设备文件。我直接从我现有的/dev子目录中复制,并使用选项-a (archive)来保留他们的属性。
倒数第二步,生成linuxrc文件。在内核挂载到RAM disk之后,它会搜索一个init文件并执行之。如果init文件没有找到,内核启动linuxrc文件作为启动脚本。在这个脚本中,你创建一个基本的环境,例如挂载/proc文件系统。除了/proc,还要挂载/sys文件系统并向控制台发出一个消息。最后,启动ash(a Bourne Shell的副本)以便与跟文件系统交互。linuxrc文件还要标记为可执行。
最后,你的根文件系统完成了。随后将它解挂载并用gzip压缩。将最终文件(ramdisk.img.gz)复制到/boot目录,以便它能随着GNU GRUB一起启动。要建立RAM disk,只要启动mkird,然后镜像就会自动创建并拷贝到/boot

测试自定义的初始化RAM盘(initial RAM disk)

Initrd 在内核中的支持

为了让linux内核支持initial RAM disk,内核必须使用CONFIG_BLK_DEV_RAM以及CONFIG_BLK_DEV_INITRD选项编译。

现在你的新initrd镜像在/boot下,所以我们接下来可以用你的默认内核测试它。首先重新启动你的Linux系统,当出现GRUB时,按C来GRUB的命令行模式。(如果你的计算机没有出现GRUB引导菜单,请在启动时反复按SHIFT——Lesca 注)现在你就可以和GRUB交互并指定要加载内核和initrd镜像。命令kernel允许你定义内核文件,initrd允许你指定initrd镜像文件。指定好之后,使用boot命令引导内核。如Listing 5所示:
Listing 5. 用GRUB手动挂载内核与initrd


                
    GNU GRUB  version 0.95  (638K lower / 97216K upper memory)

[ Minimal BASH-like line editing is supported. For the first word, TAB
  lists possible command completions. Anywhere else TAB lists the possible
  completions of a device/filename. ESC at any time exits.]

grub> kernel /bzImage-2.6.1
   [Linux-bzImage, setup=0x1400, size=0x29672e]

grub> initrd /ramdisk.img.gz
   [Linux-initrd @ 0x5f2a000, 0xb5108 bytes]

grub> boot

Uncompressing Linux... OK, booting the kernel.

在内核启动之后,它会检查initrd是否有效,再加载并将它挂载为根文件系统。你可以在Listing 6中看到这次特别的启动过程。开始之后,ash就开始等待命令的输入。在本例中,我浏览了根文件系统并查询了proc文件系统项,并证明了你可以使用touch命令在文件系统中创建文件。注意,此处第一个处理的脚本文件是linuxrc(通常会是init)。
Listing 6. 用一个简单的initrd引导Linux内核

...
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
bin         etc       linuxrc       proc        sys
dev         lib       lost+found    sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash      cat      echo     mount    sysctl
busybox  dmesg    ls       ps
/bin $ touch zfile
/bin $ ls
ash      cat      echo     mount    sysctl
busybox  dmesg    ls       ps       zfile

使用initrd引导

到目前为止,你已经知道如何建立自定义的初始化RAM盘了,本节将介绍内核是如何识别并将initrd挂载为根文件系统的。我将介绍一些引导链中的主要函数并解释其中发生了什么。

像GRUB那样的boot loader能够识别将被加载的内核,并且将它以及相关initrd复制到内存中。在./init子目录中(Linux内核源文件目录下),你能够发现很多类似的功能。

在内核与initrd被解压并加载到内存之后,内核就启动了。各种各样的初始化过程也随之开始,最后你会发现自己在init/main.c:init()函数中(格式说明:subdir/file:function)。这个函数为许多子系统进行初始化,例如其间会调用这样一个函数:init/do_mounts.c:prepare_namespace(),它将准备命名空间(挂载dev文件系统,RAID,md,其他设备,最后还有initrd)。调用init/do_mounts_initrd.c:initrd_load()之后,就开始加载initrd。

initrd_load()函数调用init/do_mounts_rd.c:rd_load_image(),这又会调用init/do_mounts_rd.c:identify_ramdisk_image()以加载initrd。这个函数会检查镜像的魔数(magic number)以确定它的文件系统类型:minux, etc2, romfs, cramfs, or gzip。一旦返回到initrd_load_image,一个新的调用随之开始。init/do_mounts_rd:crd_load()会为RAM disk分配空间,计算CRC(循环冗余校检码),然后解压加载RAM disk镜像到内存。此时,你就成功的加载了initrd镜像了。

魔数 Magic Number —— Lesca 注

一个典型的UNIX文件系统往往由引导块、超级块、空闲空间管理区、i节点区、根目录、数据块和目录块组成。其中的超级块中包含了决定该文件系统类型的魔数[2]

对于文件而言,也有这样的概念。一个典型UNIX文件总是以文件头开始,该结构的第一项记录就是魔数,用于表明该文件的类型(可执行文件、文本文件等)。[2]

总而言之,无论是描述文件系统还是文件,魔数都是用于表示类型的。

为了挂载并创建根目录设备通,就要调用init/do_mounts.c:mount_root(),然后调用init/do_mounts.c:mount_block_root(),以及init/do_mounts.c:do_mount_root(),这个函数又会调用fs/namespace.c:sys_mount()来挂载真正的根文件系统并chdir(改变当前目录到)那里。从这里,你也许找到了与Listing 6中描述的信息的相似之处。

最后,返回到init函数,并调用init/main.c:run_init_process,它会继续调用execve以启动init进程(本例中是/linuxrc)。linuxrc可以是一个可执行文件或者一个脚本(只要脚本解释器支持)。

这里的函数调用体系结构如Listing 7所示。这其中并没有列出所有与加载、调用RAM disk有关的函数,但它能表示一个调用流程的概览

Listing 7. initrd加载过程中的主要函数调用体系结构

               
init/main.c:init
  init/do_mounts.c:prepare_namespace
    init/do_mounts_initrd.c:initrd_load
      init/do_mounts_rd.c:rd_load_image
        init/do_mounts_rd.c:identify_ramdisk_image
        init/do_mounts_rd.c:crd_load
          lib/inflate.c:gunzip
    init/do_mounts.c:mount_root
      init/do_mounts.c:mount_block_root
         init/do_mounts.c:do_mount_root
           fs/namespace.c:sys_mount
  init/main.c:run_init_process
    execve

无盘引导

有点像嵌入式引导的情况,不再需要一个本地磁盘(软盘或者CD-ROM)来引导内核和ramdisk根文件系统。DHCP协议可以鉴别网络参数,例如IP地址和子网掩码。TFTP协议可以传输内核镜像以及ramdisk镜像到本地设备。一旦传输完毕,就可以引导Linux内核并挂载initrd,这就像是在本地完成的。

压缩你的initrd

在嵌入式系统上,你会需要尽可能小的initrd镜像。我们有一些提示可供参考。首先是使用BusyBox(前面提到过),它能将几兆字节的工具压缩到只有几百K字节。
本例中,BusyBox镜像被静态连接,以便不再需要库文件。然而,如果你需要标准C库文件(作为你的自定义二进制文件),与glibc的臃肿相比,我们有其他更好的选择。第一个是uClibc,对于空间有限的系统,它是一个最小化的标准C库版本;另一个库,dietlib,同样适合空间受限的环境。记住:你需要用这些库重新编译要在你嵌入式系统上运行的二进制文件,也就是说你需要一点额外的工作(但这很值得)。

总结

initial RAM disk最初是为将内核引入最终文件系统而产生的。initrd对于在嵌入式系统中的挂载到RAM disk的非永久的根文件系统也很有用。

References:
[1] IBM DeveloperWorks – Linux initial RAM disk (initrd) overview
[2] Andrew S. Tanenbaum – Moden Operating Systems 3rd Edition

版权声明

本文出自 Lesca 技术宅,转载时请注明出处及相应链接。

本文永久链接: https://www.lesca.cn/archives/linux-initial-ram-disk-initrd-overview.html

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

3 Comments
  • 林海草原

    2011-03-04 at 10:31

    我需要慢慢摸索着它。
    这都是你自己研究的?

    1. lesca

      2011-03-06 at 12:34

      我翻译的原文阿。

  • 林海草原

    2011-03-04 at 10:31

    这都是你自己研究来实践来着?