在Linux系统启动过程中,内核需要访问根文件系统来继续启动过程。然而,根文件系统可能位于内核无法直接访问的存储设备上,这就产生了一个"鸡生蛋,蛋生鸡"的问题:内核需要驱动程序来访问存储设备,而驱动程序又存储在这些设备上。为了解决这个问题,Linux引入了initrd和initramfs机制。本文将深入探讨这两种机制的工作原理、区别以及实际应用。
在Linux系统发展的早期阶段,计算机的存储设备相对简单,主要是硬盘驱动器(HDD)和软盘驱动器。由于设备类型有限,将所有必要的驱动程序直接编译到内核中是一种可行的解决方案。这种方法的优点是简单直接,内核可以在启动时立即访问根文件系统。
随着技术的发展,现代计算机系统,特别是嵌入式系统,面临着更加复杂的存储环境:
将所有可能用到的驱动程序都编译到内核中会导致:
Initrd(Initial RAM Disk)是Linux内核启动机制中的一个重要组件,它提供了一个临时的根文件系统,用于在真正的根文件系统可用之前执行必要的初始化操作。
Initrd的核心思想是:创建一个小型的、包含必要驱动程序和工具的根文件系统,将其加载到内存中,然后使用它来准备和挂载真正的根文件系统。
Bootloader阶段
内核初始化阶段
脚本执行阶段
/linuxrc
脚本/init
脚本真实根文件系统挂载
# 创建传统image格式的initrd
dd if=/dev/zero of=initrd.img bs=1024 count=4096
mke2fs -F -m0 initrd.img
mount -t ext2 -o loop initrd.img /mnt/initrd
# 在/mnt/initrd中添加必要文件
# 创建/linuxrc脚本
umount /mnt/initrd
gzip initrd.img
# 创建cpio格式的initrd
find . | cpio -o -H newc | gzip > initrd.img
尽管initrd解决了根文件系统访问的问题,但它也有一些局限性:
从Linux 2.5版本开始,内核引入了initramfs(Initial RAM File System)机制,它是initrd的改进版本,旨在解决initrd的一些局限性。
Initramfs不是一个独立的磁盘映像,而是直接编译到内核中的:
.init.ramfs
__initramfs_start
和__initramfs_end
标识边界// 内核中的相关代码示例
extern char __initramfs_start[];
extern char __initramfs_end[];
static void __init unpack_initramfs(void)
{
char *buf = __initramfs_start;
char *end = __initramfs_end;
// 解压缩initramfs数据
}
// 简化的initramfs解压流程
static int __init populate_rootfs(void)
{
// 如果存在外部initrd,先处理它
if (initrd_start) {
// 处理外部initrd
}
// 处理内置的initramfs
if (__initramfs_start < __initramfs_end) {
unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start);
}
return 0;
}
initramfs/
├── bin/
│ ├── busybox
│ └── sh -> busybox
├── sbin/
│ ├── modprobe
│ └── insmod
├── dev/
│ ├── console
│ └── null
├── proc/
├── sys/
├── lib/
│ └── modules/
├── etc/
│ └── fstab
└── init
#!/bin/bash
# create_initramfs.sh
INITRAMFS_DIR="initramfs"
KERNEL_VERSION=$(uname -r)
# 创建目录结构
mkdir -p $INITRAMFS_DIR/{bin,sbin,dev,proc,sys,lib/modules,etc}
# 复制busybox
cp /bin/busybox $INITRAMFS_DIR/bin/
# 创建必要的符号链接
cd $INITRAMFS_DIR/bin
for cmd in sh ls mkdir mount umount cat; do
ln -s busybox $cmd
done
cd -
# 创建设备节点
mknod $INITRAMFS_DIR/dev/console c 5 1
mknod $INITRAMFS_DIR/dev/null c 1 3
# 创建init脚本
cat > $INITRAMFS_DIR/init << 'EOF'
#!/bin/sh
# 挂载必要的文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
# 加载必要的模块
modprobe ext4
modprobe usb-storage
# 等待设备就绪
sleep 2
# 挂载真实根文件系统
mount /dev/sda1 /mnt
# 切换到真实根文件系统
exec switch_root /mnt /sbin/init
EOF
chmod +x $INITRAMFS_DIR/init
# 创建cpio归档
cd $INITRAMFS_DIR
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd -
特性 | Initrd | Initramfs |
---|---|---|
存储方式 | 独立文件 | 内核集成 |
文件系统类型 | 块设备文件系统 | 临时文件系统 |
内存使用 | 需要额外内存 | 直接使用内核内存 |
启动复杂度 | 较复杂 | 相对简单 |
大小限制 | 受内存限制 | 受内核大小限制 |
# 查看initramfs占用的内存
cat /proc/meminfo | grep -i initramfs
# 或者
grep initramfs /proc/iomem
# 典型的服务器initramfs配置
# 支持多种存储设备和网络启动
dracut --add "nfs iscsi" --force
# 精简的嵌入式initramfs
# 只包含必要的驱动和工具
busybox --install -s $INITRAMFS_DIR/bin
# 支持加密分区的initramfs
# 包含cryptsetup工具
dracut --add "crypt" --force
# 更新initramfs
update-initramfs -u
# 查看initramfs内容
lsinitramfs /boot/initrd.img-$(uname -r)
# 解压initramfs查看内容
mkdir /tmp/initramfs
cd /tmp/initramfs
gunzip -c /boot/initrd.img-$(uname -r) | cpio -i
# 使用dracut生成initramfs
dracut --force
# 查看dracut配置
cat /etc/dracut.conf
# 列出initramfs模块
dracut --list-modules
# 在内核启动参数中添加调试选项
linux /boot/vmlinuz root=/dev/sda1 rd.debug rd.shell
# 查看启动日志
journalctl -b | grep initrd
dmesg | grep initramfs
# 进入initramfs shell进行调试
# 在grub中添加:rd.break=pre-mount
Initramfs和initrd都是Linux内核启动过程中的重要组件,它们解决了现代计算机系统中根文件系统访问的复杂性问题。虽然initrd在历史上发挥了重要作用,但initramfs以其更简洁的设计和更好的性能逐渐成为主流选择。
理解这两种机制不仅有助于我们更好地理解Linux系统的启动过程,也为我们在遇到启动问题时提供了重要的调试和解决思路。在实际应用中,我们应该根据具体需求选择合适的技术方案,并遵循最佳实践来确保系统的稳定性和安全性。
随着容器化和云计算技术的发展,initramfs的作用也在不断演进,它不仅仅是一个启动辅助工具,更是现代Linux系统架构中不可或缺的组成部分。掌握这些基础知识,将有助于我们更好地适应和利用现代Linux系统的特性。