这篇文章主要是后面博客的总结,使用的例子也来自于这篇博客 https://www.linkedin.com/pulse/kernel-rootfs-where-linux-actually-begins-moon-hee-lee-0fjqc/
Linux启动的三大核心组件
当我们深入理解Linux系统启动过程时,会发现它远比简单的"开机即用"要复杂。一个完整的Linux启动过程实际上是由三个关键组件精密协作的结果:
内核 (Kernel)
内核是Linux系统的核心,负责硬件抽象、进程管理、内存管理、设备驱动等底层功能。它是整个系统的"大脑",直接与硬件交互。
根文件系统 (RootFS)
RootFS包含了用户空间的所有组件,包括:
- 系统管理器 (systemd)
- 包管理工具 (apt/dpkg)
- 核心系统库
- 用户程序和配置文件
它是系统运行的基础环境。
初始内存文件系统 (Initramfs)
Initramfs是一个临时的微型根文件系统,它在内核启动的早期阶段被加载。它的核心作用是:
- 为内核提供必要的驱动模块
- 检测并挂载真正的根文件系统
- 完成系统切换后移交控制权给真正的init系统
启动流程的精妙协作
Linux系统的启动流程是一个精心设计的接力过程:
为什么需要Initramfs?
一个常见的问题是:为什么内核不直接加载RootFS,而是需要一个中间的Initramfs?
- 硬件支持:现代系统需要复杂的驱动程序来访问存储设备,这些驱动可能需要从网络或特殊存储加载
- 模块化设计:允许系统在真正根文件系统挂载前完成硬件初始化
- 灵活性:支持不同的存储设备类型和网络启动场景
- 错误恢复:提供系统诊断和修复的临时环境
Initramfs的核心职责
Initramfs在这个启动过程中承担着至关重要的职责:
- 驱动程序加载:加载访问根设备所需的内核模块
- 设备检测:识别和初始化存储设备(如SCSI、NVMe、virtio等)
- 文件系统挂载:找到并挂载真正的根文件系统
- 环境准备:为最终系统切换准备必要的环境
- 控制权移交:通过
switch_root切换到真正的根文件系统并启动init系统
这种设计确保了Linux系统能够在各种硬件环境下稳定启动,即使根文件系统位于复杂的存储设备或通过网络访问也能正常工作。
Prerequisite
工具安装
在进行Linux系统构建实验前,我们需要安装必要的开发工具链。这些工具涵盖了从内核编译、文件系统创建到虚拟化模拟的完整构建流程。
sudo apt updatesudo apt install -y \git build-essential bc bison flex libssl-dev libelf-dev dwarves \pahole openssl cpio rsync xz-utils zstd kmod \debootstrap qemu-system-x86 qemu-utils e2fsprogs
内核构建工具:
- build-essential、bc、bison、flex - 内核编译必需的基础工具
- libssl-dev、libelf-dev、dwarves、pahole - 内核模块开发和调试依赖
- openssl - 加密和哈希函数支持
文件系统工具: - debootstrap - 创建Debian兼容的根文件系统
- cpio、rsync、xz-utils、zstd - 文件系统打包和压缩
- e2fsprogs - ext4文件系统工具
虚拟化工具: - qemu-system-x86、qemu-utils - x86架构虚拟化支持
- kmod - 内核模块管理工具
工作区
创建工作区:
mkdir -p ~/linux-boot-lab/{src,build,artifacts/kernel,artifacts/modules}cd ~/linux-boot-lab
目录规划说明:
linux-boot-lab/
├── src/ # 内核源码存储目录
├── build/ # 临时构建工作区
└── artifacts/ # 最终产物存储 ├── kernel/ # 内核镜像和配置 └── modules/ # 内核模块文件
Build the Kernel
编译内核大致分为几个步骤:
- 下载内核源码
- 配置内核
- 编译
- 安装模块
下面是详细的步骤和命令
下载内核源码
从官方仓库获取最新的Linux内核源码,使用--depth=1仅获取最新版本以节省时间和空间:
cd ~/linux-boot-labgit clone --depth=1 \https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git \src/linux-mainline
配置内核
基于x86_64架构的默认配置开始,然后针对QEMU虚拟化环境添加必要的支持选项。这些配置确保内核能在虚拟环境中正常工作并支持我们所需的硬件特性。
先使用默认的配置生成 .config:
SRC=$HOME/linux-boot-lab/src/linux-mainline
OUT=$HOME/linux-boot-lab/build/linux-x86_64make -C "$SRC" O="$OUT" ARCH=x86_64 x86_64_defconfigecho "-boot-lab" > "$OUT/localversion"
-C 表示先 cd 到 $SRC 再执行 make
O=build目录
ARCH=x86_64 指定目标架构
x86_64_defconfig x86 架构下的默认配置文件
因为需要在 Qemu 里面运行,所以需要修改一些配置:
"$SRC/scripts/config" --file "$OUT/.config" \-e DEVTMPFS \-e DEVTMPFS_MOUNT \-e VIRTIO \-e VIRTIO_PCI \-e VIRTIO_BLK \-e VIRTIO_NET \-e EXT4_FS \-e SERIAL_8250 \-e SERIAL_8250_CONSOLEmake -C "$SRC" O="$OUT" ARCH=x86_64 olddefconfig
olddefconfig 作用:读取已有的 .config 对新增配置项自动使用默认值
编译构建
编译内核:
make -C "$SRC" O="$OUT" ARCH=x86_64 -j"$(nproc)" bzImage modules
build 完后可以通过下面命令查看一下版本:
KREL=$(make -s -C "$SRC" O="$OUT" ARCH=x86_64 kernelrelease)
echo "$KREL"
先把编译出来的产物移到 aritifacts 下面:
cd ~/linux-boot-labKART=$HOME/linux-boot-lab/artifacts/kernel/$KREL
MART=$HOME/linux-boot-lab/artifacts/modules/$KRELmkdir -p "$KART" "$MART"cp "$OUT/arch/x86/boot/bzImage" "$KART/vmlinuz-$KREL"
cp "$OUT/.config" "$KART/config-$KREL"
cp "$OUT/System.map" "$KART/System.map-$KREL"
安装 modules:
make -C "$SRC" O="$OUT" ARCH=x86_64 \INSTALL_MOD_PATH="$MART" \modules_install
可以用下面的命令检查一下编译出来的 module 有没有问题,没有问题的话就没有输出:
sudo depmod -b "$MART" "$KREL"
Build the Rootfs
根文件系统(RootFS)是Linux系统运行的基础环境,包含了用户空间的所有必要组件。我们将使用文件系统镜像的方式构建一个最小化的根文件系统。
创建文件系统镜像
创建一个2GB的ext4文件系统镜像作为根文件系统的容器。使用truncate创建稀疏文件以节省磁盘空间:
cd ~/linux-boot-lab/buildrm -f debian-rootfs.img
rm -rf rootfstruncate -s 2G debian-rootfs.img
mkfs.ext4 -F debian-rootfs.img
truncate 本身的作用是调整文件大小,值得注意的是 truncate 创建的是 sparse file,并不会真正写满 2GB;
-s 指定文件大小;
mkfs.ext4 -F 把文件格式化成 ext4 文件系统;
还要一个工具是dd,但是dd会真正占满空间
把这个文件系统挂载到一个目录上:
mkdir -p rootfs
sudo mount -o loop debian-rootfs.img rootfs
安装基础系统
使用 debootstrp 来构建 debian 兼容的最小化用户空间,包含基本的系统组件和包管理器:
sudo debootstrap \--arch=amd64 \--variant=minbase \trixie \rootfs \http://deb.debian.org/debian
包括:
/bin
/etc
/lib
/root
/sbin
/usr
/var
系统配置
为根文件系统配置基本系统信息,包括主机名、网络解析、软件源和系统设置。
修改文件系统里的 host:
sudo tee rootfs/etc/hostname >/dev/null <<EOF
boot-lab
EOFsudo tee rootfs/etc/hosts >/dev/null <<EOF
127.0.0.1 localhost
127.0.1.1 boot-lab
EOF
package source:
sudo tee rootfs/etc/apt/sources.list >/dev/null <<EOF
deb http://deb.debian.org/debian trixie main
deb http://security.debian.org/debian-security trixie-security main
deb http://deb.debian.org/debian trixie-updates main
EOF
resolver:
sudo tee rootfs/etc/resolv.conf >/dev/null <<EOF
nameserver 1.1.1.1
nameserver 8.8.8.8
EOF
locale:
sudo tee rootfs/etc/default/locale >/dev/null <<EOF
LANG=C.UTF-8
EOF
fstab:
sudo tee rootfs/etc/fstab >/dev/null <<EOF
/dev/vda / ext4 defaults 0 1
EOF
fstab的作用:系统启动后,哪些文件系统应该挂载到哪里
这行的意思是:启动后,把 virtio 磁盘/dev/vda挂载成系统根目录/, 文件系统类型是 ext4。
内核集成
将编译好的内核模块和内核文件集成到根文件系统中,确保系统启动时能正确加载内核组件。
拷贝 Linux modules 到 rootfs 里面:
cd ~/linux-boot-labSRC=$HOME/linux-boot-lab/src/linux-mainline
OUT=$HOME/linux-boot-lab/build/linux-x86_64KREL=$(make -s -C "$SRC" O="$OUT" ARCH=x86_64 kernelrelease)
echo "$KREL"sudo mkdir -p build/rootfs/lib/modulessudo rsync -a \"artifacts/modules/$KREL/lib/modules/$KREL" \build/rootfs/lib/modules/
rsync 用于高效同步文件和目录。 rsync 比 cp 更强大,可以:1. 目录同步 2. 保持权限 3. 增量复制 4. 保持 symlink 5. 保持时间戳 6. 大目录树复制。 因为 kernel modules 正好是:大量目录 + symlink + metadata,所以适合 rsync
把 kernel image, config, System.map 拷贝到 rootfs/boot 下面:
sudo mkdir -p build/rootfs/bootsudo cp "artifacts/kernel/$KREL/vmlinuz-$KREL" \"build/rootfs/boot/vmlinuz-$KREL"sudo cp "artifacts/kernel/$KREL/config-$KREL" \"build/rootfs/boot/config-$KREL"sudo cp "artifacts/kernel/$KREL/System.map-$KREL" \"build/rootfs/boot/System.map-$KREL"
系统环境配置
通过chroot进入目标根文件系统,安装必要的系统组件并配置网络和服务。
chroot 前我们需要挂载一些东西到 rootfs 下面:
cd ~/linux-boot-lab/buildsudo mount --bind /dev rootfs/dev
sudo mount --bind /dev/pts rootfs/dev/pts
sudo mount -t proc proc rootfs/proc
sudo mount -t sysfs sysfs rootfs/sys
mount -t 创建并挂载一种文件系统
mount --bind 把已经存在的东西,在另外一个地方再显示一次
chroot:
sudo chroot rootfs /bin/bash
chroot 把当前目录伪装成根目录
进入后就可以开始配置一些环境:
export LANG=C.UTF-8
export LC_ALL=C.UTF-8apt updateapt install -y \systemd-sysv \initramfs-tools \kmod \iproute2 \iputils-ping \openssh-server \ca-certificatescat >/etc/systemd/network/20-wan.network <<EOF
[Match]
Name=en*[Network]
DHCP=yes
EOFsystemctl enable systemd-networkd
systemctl enable ssh
systemctl enable serial-getty@ttyS0.servicemkdir -p /etc/ssh/sshd_config.dcat >/etc/ssh/sshd_config.d/00-boot-lab-root-login.conf <<EOF
PermitRootLogin yes
PasswordAuthentication yes
PermitEmptyPasswords yes
EOFpasswd -d roottouch /etc/securetty
grep -qxF ttyS0 /etc/securetty || echo ttyS0 >> /etc/securetty
grep -qxF console /etc/securetty || echo console >> /etc/securetty
我们的rootfs到现在就build完成了,它包含:
- systemd
- module tools
- initramfs tool
- 基本的网络配置
- serial login path
Build the Initramfs
文章最开头提到,kernel 启动后需要先加载 initramfs 才是 rootfs。所以我们现在要基于 rootfs 来 build 一个 initramfs。我们仍然是在 chroot 后的环境里面,直接使用 update-initramfs 这个工具来帮我们生成一个 initramfs
KREL=$(ls -1 /lib/modules | sort | tail -1)
echo "$KREL"update-initramfs -c -k "$KREL"ls -l /boot/initrd.img-$KREL
退出 chroot,把我们build好的 initramfs 拷贝出来:
cd ~/linux-boot-labKREL=$(make -s -C "$SRC" O="$OUT" ARCH=x86_64 kernelrelease)sudo cp "build/rootfs/boot/initrd.img-$KREL" \"artifacts/kernel/$KREL/initrd.img-$KREL"
build 完后卸载之前 chroot 前挂载上去的设备:
cd ~/linux-boot-lab/buildsudo umount rootfs/dev/pts
sudo umount rootfs/dev
sudo umount rootfs/proc
sudo umount rootfs/sys
sudo umount rootfs
Booting
使用 QEMU 启动:
cd ~/linux-boot-labSRC=$HOME/linux-boot-lab/src/linux-mainline
OUT=$HOME/linux-boot-lab/build/linux-x86_64KREL=$(make -s -C "$SRC" O="$OUT" ARCH=x86_64 kernelrelease)
echo "$KREL"qemu-system-x86_64 \-m 1024M \-smp 2 \-kernel "artifacts/kernel/$KREL/vmlinuz-$KREL" \-initrd "artifacts/kernel/$KREL/initrd.img-$KREL" \-append "root=/dev/vda rw rootwait console=ttyS0" \-drive file="build/debian-rootfs.img",format=raw,if=virtio \-netdev user,id=net0,hostfwd=tcp::2222-:22 \-device virtio-net-pci,netdev=net0 \-nographic
