initramfs 在内核中的作用与实现

0x10 initramfs 简介

Linux 允许将一部分内存作为块设备 (RAM block device support)。这通常见于完全运行于内存上的 Linux 的 live 发行版。Linux 的 live 发行版会卸载光盘并接着加载到内存中,所以在尝试一个新的操作系统或者修复另一个系统时不会伤害到已安装的系统。

initrd 是一个内存中的磁盘结构(ramdisk),其中包含必要的工具和脚本,用于在将控制权交给根文件系统上的 init 应用程序之前挂载所需的文件系统。Linux 内核在此根磁盘上触发安装脚本(通常称为linuxrc,但该名称不是必需的),此脚本的工作是准备系统,切换到真正的根文件系统,然后调用 init。

initrd 有一些缺点

  • 是一个完整的块设备,需要整个文件系统的开销,它有一个固定的大小。选择一个 initrd 是太小了,所需要的脚本不适用。让它过大的话,就会浪费内存。
  • 因为它是一个真实的,静态的设备,它消耗 Linux 内核中的缓存内存和易于使用的文件(如分页),这使得 initrd 有更多的内存消耗。

正因如此,出现了 initramfs,作为 initrd 的实现,或者说是替代品,能够更好的解决 initrd 存在的弊端,并且实现相应的功能。

0x11 initramfs 是什么

initramfs 即 initram file system,翻译成中文意思就是 初始 ram 文件系统,基于 tmpfs,是一种大小灵活,直接作用在内存中的文件系统。initramfs 包含的工具和脚本,在正式的根文件系统的初始化脚本 init 启动之前,就被挂载。initramfs 是可选的,内核编译选项默认开启 initramfs(initrd)。那么什么情况下考虑使用 initramfs 呢?

  • 加载模块,如三方驱动
  • 定制化启动系统
  • 制作一个很小的 rescue shell
  • 内核不能,但是用户态可以完成的命令

initramfs 在内核启动的早期提供用一个户态环境,用于完成在内核启动阶段不易完成的工作。initramfs 包含的工具可以解密抽象层(用于加密的文件系统),逻辑卷管理器,软件 RAID,蓝牙驱动程序等。

一个 initramfs 至少包含一个文件,即 /init,内核将这个文件执行起来的进程设置为 main init 进程,pid = 1。内核挂载 initramfs 时,文件系统的根分区并没有挂载,所以无法访问文件系统中的文件。大多数嵌入式设备可能需要一个 shell,那么也会在 initramfs 打包进一个 shell。如果还需要其他工具或者脚本,也可以打包进 initramfs,但注意,必须包含依赖,因为 initramfs 是一个能够独立运行的 ram 文件系统

0x12 initramfs 如何工作

initramfs 和我们常见的文件系统类似,可能存在 /usr、/bin 等目录。里面包含着我们的工具和脚本。initramfs 需要使用 cpio 来归档,cpio 是一个有着古老历史的文件归档解决方案,类似于 linux 中常用的 tar,或者 windows 中的 zip,主要作用是将多个文件打包成一个文件(但是没有压缩)。使用 cpio,是因为其代码易于实现,而且能够兼容更多的设备。

归档之后,需要考虑 initramfs 文件的体积,要进一步压缩,减少内存或磁盘的占用。所有文件,工具,库,配置设置(如果适用)等都放入 cpio 归档后,使用 gzip 实用程序压缩 cipo 文件,并将其与linux 内核一起存储。引导加载程序(通常是 grub 或者 uboot)将在引导时将其提供给内核,以便内核知道需要一个 initramfs。

内核一旦检测到 initramfs,会创建一个 tmpfs 文件系统,提取 gzip 中存档的 initramfs,并存入 tmpfs 中,内核启动 tmpfs 文件系统中的 init 脚本。该脚本用于挂载实际的根文件系统,当完成根文件系统和其他的一些重要的文件系统的安装之后,init 脚本会切换至真实的根文件系统,并在系统上调用 / sbin / init 二进制文件,进行后续的启动过程。

0x20 initramfs 实际应用

为了更好的了解 initramfs 的概念与实现,我们使用 qemu 来进行一次实际的 initramfs 制作与启动。如果你对 qemu 启动内核还不太了解,可以参考:为 QEMU ARM 仿真器编译 Linux 内核:QEMU 模拟 ARM 环境

0x21 配置与编译内核

下载交叉编译器

sudo apt-get install gcc-arm-linux-gnueabi

自主选择某一版本的内核,下载后进行配置,配置完成再进行编译,以下是两个内核源码的镜像站点,可以加快源码的下载速度

