概述
- dracut是制作initramfs的工具。 initramfs(Initial RAM File System,初始内存文件系统)是 Linux 内核启动过程中使用的一个临时根文件系统,它在内存中加载并提供必要的工具、驱动和脚本,用于在挂载真正的根文件系统之前完成关键初始化任务。使用initramfs是为了解决一个先有鸡还是先有蛋的问题:内核不包含所有外存储设备的驱动,这些驱动并没有全都编译到内核(可能编译到模块,也可能没有开启配置)。缺少对应的驱动,内核无法挂载外存储设备,也就无法启动外存储设备中的用户进程(例如systemd)。
- dracut 的 initramfs 生成功能主要由一系列模块化生成器(generator modules) 实现。这些模块由主 dracut 脚本调用,用于将特定功能注入 initramfs。它们位于 /usr/lib/dracut/modules.d 目录中,并利用 dracut-functions 提供的功能完成工作。
- 制作initramfs:dracut --kver <内核版本> -f
- 内核版本对应/lib/modules/目录下的文件夹名,-f表示覆盖原文件,也可直接指定文件名
安装dracut模块
- /usr/lib/dracut/modules.d 中包含多个以 [优先级][模块名] 为命名格式的文件夹,模块安装由模块中的module-setup.sh脚本决定
- 制作initramfs过程中,dracut会解析模块的module-setup.sh中的函数:
- check()
- 评估是否将某个 dracut 模块包含在 initramfs 中
- 返回值:0包含、1不包含、255仅当其他模块需要该模块,或在配置文件或参数列表中明确指定时,才包含
- depend()
- 该函数应输出(echo)该模块依赖的所有其他 dracut 模块名称
- cmdline()
- 该函数应打印启动当前机器设置所需的内核命令行选项。应以空格开头且不应打印换行符
- install()
- 用来安装所有非内核相关的内容。要安装二进制文件、脚本和其他文件
- 变量$moddir代表当前模块的目录
- 不同的待安装文件可以用不同的预定义函数:
- inst_multiple [-o] <文件> [<文件>...]
安装多个二进制文件和普通文件。若可执行文件未指定路径,dracut将在PATH=/usr/sbin:/sbin:/usr/bin:/bin路径中搜索。若第一个参数为"-o"选项,则缺失文件不会报错。 - inst <源文件> [<目标路径>]
安装单个文件<源文件>到initramfs中相同位置或可选的<目标路径>。若inst接收超过两个参数,则按inst_multiple方式处理,所有参数均视为待安装文件而非安装目标。 - inst_hook <钩子目录> <优先级> <源文件>
在dracut的<钩子目录>中以<优先级>安装可执行文件/脚本<源文件>。优先级是数字,数值越小余额优先,钩子目录对应dracut启动过程的每个阶段,例如cmdline、pre-pivot - inst_rules <udev规则> [<udev规则>...]
安装一个或多个udev规则。不存在的udev规则会被报告但不会导致dracut失败。
udev规则位于/usr/lib/udev/rules.d - instmods <内核模块> [<内核模块>...]
instmods应仅用于installkernel()函数。安装一个或多个内核模块到initramfs中。<内核模块>也可用"="前缀表示整个子系统,如"=drivers/net/team"。模块位于/usr/lib/modules/$(uname -r)/
- inst_multiple [-o] <文件> [<文件>...]
- installkernel()
安装所有与内核相关的文件。同样可以使用上述函数
- check()
- dracut模块的安装顺序由两方面决定:
- 模块文件夹前面的优先级序号,越小越优先
- 模块之间的依赖关系,依赖会优先安装。例如30module_a模块依赖40module_b,那么当安装modual_a时,会根据依赖关系先将module_b安装上
dracut启动流程
- initramfs中,systemd启动过程,以及dracut钩子的执行顺序如下。钩子被标注为斜体。
- cmdline:此阶段用于解析内核命令行参数并准备后续操作,如设置udev规则和配置文件。该阶段会定义两个关键环境变量:
- root:指定根文件系统设备
- rootok:标识是否有模块能处理指定的root参数(如iscsi模块会处理root=iscsi:...参数并设置rootok)
- pre-udev:此时root和rootok已设置但udev尚未运行。模块可以在此阶段对最终确定的root设备进行操作。
- cmdline:此阶段用于解析内核命令行参数并准备后续操作,如设置udev规则和配置文件。该阶段会定义两个关键环境变量:
- 启动udev:初始化udev服务并设置日志记录
- pre-trigger:通过udevadm trigger触发所有设备和子系统的add事件
- 主循环:dracut会循环执行直到udev事件处理完成并且initqueue/finished中所有脚本返回true。主循环中包含5个可插入脚本的钩子点,通过在启动阶段执行的脚本中调用/sbin/initqueue可执行文件来注册主循环中的钩子(例如/sbin/initqueue --settled --unique --onetime /sbin/insmodpost.sh):
- initqueue:每次通过/sbin/initqueue注册initqueue钩子时,都会立即执行该脚本,无论udev状态如何initqueue/settled:每次udev稳定时执行该钩子脚本,udev稳定是指所有的udev事件都处理完毕,udevadm settle指令便是静默等待所有udev事件处理完毕,执行echo "$?"回显为0,则说明udev settled(稳定)
- initqueue/timeout:当主循环计数器达到rd.retry一半值时执行该钩子脚本
- initqueue/online:当网络接口就绪(启动并完成配置)时执行
- initqueue/finished:在udev稳定后执行,若所有脚本返回0则结束主循环。模块可在此插入等待特定条件的脚本。
- 主循环结束
- pre-mount:在挂载根设备前执行所有脚本
- mount:主要用于挂载真正的根设备
- pre-pivot:在清理阶段之前执行,适合放置非清理类但需要在切换根目录前完成的操作
- cleanup:最终阶段,在切换到真实根设备前执行最后的清理工作,终止不必要的进程
- 清理并切换根目录
- init(或systemd)会终止所有udev进程、清理运行环境、为真正的init进程配置参数,最终调用switch_root。switch_root会执行以下操作:
- 彻底移除initramfs的整个文件系统层级(需要满足所有源自initramfs的进程都不应保留任何打开的文件描述符)
- 通过chroot()切换到真实的根设备
- 使用指定参数调用/sbin/init
- init(或systemd)会终止所有udev进程、清理运行环境、为真正的init进程配置参数,最终调用switch_root。switch_root会执行以下操作:
- dracut钩子脚本中可以使用在dracut-utils dracut-lib 中的函数,如用warn替代echo,用echo需要重定向到/dev/console才会在开机界面打印,重定向到/dev/kmsg才会出现在dmesg、journalctl中
- 内核参数rd.break的作用便是使系统停止在initramfs中pre-pivot钩子函数执行之前。该参数还可以指定停止的阶段,例如rd.break=pre-mount,将会使系统停止在挂载根文件系统的前一刻,当系统无法启动,并且提示根文件系统损坏时,可添加此参数,在根文件系统挂载之前尝试手动对其进行修复。
示例:
[root@localhost 95my-dracut-module]# cat /usr/lib/dracut/modules.d/95my-dracut-module/exec\_echo.sh
#!/bin/bash
// do what you need to do
for ((row=1; row<=5; row++)); do
for ((col=1; col<=5; col++)); do
echo -n "."
sleep 0.01
done
echo ""
done
[root@localhost 95my-dracut-module]# cat /usr/lib/dracut/modules.d/95my-dracut-module/module-setup.sh
#!/bin/bash
# called by dracut
check() {
return 0
}
# called by dracut
depends() {
return 0
}
# called by dracut
install() {
inst\_hook pre-mount 90 "\$moddir/exec\_echo.sh"
}
附录:initramfs中使用systemd的执行流程