深入 initrd

大家好!我是大聪明-PLUS

今天我想谈谈一些基本问题:

  • 将操作系统加载到自动化工作站 (AWP) 的过程,
  • 解包初始 RAM 映像,
  • 对 initrd 的详细分析,它是什么以及如何使用它。

本资料将帮助您了解 Astra Linux 特别版操作系统的加载特性,以及 GNU/Linux 的一般加载特性。

1. initrd是什么?

Linux 初始启动 RAM 磁盘 (initrd) 是一个块设备,在操作系统中以镜像形式存在,在系统启动过程中被挂载到 RAM 中,以支持两阶段启动模型。initrd 由各种可执行文件和驱动程序组成,用于挂载实际的根文件系统。挂载完成后,initrd 会被卸载,内存也会被释放。

我们来确定 initrd 是如何加载到系统中的。为此,请运行以下命令:

复制代码
`sudo dmesg | grep -i 'initramfs'
[0.322851] Trying to unpack rootfs image as initramfs...
[0.651110] Freeing initrd memory: 75824K
[2.320381] systemd[1]: systemd-fsck-root.service - File System Check on Root Device was skipped because of an unmet condition check (ConditionPathExists=!/run/initramfs/fsck-root).`

从第一行消息可以看出,在系统启动阶段,内核尝试解包根文件系统映像(如 initramfs),以初始化文件系统。

第二条消息指的是 initrd 占用的内存已被释放。 它还明确表明,初始启动时使用了 *initrd,*而 initrd 在完成其任务后,会释放之前占用的硬件资源。

第三条消息确认initramfs 确实正在使用 ,systemd 报告说,由于路径 /run/initramfs/fsck-root 不存在,因此跳过了 systemd-fsck-root.service文件系统检查。

该检查的逻辑是:检查文件系统,如果 /run/initramfs/fsck-root 不存在。

由于跳过了检查,因此该路径在操作系统启动时就已存在,因为没有出现其他与 initramfs相关的消息。

1.1 initrd是如何加载到内存中的?

当我们按下计算机或服务器的电源按钮时,BIOS/UEFI启动过程就开始了。它首先通过检查设备的MBR或GPT分区表来确定启动设备。

然后,利用分区和引导数据的信息,加载引导加载程序(例如 GRUB)的初始部分,该部分负责加载 GRUB 的第二阶段,而 GRUB 的第二阶段负责加载操作系统内核并切换到其执行。

内核加载到 RAM 后,GRUB 会通过直接跳转(例如,在 x86_64 架构上通过 jmp 或 call 函数)将控制权移交给内核:

复制代码
`jmp [адрес начала ядра]`

之后,内核开始在 RAM 中设置页表、缓存以及设置其他系统结构。

由于引导加载程序指定了 initrd ,内核会搜索并将其加载到 RAM 中。在这种情况下,GRUB 通过命令行参数将initrd 的路径传递给内核 。内核使用其内置驱动程序来解压缩并挂载 initrd。 根据压缩类型,内核会使用相应的解压缩程序(Astra Linux SE 使用 gzip 压缩)。initrd 根据 GRUB 通过 boot_params 结构传递的参数解压缩到 RAM 中。 该结构在 Linux 内核头文件中进行了描述,并包含以下字段:

复制代码
`	__u32 ext_ramdisk_image;			/* 0x0c0 */
	__u32 ext_ramdisk_size;				/* 0x0c4 */
	__u32 ext_cmd_line_ptr;				/* 0x0c8 */`

此信息包含在路径中:

复制代码
`/usr/src/linux-headers-$(uname -r)/arch/x86/include/uapi/asm/bootparam.h
`

这发生在初始化的早期阶段,甚至在初始化过程开始之前。


但既然本文要研究的是 initrd,那么让我们通过 在操作系统中解压initrd 来查看其目录和文件结构:

