我试图了解 linux 内核如何知道所需的 rootfs 在启动时的位置。
我阅读了这份文件:
https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
兴趣的一部分说:
所有 2.6 Linux 内核都包含一个 gzip 压缩的“cpio”格式存档,当内核启动时,它会被提取到 rootfs...用于定位和挂载根分区的旧代码
我们的内核是 4.X,但我猜这仍然适用?这听起来像所有内核都有一个嵌入的“cpio”rootfs。
事实上,正如我们读到的那样:
2.6 内核构建过程始终创建一个 gzip 压缩的 cpio 格式 initramfs 存档并将其链接到生成的内核二进制文件中。默认情况下,此存档为空... 配置选项 CONFIG_INITRAMFS_SOURCE ... 可用于指定 initramfs 存档的源
这又提出了几个问题:
- 因此,如果我希望我的 rootfs 在 RAM 中,我需要设置
CONFIG_INITRAMFS_SOURCE
为指向我的 rootfs(大概是 cpio 格式)。
但这不是意味着我的内核和 rootfs 现在是不可分割的吗?如果我想在不重建的情况下对 RootFS 做一些小调整怎么办?如果我希望我的 rootfs 与内核分开存储怎么办?如何告诉内核我的 rootfs 的位置?
- 此外,如果我希望我的 rootfs 位于物理存储(如 eMMC、闪存驱动器等)而不是 RAM 中怎么办?
前面说过:
如果在嵌入的 cpio 归档文件被提取到 rootfs 后不包含 init 程序,内核将使用旧代码来定位和挂载根分区
但是……怎么办?它如何知道 rootfs 的位置?如果它在 eMMC 上,我需要以某种方式告诉内核,对吗?
我使用的引导加载程序是 U-boot。我检查了 U-boot 环境变量,看看它是否以某种方式将 rootfs 位置作为引导参数传递给内核,但似乎并非如此......
编辑:
正如评论中所指出的,rootfs 的位置通过 boot arg 传递给内核。就我而言,u-bootroot=/dev/mmcblk0p4 rw
作为引导参数传递给内核。所以这回答了我的一个问题 - 您可以将该位置作为引导参数传递给任何解压缩的 rootfs。
我仍然不清楚如何,考虑到一些rootfs.tar.gz
与内核分开的内容,如何告诉内核将其解压到 RAM 中并将其用作 rootfs。也许这是不可能的,我只需要使用CONFIG_INITRAMFS_SOURCE
?无论如何,我会阅读 4.X 文档。
首先,不要被内核文档中对“2.6”的引用吓到。当前的内核仍然是“2.6”系列的成员,但它们经历了两轮重新编号,只是为了“营销目的”(所以 2.6.40 变成了 3.0,3.20 然后变成了 4.0)。您的 4.19 内核通常标记为 2.6.79。
看起来这里对“rootfs”的含义有些混淆。“rootfs”是内核内部使用的一个特殊的基于 RAM 的文件系统。它与通常安装在
/run
,/dev/shm
或有时/tmp
. (好吧,除非“tmpfs”功能没有编译到内核中,在这种情况下,将使用称为“ramfs”的精简“tmpfs”。)这些文件系统只存在于页面缓存中,根本没有支持设备ramfs,而 tmpfs 由 swap 支持(如果可用)。因此内核不需要担心“找到”“rootfs”,但它需要以某种方式填充它,因为整个页面缓存在启动时是空的。这就是“initramfs 文件”发挥作用的地方。这只是一个(压缩的)
cpio
存档(不是tar
出于文档中提到的原因),它被内核解压缩到空的“rootfs”中。该存档可以通过CONFIG_INITRAMFS_SOURCE
在构建期间设置直接嵌入到内核映像中,也可以由引导加载程序提供(这就是initrd
GRUB 中的选项所做的)。这个档案通常是使用用户空间工具创建的,比如dracut
or (confusingly)mkinitrd
。如果 cpio 映像不可用或不包含可执行文件
/init
,内核将退回到另一种方法,在该方法中它查看root=
命令行参数,将其解释为真正的根文件系统的位置,将其挂载/
并直接执行init
。但是,现在很少使用它,因为它需要将所有必要的存储和文件系统驱动程序直接编译到内核中。在大多数系统中root=
,内核不使用该参数,而是由/init
initramfs 处理。它/init
(通常是systemd
shell 脚本)负责加载所需的模块、安装真正的根文件系统并切换到它。很久以前,使用了一种不同的机制,称为“initrd”,而不是现代的“initramfs”。“initrd”(“init ramdisk”)是一个基于 RAM 的块设备,它使用真实的文件系统(例如 ext2)进行初始化,其映像就像现代
cpio
存档一样提供。这就是为什么许多地方仍然使用“initrd”名称来指代这些早期启动的东西。这是一种方法,是的,但它不是唯一的方法。
CONFIG_INITRAMFS_SOURCE
如果您有一个可以配置为将内核和 initramfs 作为单独文件加载的引导加载程序,则在构建内核时不需要使用。CONFIG_BLK_DEV_INITRD
在内核配置中设置就足够了。(在 initramfs 之前,有一个旧版本的技术initrd
名为已加载内核映像的特定位置。内核具有内置例程,这些例程将使用该信息在系统 RAM 中查找 initramfs 并将其解压缩。将 initramfs 作为一个单独的文件将允许您更轻松地修改 initramfs 文件,并且如果您的引导加载程序可以接受来自用户的输入,也许可以在引导时指定另一个要加载的 initramfs 文件而不是常规文件。(如果您尝试创建自定义的 initramfs 并出现一些错误,那将非常方便。去过那里,做到了。)
对于传统的基于 BIOS 的 x86 系统,您可以在(kernel source)/Documentation/x86/boot.txt中找到有关这些详细信息的信息。基于 UEFI 的系统的做法稍有不同(也在同一个文件中进行了描述),而其他架构(如 ARM)有自己的一组关于将信息从引导加载程序传递到内核的详细信息。
在常规的非嵌入式系统中,initramfs 通常只包含足够的功能来激活必要的子系统。在普通 PC 中,这些通常是键盘驱动程序、显示器驱动程序和根文件系统的存储控制器驱动程序,以及激活 LVM、磁盘加密和/或软件 RAID 等子系统所需的任何内核模块和工具,如果您使用这些功能。
一旦基本的子系统处于活动状态并且根文件系统可以访问,initramfs 通常会执行
pivot_root(8)
从 initramfs 切换到真正的根文件系统的操作。但是嵌入式系统或像DBAN这样的专用实用程序可以将所需的所有内容打包到 initramfs 中,并且永远不会执行pivot_root
操作。通常,initramfs 中的脚本和/或工具会从内核命令行的选项中获取必要的信息来定位真正的根文件系统。但是您不必这样做:使用自定义的 initramfs,如果在引导序列中的特定时间按住特定键或鼠标按钮,您可以执行诸如切换到不同根文件系统之类的操作。
对于复杂的存储配置(例如,软件 RAID 之上的加密 LVM,在使用冗余多路径 SAN 存储的系统上),激活根文件系统所需的所有信息可能不适合内核命令行,因此您可以包括更大的部分进入 initramfs。
现代发行版通常使用initramfs 生成器为每个已安装的内核构建定制的 initramfs。不同的发行版曾经有自己的 initramfs 生成器:RedHat 使用
mkinitrd
,而 Debian 有update-initramfs
. 但是在引入systemd
它之后,看起来许多发行版都标准化dracut
为 initramfs 生成器。现代的 initramfs 文件可以是多个
.cpio
档案的串联,每个部分可能会或可能不会被压缩。现代 x86_64 系统上的典型 initramfs 文件可能具有“早期微码更新”文件作为第一个组件(通常只是未压缩 cpio 存档中的单个文件,因为微码文件通常是加密的,因此不太可压缩。之后是常规 initramfs 内容,作为压缩.cpio
文件。为了更深入地了解您的系统,我建议您将一个 initramfs 文件提取到一个临时目录,然后检查其内容。在 Debian 上,有一个
unmkinitramfs(8)
工具可用于以简单的方式提取 initramfs 文件。在 RedHat 7 上,您可能需要使用/usr/lib/dracut/skipcpio <initramfs file>
跳过微码更新文件,然后将生成的输出通过管道传输到gzcat
并继续以cpio -i -d
将 initramfs 内容提取到当前工作目录。Ubuntu 可能会使用lzcat
.gzcat