initrd和initramfs实操
RAMFS 和 RAMDISK 都是内存文件系统,他们有着小巧快速的特点。INIT前缀表示其包含有效“init”可执行文件,可以作为启动的root文件系统。INITRAMDISK出现较早,在使用nor flash和2.4kernel盛行的时期很流行。RAMFS出现也很早,但INITRAMFS是2.6版本kernel添加的新功能,INITRAMFS更象是INITRAMDISK的一个简化版本。如今嵌入式设备大量使用nand flash,像INITRAMFS和INITRAMDISK已经不适合用来管理nand这种大容量并且需要校验机制的存储设备。
在 boot loader 配置了 initrd 在这情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的"某个文件",完成加载驱动模块等任务,第二阶段才会执行真正的文件系统中的任务 /sbin/init 进程。
initrd
linux 发行版必须适应各种不同的硬件架构,将所有的驱动编译进内核是不现实的,根文件系统可能保存到各种存储设备上包括scsi、sata,u-disk等等,但是又必须先加载他们的驱动才能加载根文件系统。
为了解决这一矛盾,于是出现了基于ramdisk的initrd( bootloader initialized RAM disk )。Initrd是一个被压缩过的小型根目录,这个目录中包含了启动阶段中必须的驱动模块,可执行文件和启动脚本。当系统启动的时候,bootloader会把initrd文件读到内存中,然后把initrd文件在内存中的起始地址和大小传递给内核。内核在启动初始化过程中会解压缩initrd文件,然后将解压后的initrd挂载为根目录,然后执行根目录中的/linuxrc脚本。
initrd 的英文含义是 boot loader iniTIalized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的文件系统前先访问该内存中的 initrd 文件系统。
initramfs
在linux2.5中出现了initramfs,它的作用和initrd类似,只是和内核编译成一个文件(该initramfs是经过gzip压缩后的cpio格式的数据文件),该cpio格式的文件被链接进了内核中特殊的数据段.init.ramfs上,其中全局变量__initramfs_start
和 __initramfs_end
分别指向这个数据段的起始地址和结束地址。内核启动时会对.init.ramfs段中的数据进行解压,然后使用它作为临时的根文件系统。
实操
initrd
Linux内核配置:
选中RAM disk支持,注意一定要变成星号,并且将大小调整为64M:
Device Drivers > Block devices
<*> RAM block device support
(16) Default number of RAM disks (NEW)
(65536) Default RAM disk size (kbytes)
initrd制作脚本:
#!/bin/bash
MOUNT_DIR=mnt
CURR_DIR=`pwd`
rm initrd.ext4
dd if=/dev/zero of=initrd.ext4 bs=1M count=64
mkfs.ext4 initrd.ext4
mkdir -p $MOUNT_DIR
mount initrd.ext4 $MOUNT_DIR
cp -arf busybox-1.35.0/_install/* $MOUNT_DIR
cd $MOUNT_DIR
mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/
echo "proc /proc proc defaults 0 0" > etc/fstab
echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab
echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -a" >> etc/init.d/rcS
echo "mount -o remount,rw /" >> etc/init.d/rcS
echo "echo -e \"Welcome to ARM64 Linux\"" >> etc/init.d/rcS
chmod 755 etc/init.d/rcS
echo "::sysinit:/etc/init.d/rcS" > etc/inittab
echo "::respawn:-/bin/sh" >> etc/inittab
echo "::askfirst:-/bin/sh" >> etc/inittab
chmod 755 etc/inittab
cd dev
mknod console c 5 1
mknod null c 1 3
mknod tty1 c 4 1
cd $CURR_DIR
umount $MOUNT_DIR
echo "make initrd ok!"
制作完成后产生文件
-rw-r--r-- 1 root root 64M 6月 13 19:27 initrd.ext4
QEMU配置:
#!/bin/bash
source/qemu-7.2.0-rc1/build/aarch64-softmmu/qemu-system-aarch64 \
-nographic \
-M virt \
-cpu cortex-a57 \
-smp 2 \
-m 1G \
-kernel source/linux-6.0.9/build/arch/arm64/boot/Image \
-append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
-initrd source/initrd.ext4
启动即可看到kernel加载initrd的过程。
我们再执行一个操作gzip initrd.ext4
,然后QEMU配置也改成加载-initrd source/initrd.ext4.gz
,也可以支持gzip格式的
initramfs
制作initramfs文件系统
Linux内核只认cpio格式的initramfs文件包(因为unpack_to_rootfs只能解析cpio格式文件),非cpio格式的 initramfs文件包将被系统抛弃,而initrd可以是cpio包也可以是传统的镜像(image)文件,实际使用中initrd都是传统镜像文件。
initramfs可以独立ram disk单独存在,而要支持initrd必须要先支持ram disk
和initrd不同,第一个运行的是init文件,这个文件可以自己创建,也可以直接使用busybox
#!/bin/bash
MOUNT_DIR=initramfs_root
CURR_DIR=`pwd`
rm -rf $MOUNT_DIR initramfs.img.gz
mkdir -p $MOUNT_DIR
cp -arf busybox-1.35.0/_install/* $MOUNT_DIR
cd $MOUNT_DIR
mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/
echo "proc /proc proc defaults 0 0" > etc/fstab
echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab
echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -a" >> etc/init.d/rcS
echo "mount -o remount,rw /" >> etc/init.d/rcS
echo "echo -e \"Welcome to ARM64 Linux\"" >> etc/init.d/rcS
chmod 755 etc/init.d/rcS
echo "::sysinit:/etc/init.d/rcS" > etc/inittab
echo "::respawn:-/bin/sh" >> etc/inittab
echo "::askfirst:-/bin/sh" >> etc/inittab
chmod 755 etc/inittab
ln -s bin/busybox init
cd dev
mknod console c 5 1
mknod null c 1 3
mknod tty1 c 4 1
cd $CURR_DIR
find $MOUNT_DIR/ | cpio -c -o > initramfs.img
gzip initramfs.img
echo "make initramfs ok!"
由于initramfs使用cpio包格式,所以很容易将一个单一的文件、目录、node编译链接到系统中去,这样很简单的系统中使用起来很方便,不需要另外挂接文件系统。
但是因为cpio包实际是文件、目录、节点的描述语言包,为了描述一个文件、目录、节点,要增加很多额外的描述文字开销,特别是对于目录和节点,本身很小额外添加的描述文字却很多,这样使得cpio包比相应的image文件大很多。
Linux内核配置:
General setup
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(../source/initramfs_root) Initramfs source file(s)
路径输入上述文件系统根目录,这样内核编译时就会和内核编译成同一个文件。
我们使用cpio工具单独打包出来,可以观察跟文件系统的大小,如果不在内核配置这个选项,也可以使用cpio生成的镜像和内核产物再次打包,有利于路径解耦。
QEMU配置:
#!/bin/bash
source/qemu-7.2.0-rc1/build/aarch64-softmmu/qemu-system-aarch64 \
-nographic \
-M virt \
-cpu cortex-a57 \
-smp 2 \
-m 1G \
-kernel source/linux-6.0.9/build/arch/arm64/boot/Image \
-append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
可以看到不需要再制定initrd了,内核和initramfs绑定在一起有利于分区管理
总结
本篇介绍了initrd和initramfs的使用方法和差异点,其实他们的差异点远不止这些,如果写入真实flash还有启动命令等的差异。
后面可以分析下源码是如何区别的,启动流程的差异