复制代码
`mkdir initrd_content && cp /boot/initrd.img-6.1.90-1-generic ~/initrd_content/initrd.img-6.1.90-1-generic.xz
cd ~/initrd_content/
unzstd initrd.img-6.1.90-1-generic.xz && cpio -id < ./initrd.img-6.1.90-1-generic
rm initrd.img-6.1.90-1-generic*`

这就是解压后的 initrd 镜像的样子

复制代码
`administrator@astra-02841:~/initrd_content$ ls -la
итого 48
drwxr-xr-x   9 administrator administrator 4096 сен  3 17:43 .
drwxr-x---+ 20 administrator administrator 4096 сен  3 17:43 ..
lrwxrwxrwx   1 administrator administrator    7 сен  3 17:43 bin -> usr/bin
drwxr-xr-x   3 administrator administrator 4096 сен  3 17:43 conf
drwxr-xr-x   2 administrator administrator 4096 сен  3 17:43 cryptroot
drwxr-xr-x  11 administrator administrator 4096 сен  3 17:43 etc
-rwxr-xr-x   1 administrator administrator 6556 сен  3 17:43 init
lrwxrwxrwx   1 administrator administrator    7 сен  3 17:43 lib -> usr/lib
lrwxrwxrwx   1 administrator administrator    9 сен  3 17:43 lib64 -> usr/lib64
drwxr-xr-x   2 administrator administrator 4096 сен  3 17:43 run
lrwxrwxrwx   1 administrator administrator    8 сен  3 17:43 sbin -> usr/sbin
drwxr-xr-x  10 administrator administrator 4096 сен  3 17:43 scripts
drwxr-xr-x   9 administrator administrator 4096 сен  3 17:43 usr
drwxr-xr-x   3 administrator administrator 4096 сен  3 17:43 var`

在 Astra Linux 中,许多操作和保护机制都包含在 scripts 目录中。

1.2. 让我们开始编写脚本吧!初始化脚本

我们来看一下脚本目录的内容:

复制代码
`administrator@astra-02841:~/initrd_content/scripts$ ls -lah
итого 64K
drwxr-xr-x 10 administrator administrator 4,0K сен  3 17:43 .
drwxr-xr-x  9 administrator administrator 4,0K сен  3 17:43 ..
-rw-r--r--  1 administrator administrator  12K сен  3 17:43 functions
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 init-bottom
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 init-premount
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 init-top
-rw-r--r--  1 administrator administrator 5,2K сен  3 17:43 local
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 local-block
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 local-bottom
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 local-premount
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 local-top
-rw-r--r--  1 administrator administrator 3,1K сен  3 17:43 nfs
drwxr-xr-x  2 administrator administrator 4,0K сен  3 17:43 panic`

我们看到不同层级的脚本按照特定的顺序加载。那么哪个脚本最先加载呢?

速度最快的脚本是 ./init,它位于 inird 的根目录下:

他在做什么?

初始化脚本的工作原理及初始测试

首先,他营造了环境:

复制代码
`export PATH=/sbin:/usr/sbin:/bin:/usr/bin`

脚本随后挂载 /dev/root/sys/proc/tmp 目录。 接着,它挂载 sysfsproc文件系统 ,这些文件系统提供有关系统和进程的信息。

复制代码
`[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc`

我们差点忘了制作产品目录:

复制代码
`mkdir -p /var/lock `

它会创建一个锁目录来存储锁文件。该目录的主要目的是防止竞争条件和冲突,确保资源同步,并管理用户模式下的资源访问。

接下来是处理 GRUB 菜单中指定的命令行参数。

复制代码
`for x in $(cat /proc/cmdline); do
	case $x in
	initramfs.clear)
		clear
		;;
	quiet)
		quiet=y
		;;
	esac
done`

这段脚本并不重要,它只是响应 quiet 参数并在屏幕上显示" **正在加载,请稍候......"**的消息 :

复制代码
`if [ "$quiet" != "y" ]; then
	quiet=n
	echo "Loading, please wait..."
fi
export quiet`