https://mirrors.edge.kernel.org/pub/linux/kernel/
http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/

编译内核,需要注意的是,最后的编译命令是需要 root 权限的,并且编译内核可能需要一些依赖

# 内核配置
make distclean
make ARCH=arm vexpress_defconfig
make ARCH=arm menuconfig # 内核配置如下面的截图
# 以下是正式编译
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all

因为编译内核默认使用单核,为了加快编译速度,可在编译命令上加上参数 -j,用于多核加速

sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all -j 4        # 4核

0x22 有关 initramfs 的内核编译配置

选项一(内核默认开启)

General setup 中的 Initial 用于对 initramfs 的支持。初始 RAM 的文件和 RAM 磁盘( initramfs /initrd)支持(如果要采用 initrd 启动则要选择,否则可以不选),对于默认的 Linux 内核配置,该选项默认开启
在这里插入图片描述

选项二(vexpress 未开启)

Linux 允许将一部分内存作为块设备 (RAM block device support),即 ramdisk。对于 vexpress 开发板的默认配置,是没有开启 ramdisk 选项的,也就是说,如果使用 vexpress_defonfig 配置,块设备中,只有 mtdblock,没有 ram,而 initramfs 是需要加载到 ram 的块设备中,因此,在内核的图形化配置菜单中,选择 Device Drivers -> Blocks devices -> RAM block device support
在这里插入图片描述

大家可能对上述两个配置的区别还不是很有体会,看完整个例子相信你会有更深的认识。

0x23 制作 initramfs

像所有编程语言的牙牙学语的阶段一样,为了简便起见,我们只编写一个简单的 C 程序

/* hello.c */
#include <stdio.h>

void main()
{
    
    
    printf("Hello World\n");
    fflush(stdout);
    /* 让程序打印完后继续维持在用户态 */
    while(1);
}

编译程序,因为我们在 0x21 节使用交叉编译器来编译内核,即该内核支持 arm32 平台的程序,因此在这里,也需要使用交叉编译器编译该 C 代码,为了避免还要拷贝库文件,这里使用静态编译

arm-linux-gnueabi-gcc -static hello.c -o hello

查看文件属性,检查是否生成目标平台程序

lys@kali:~/Documents/test$ file hello
hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, BuildID[sha1]=997821ab3ea778fd33cfea6dae5c36f5ab3c6104, for GNU/Linux 3.2.0, not stripped

使用 cipo 归档程序

echo hello| cpio -o --format=newc > helloinitramfs

0x24 qemu 启动 initramfs

使用 qemu 启动编译好的内核,并且指定 initrd 为生成的 initramfs,rdinit 指名要启动的脚本

qemu-system-arm -M vexpress-a9 -m 512M -kernel linux-5.6.6/arch/arm/boot/zImage \
-dtb linux-5.6.6/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-append "rdinit=hello console=ttyAMA0" \
-initrd test/helloinitramfs -serial stdio

成功启动
在这里插入图片描述
这个时候,我们再回过头来看看 0x22 节的两个内核配置选项

  • 选项一是内核默认开启的,Initial Ram filesystem and Ramdisk support 选项就是用来支持 initramfs / initrd 的,如果关闭此选项,则不能使用 -initrd 参数启动内核;
  • 选项二对于不同的开发板,默认会有不同的设置,RAM block device support,如果关闭,则不能在 -append 参数中,添加 root=/dev/ram,即将 initramfs 挂载到 /dev/ram 中启动

使用 initrd 的内核配置参数:initrd=addr root=/dev/ram **而 /dev/ram 这个设备(ramdisk 的设备节点)**是需要开启内核的选项二配置

0x30 总结

initramfs 作为根文件系统一个重要补充,在特定条件下弥补了内核启动时,没有用户态的缺陷。很多 Linux 发行版都使用了 initramfs,当然也有一些并没有使用,该 ram 文件系统并非必须,但在很多场景下又不可或缺。例如 Kali Linux 系统,就使用了 initramfs
在这里插入图片描述
Debian 系的 Linux 发行版,initrd/initramfs 映象文件一般在 /boot 目录下,initramfs 与根分区文件系统的雏形很像,只是它的大小不大,少了很多工具和库。

本文首先讲述了 initramfs 的由来和工作过程,然后举了一个实际的例子,以将抽象的过程具体化,最后使用 qemu 启动了我们制作的 initramfs,相信通过此文,你一定能够对 initramfs 有一个更加清晰的认识。

猜你喜欢

转载自blog.csdn.net/song_lee/article/details/106027410