嵌入式Linux中initrd的应用--浅析ramdisk、ramfs、initrd和initram

2019-07-13 08:10发布

在Essential Linux.Device Drivers里看到这样一句话:
在嵌入式系统开发过程中, initrd 和 initramfs 有时候也可被用作嵌入式设备上实际的根文件系统。 看到这样的一句话很让我费解,因为似乎我理解为这样做能够提高一点嵌入式linux启动速度,我对此是非常地感兴趣。
自此,就开始了我的解惑之旅。 首先需要知道的是ramdisk和ramfs
http://www.linuxfordevices.com/c/a/Linux-For-Devices-Articles/Introducing-initramfs-a-new-model-for-initial-RAM-disks/
或者内核源码/Documentation/filesystems/ramfs-rootfs-initramfs.txt。
从上述文章,大致可以了解到 ramdisk是一种基于ram的块设备,ramfs是一种基于ram的文件系统,开发ramfs的目的是因为ramdisk浪费了太多的内存cache页,ramfs是基于tmpfs的一个实例。

顺理成章,initrd是init ramdisk的缩写,initramfs是init ramfs的缩写。
名称里加了init前缀,代表它们具有了引导内核启动的功能。
ramfs比ramdisk更加高效,自然initramfs也更加优秀,是2.6内核新加入的推荐使用的机制,虽然可能pc中grub使用的内核cmd line参数是kernel /vmlinuz-2.6.25-14.fc9.i686 ro root=UUID=11d7ac51-2b45-489e-8a48-8d2a28e2c04e rhgb quiet
initrd /initrd-2.6.25-14.fc9.i686.img ,实际上使用的是initramfs,可以靠文件格式来区分。initrd是gziped的,initramfs是cpio的。

剩下的部分就只说initrd了。
首先是initrd的作用:内核镜像要尽可能小,所以不应该静态包含进太多驱动模块。但是Linux内核启动最后一步,创建init内核线程,需要执行的init程序或者脚本并不在内核镜像中,而在根文件系统中。根文件系统可能在硬盘、磁盘阵列、nfs、flash上,同时根文件系统的格式也是五花八门。此时,实在不想包含这么多驱动到镜像的话,可以使用initrd作为一个过渡。
initrd本身的内容就是一个精简的根文件系统,包含必备的目录和程序,甚至也有一个init脚本或程序linuxrc。
一切的一切,因为linux的高可定制性,一切随你便。在pc上通常是后者。 以pc上的为例,首先拷贝一个initrd-2.6.25-14.fc9.i686.img,防止破坏了它开不了机。
然后将其备份改名为gz,解压缩。 [root@localhost boot]# mkdir /tmp/initrd
[root@localhost boot]# cp initrd-2.6.25-14.fc9.i686.img /tmp/initrd/
[root@localhost boot]# cd /tmp/initrd/
[root@localhost initrd]# mv initrd-2.6.25-14.fc9.i686.img initrd-2.6.25-14.fc9.i686.gz
[root@localhost initrd]# gunzip initrd-2.6.25-14.fc9.i686.gz
[root@localhost initrd]# ls
initrd-2.6.25-14.fc9.i686
[root@localhost initrd]# file initrd-2.6.25-14.fc9.i686
initrd-2.6.25-14.fc9.i686: ASCII cpio archive (SVR4 with no CRC)

继续解压缩,查看其根目录下的init脚本,其重要的地方都用加粗了。 #!/bin/nash mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd module"
modprobe -q ehci-hcd
echo "Loading ohci-hcd module"
modprobe -q ohci-hcd
echo "Loading uhci-hcd module"
modprobe -q uhci-hcd
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading ext3 module"
modprobe -q ext3
echo "Loading scsi_mod module"
modprobe -q scsi_mod
echo "Loading sd_mod module"
modprobe -q sd_mod
echo "Loading libata module"
modprobe -q libata
echo "Loading ata_generic module"
modprobe -q ata_generic
echo "Loading pata_acpi module"
modprobe -q pata_acpi
echo Waiting for driver initialization.
stabilized --hash --interval 250 /proc/scsi/scsi
echo "Loading ata_piix module"
modprobe -q ata_piix
echo Waiting for driver initialization.
stabilized --hash --interval 250 /proc/scsi/scsi
echo "Loading dm-mod module"
modprobe -q dm-mod
echo "Loading dm-mirror module"
modprobe -q dm-mirror
echo "Loading dm-zero module"
modprobe -q dm-zero
echo "Loading dm-snapshot module"
modprobe -q dm-snapshot
echo Making device-mapper control node
mkdmnod
modprobe scsi_wait_scan
rmmod scsi_wait_scan
mkblkdevs
echo Scanning logical volumes
lvm vgscan --ignorelockingfailure
echo Activating logical volumes
lvm vgchange -ay --ignorelockingfailure VolGroup00
resume /dev/VolGroup00/LogVol01
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/VolGroup00/LogVol00
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
loadpolicy
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1

首先,这里使用的是nash,不是bash、csh等常见shell。nash是专门为了init设计的,因为体积小。
pc机上的initrd不过只是一个过渡,所以在加载完了硬盘、ext3等驱动后,就迫不及待地要重新设置根文件系统了。
首先mkrootdev指定最终的根目录,参数跟grub的内核启动参数一致。接着mount /sysroot将最终的根目录先挂在到/sysroot ,setuproot开始将initrd中的/proc /dev /sys 中的资料转移到 /sysroot ,switchroot会开始转换/sysroot 为最终的根文件系统,完成后顺便将initrd之前在ram中的一切清空。
(如果有/initrd 目录的话就会把initrd挂载到该目录下)

好了,扯了半天,终于能解释最开始的疑惑了。
initrd,或者initramfs,无论在pc还是嵌入式,都是可以选择的,分3种情况。
1 完全不要initrd。
2 initrd作为最终的根文件系统。
3 initrd作为过渡,由initrd的init来加载最终的根文件系统。

情况1,比如嵌入式linux静态包含了nand flash驱动和jffs2驱动,指定内核启动参数。
root=/dev/mtdblock2 rootfs=jffs2 rw console=ttySAC0,115200 init=/linuxrc
(使用busybox作为根文件系统)

情况2,将根文件系统做成ramdisk镜像,使用ubbot下载到0x30800000,内核启动参数。
root=/dev/ram rw init=/linuxrc initrd=0x30800000,8M console=ttySAC0,115200
注意,一旦使用了ramdisk作为内核命令行参数root的参数,root=/dev/ram,那么就直接把 initrd当做最终的根文件系统。

情况3,pc常见,嵌入式linux也可见这样的启动参数。
console=ttySAC0,115200 root=nfs nfsroot=192.168.1.9:/source/rootfs initrd=0x10800000,0x14af47
也属于情况3。 对于arm平台,如果bootloader不支持装入initrd的话,可以使用bootpImage,这种复合镜像会把内核和initrd直接连接到一起。

现在看来,initrd作为最终的根文件系统,将花费时间将initrd从flash读入ram,同时对文件系统的读写不会写入falsh。
完全不要initrd,根文件系统一直存在于flash,如果根文件系统可写,也将写入flash。