接下来是脚本:

复制代码
`mount -t devtmpfs -o nosuid,mode=0755 udev /dev`

挂载devtmpfs虚拟文件系统 ,从而可以自动创建设备文件(因为在 Linux 中一切皆文件!)。

这些参数的目的是授予 root 用户读取、写入和执行权限,以及其他用户读取和执行权限。nosuid属性还 可以阻止执行带有setuidsetgid 位 的文件, 从而 提高安全性。除非使用 udev 脚本,否则这还不是一个 root 文件系统,正如注释中所述:

复制代码
`# Note that this only becomes /dev on the real filesystem if udev's scripts
# are used; which they will be, but it's worth pointing out`

然后, udev脚本 执行操作,最终将设备文件移动到根文件系统。

这项说明对于理解文件系统和设备目录在系统启动不同阶段的集成方式至关重要。临时文件系统在启动过程的早期阶段使用,然后将控制权移交给主文件系统,最终的配置和通过 *udev 进行的设备管理都在主文件系统中进行。*稍后会详细介绍这一点。

以下是输入/输出流:

复制代码
`# Prepare the /dev directory
[ ! -h /dev/fd ] && ln -s /proc/self/fd /dev/fd
[ ! -h /dev/stdin ] && ln -s /proc/self/fd/0 /dev/stdin
[ ! -h /dev/stdout ] && ln -s /proc/self/fd/1 /dev/stdout
[ ! -h /dev/stderr ] && ln -s /proc/self/fd/2 /dev/stderr`

四条线路即可实现流式输入和输出!

每行代码的第一部分检查文件是否存在。如果文件不存在或不是符号链接,则返回 true,然后链接到 /dev目录下的对应文件。每个 Linux 用户都曾做过类似的事情:

复制代码
`somecommand 2>> error.log`

这正是创建输入输出流的脚本的用途所在。

接下来我们来谈谈终端。或者更确切地说,是伪终端!

复制代码
`mkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true`

通过这些命令,initrd 会创建一个用于伪终端的目录,现在我们来看看它的挂载选项!

noexec 阻止文件在此文件系统中执行。

nosuid与 /dev 类似 , 禁止执行带有 setuidsetgid 位的文件,并授予第 5 组写入权限。在任何 Linux 系统中,这都是 tty 组 (tty:x:5:)。

在这种情况下, root 具有读/写权限, devpts 是标准的伪终端文件系统名称,而 /dev/pts || true 是挂载点和有趣的管道,可确保在挂载失败时脚本继续运行。

导出变量

设备挂载完成后,变量将被导出:

复制代码
`# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf`

第一个 DPKG 架构导出参数的值取自 /conf/arch.conf 文件,对于 x86_64 和 amd64 架构,其值等于 amd64。我想这里没什么好困惑的,对吧?

复制代码
`# Set modprobe env
export MODPROBE_OPTIONS="-qb"`

modprobe命令的选项 ,用于指示其"停止打印状态消息并忽略错误"。

复制代码
`# Export relevant variables
export ROOT=
export ROOTDELAY=
export ROOTFLAGS=
export ROOTFSTYPE=
export IP=
export DEVICE=
export BOOT=
export BOOTIF=
export UBIMTD=
export break=
export init=/sbin/init
export readonly=y
export rootmnt=/root
export debug=
export panic=
export blacklist=
export resume=
export resume_offset=
export noresume=
export drop_caps=
export fastboot=n
export forcefsck=n
export fsckfix=`

现在我们来详细了解一下具体的环境变量:

init=/sbin/init --- 指定启动后应启动哪个初始化进程。默认值当然是 init ,它以 进程 ID 0 启动 并调用 systemd。

readonly=y:指定根文件系统应以只读方式挂载。该值设置为"Yes"。

rootmnt=/root --- 表示我们已定义了根挂载目录。
导出操作会设置后续 initrd 操作所需的环境变量。

相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室11 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕15 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1117 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯19 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983420 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器