本文呈现了一个完整、无遗漏的4大主要 Linux 根文件系统挂载全景内容,我将 initramfs、initrd、本地 Ext4、网络 NFS 这四大根文件系统挂载路径整合并写了这份深度技术文档和超高清全景流程图。
一、 Linux 根文件系统挂载全景流程图
二、 四种挂载方式分析
initrasmfs (现代标准内存文件系统)
initramfs(基于内存的 cpio 归档)是现代 Linux 发行版的绝对主力。它是一个直接驻留在内存中的文件系统(基于 tmpfs 或 ramfs),完全绕过了块设备层,运行效率极高。
1. 挂载前提条件(内核编译选项)
要让内核完美解压并运行 initramfs,内核配置文件中必须内置以下选项:
CONFIG_BLK_DEV_INITRD=y # 启用早期内存文件系统/设备支持CONFIG_RD_GZIP=y # 开启 Gzip 解压支持 (根据压缩格式可选 ZSTD/XZ/LZ4)CONFIG_TMPFS=y # 启用内存临时文件系统支持 (initramfs 运行的基石)
2. 内核启动参数示例 (Bootargs / Cmdline)
initrd=0x40000000,16M console=ttyS0 init=/init
initrd=0x40000000,16M:显式指定 Bootloader 加载 initramfs 镜像的物理内存起始地址和大小(在某些架构如 ARM 上,此参数由设备树 DTB 自动传入,命令行中可省略)。
init=/init:指定 initramfs 中第一个要运行的用户态进程,默认为根目录下的 /init。
3. 挂载内核源码级执行流程 (Linux 6.x)
【内核源码调用栈】:kernel_init() └─ kernel_init_freeable() <-- 准备内核初始化环境 (init/main.c) ├─ do_basic_setup() │ └─ populate_rootfs() <-- 核心入口:加载解压 (init/initramfs.c) │ ├─ xwrite() <-- 扫描内存中的镜像数据 │ └─ unpack_to_rootfs() <-- 解包 CPIO 数据到内置 rootfs └─ try_to_run_init_process("/init") <-- 运行 initramfs 中的初始脚本
4. 步骤深度技术解密
步骤 ①:内存保护(早期准备)在内核初始化极早期,setup_arch() 通过解析 DTB 或 boot_params 锁定 initramfs 的内存物理区间,调用 memblock_reserve() 将其保护起来,防止被伙伴系统(Buddy System)分配出去。
步骤 ②:特征码识别与解包 (populate_rootfs)内核在 populate_rootfs() 中读取该内存区间。它首先通过特征字符 070701(ASCII CPIO 头部标识)识别文件格式。一旦确认,调用 unpack_to_rootfs()。
步骤 ③:填充 VFS 根目录内核直接在早已建立好的虚拟 rootfs(基于 ramfs,挂载在 VFS 的 /)中按目录结构释放 CPIO 归档中的所有文件和文件夹。
步骤 ④:执行 /init 与挂载接力内核释放原始内存(调用 free_initrd_mem()),并启动进程 /init。该脚本探测本地硬件,加载所需的 NVMe/SAS 驱动,将真实的 Ext4 本地磁盘挂载到临时的 /new_root。
步骤 ⑤:完美退场(switch_root)/init 脚本调用 switch_root。内核会清空当前 rootfs 内存空间以防内存泄露,然后利用 sys_mount(..., MNT_DETACH) 将根挂载点移交给物理磁盘,最后 exec 真实系统中的 /sbin/init。
initrd (传统遗留虚拟块设备)
initrd 是 Linux 2.4/2.6 时代的产物。由于它是一个整块的文件系统镜像,VFS 无法直接挂载,必须通过内存虚拟块设备(Ramdisk)进行中转,机制繁琐。
1. 挂载前提条件(内核编译选项)
要支持这种传统的挂载方式,内核 .config 必须静态编译以下驱动:
CONFIG_BLK_DEV_INITRD=y # 启用早期内存设备支持CONFIG_BLK_DEV_RAM=y # 激活内存虚拟块设备驱动 (drivers/block/brd.c)CONFIG_BLK_DEV_RAM_COUNT=1 # 至少创建一个 ram 设备 (/dev/ram0)CONFIG_BLK_DEV_RAM_SIZE=65536 # 必须大于你的 initrd 镜像大小 (单位: KB)CONFIG_EXT2_FS=y # 必须静态编译镜像对应的文件系统驱动 (通常为 Ext2/Ext4)
2. 内核启动参数示例 (Bootargs / Cmdline)
root=/dev/ram0 rw initrd=0x50000000,32M init=/linuxrc console=ttyS0
3. 挂载内核源码级执行流程 (Linux 6.x)
【内核源码调用栈】:kernel_init() -> kernel_init_freeable() ├─ populate_rootfs() │ └─ clean_rootfs() -> write_initrd() <-- 1. 将镜像转存为虚拟 rootfs 里的文件 (init/initramfs.c) └─ prepare_namespace() (init/do_mounts.c) <-- 2. 准备挂载 └─ initrd_load() (init/do_mounts_rd.c) ├─ rd_load_image() <-- 3. 解析 /initrd.image │ └─ identify_ramdisk_image() └─ handle_initrd() ├─ mount_block_root("/dev/ram0")<-- 4. 挂载虚拟块设备为临时根 └─ pivot_root() <-- 5. 切换到真实磁盘,保留旧根 (init/do_mounts_initrd.c)
4. 步骤深度技术解密
步骤 ①:格式扫描与转存 (populate_rootfs)内核在 populate_rootfs() 阶段检测内存中的镜像。因为无法直接解压非 CPIO 格式的文件,内核会在内置虚拟 rootfs 中建立一个名为 /initrd.image 的临时文件,将整个镜像拷贝进去。
步骤 ②:拉起 Ramdisk 设备在 prepare_namespace() 阶段,内核初始化内存块设备驱动(brd.c),生成设备节点 /dev/ram0。
步骤 ③:解压写入虚拟磁盘 (rd_load_image)内核调用 rd_load_image() 读取 /initrd.image,识别其文件系统格式(如 Ext2),并将其解包解压,按 Block 写入 /dev/ram0 的后端内存页中。
步骤 ④:挂载过渡根VFS 调用 init_mount("/dev/ram0", "/", ...),/dev/ram0 成为第一个根目录,并执行 /linuxrc 脚本。
步骤 ⑤:根交换与手动清理(pivot_root)/linuxrc 加载物理硬盘驱动,挂载真实 Ext4 分区。最后调用 pivot_root。内核将新物理根置于 /,将旧的 /dev/ram0 挂载到 /initrd 目录下。用户态程序必须手动调用 umount /initrd 才能释放这几十兆的虚拟磁盘内存。
挂载本地物理 Ext4
这是最简洁直接的方式。没有临时内存系统的过渡,内核在初始化完成后,直接驱动物理磁盘控制器,挂载本地磁盘(HDD/SSD/eMMC/NVMe)上的 Ext4 分区。
1. 挂载前提条件(内核编译选项)
此方式下,所有的存储控制器驱动必须静态编译入内核内核。如果配置为模块(=m),由于此时还未挂载磁盘,内核根本无法读取 .ko 文件,从而陷入死锁。
CONFIG_EXT4_FS=y # 静态编译 Ext4 文件系统核心CONFIG_SCSI=y # 启用 SCSI 核心支持 (大多数 SATA/USB 设备依赖)CONFIG_BLK_DEV_SD=y # 启用 SCSI 磁盘驱动CONFIG_BLK_DEV_NVME=y # 如果根系统在 NVMe 上,必须内置此驱动CONFIG_VIRTIO_BLK=y # 如果在虚拟机中,必须内置 VirtIO 磁盘驱动
2. 内核启动参数示例 (Bootargs / Cmdline)
root=/dev/sda1 rootfstype=ext4 rw rootwait console=ttyS0 init=/sbin/init
root=/dev/nvme0n1p2:指定根文件系统所在的物理分区。也可以使用 UUID(如 root=UUID=xxxx-xxxx)。
rootfstype=ext4:强制 VFS 以 Ext4 驱动进行解析,加快挂载速度。
rootwait:由于物理磁盘(特别是 USB 和 SD 卡)异步扫描较慢,此参数命令内核必须无限期等待该设备节点出现,防止超时 panic。
3. 挂载内核源码级执行流程 (Linux 6.x)
【内核源码调用栈】:kernel_init() -> kernel_init_freeable() └─ prepare_namespace() (init/do_mounts.c) ├─ devtmpfs_mount() <-- 1. 挂载 devtmpfs 建立设备文件 ├─ wait_for_device_probe() <-- 2. 等待物理磁盘总线扫描就绪 (rootwait 作用点) └─ mount_root() └─ mount_block_root() <-- 3. 挂载本地物理块设备 ├─ get_fs_names() └─ do_mount_root() └─ vfs_kern_mount() <-- 4. VFS 读取 Ext4 超级块完成挂载
4. 步骤深度技术解密
步骤 ①:硬件总线探测与驱动加载内核启动后,静态编译的 PCI/PCIe、SATA 或 NVMe 控制器驱动被激活,开始扫描硬件总线,发现物理硬盘。
步骤 ②:建立物理设备节点内核挂载 devtmpfs,并在该目录下动态生成对应的块设备文件(如 /dev/nvme0n1p2)。
步骤 ③:等待设备就绪(rootwait 机制)在 prepare_namespace() 中,内核调用 wait_for_device_probe()。如果物理设备还没扫描出来,内核会在此处休眠,直到设备节点被成功创建。
步骤 ④:Ext4 超级块校验mount_block_root() 引导 VFS 读取该设备分区的第 1024 字节处(Ext4 超级块所在位置)。Ext4 驱动验证 Magic Number 是否为著名的 0xEF53。
步骤 ⑤:直接挂载与根启动校验成功后,内核直接调用底层挂载函数,将该物理分区挂载在 VFS 挂载树的 / 处,紧接着调用 try_to_run_init_process("/sbin/init") 启动真实系统。
挂载 NFS 网络文件系统
针对无盘工作站、云计算宿主机以及嵌入式板卡调试,直接通过网络协议栈(TCP/IP, RPC)将远程服务器的目录挂载为本地根目录。
1. 挂载前提条件(内核编译选项)
网络根文件系统挂载的底层支撑极其庞大,网卡驱动、网络协议栈、NFS 客户端必须全部静态编译(=y):
CONFIG_NET=y # 启用网络支持CONFIG_IP_PNP=y # 启用内核级 IP 自动配置 (核心配置)CONFIG_IP_PNP_DHCP=y # 支持通过 DHCP 动态获取本机 IPCONFIG_NFS_FS=y # 静态编译 NFS 客户端核心CONFIG_ROOT_NFS=y # 开启 NFS 根文件系统挂载功能CONFIG_NET_ETHERNET=y # 内核内置对应的网卡驱动
2. 内核启动参数示例 (Bootargs / Cmdline)
root=/dev/nfs nfsroot=192.168.1.100:/opt/rootfs,v3 ip=dhcp rw console=ttyS0 init=/sbin/init
root=/dev/nfs:强制 VFS 路由到网络挂载流程。
nfsroot=192.168.1.100:/opt/rootfs,v3:远程 NFS 服务器 IP 地址、共享目录路径,及 NFSv3 协议版本。
ip=dhcp:命令内核在挂载前,必须通过网卡向网络广播 DHCP 请求以配置网络。
3. 挂载内核源码级执行流程 (Linux 6.x)
【内核源码调用栈】:kernel_init() -> kernel_init_freeable() └─ prepare_namespace() (init/do_mounts.c) ├─ ip_auto_config() <-- 1. 自动配置网络物理网卡 (net/ipv4/ipconfig.c) └─ mount_root() └─ mount_nfs_root() <-- 2. NFS 挂载专用入口 (fs/nfs/nfsroot.c) ├─ nfs_root_parm() <-- 3. 解析命令行 nfsroot 参数 └─ nfs_mount() └─ nfs_get_tree() <-- 4. 发起网络 RPC Mount,构建 VFS 超级块
4. 步骤深度技术解密
步骤 ①:网卡激活与内核级 IP 配置 (ip_auto_config)在挂载前,内核调用 ip_auto_config() 启动内置网卡。内核直接在未进入用户态的情况下,广播 DHCP Discover 包。从 DHCP 服务器获取网卡 IP、子网掩码、网关,并硬编码进内核网络栈。
步骤 ②:解析参数与连接服务器内核解析 nfsroot= 参数,通过 TCP 或 UDP 协议与远程 NFS 服务器(如 192.168.1.100)建立连接。
步骤 ③:远程 RPC Mount 协商内核网络子系统发送 RPC(远程过程调用)请求到 NFS 服务器的 Portmapper(端口 111),查询 mountd 服务的端口。接着向 mountd 申请挂载 /opt/rootfs 目录。
步骤 ④:接收文件句柄与 VFS 绑定NFS 服务器验证客户端 IP 权限,通过 RPC 向内核返回一个根目录文件句柄(File Handle)。内核的 NFS 客户端驱动利用该句柄构建 VFS super_block 结构体。
步骤 ⑤:网络根挂载成功VFS 将该远程网络挂载点绑定到 /。接下来,系统所有的文件读写(如执行 /sbin/init)都会被转化为 NFS 协议的网络包发送给服务器,实现无盘启动。
三、 四大挂载方式多维度对比
| 特征维度 | initramfs (现代标准) | initrd (传统遗留) | 直接挂载本地 Ext4 | 直接挂载网络 NFS |
|---|
| 介质载体 | 物理内存(RAM) | 物理内存 & 虚拟块设备 | 本地存储(SATA/NVMe/Flash) | 网络存储服务器(NFS Server) |
| 传输协议 | 无(直接在内存解压) | 内存拷贝 | 本地总线(PCIe/SATA/SDIO等) | 网络协议栈(TCP/UDP, RPC等) |
| 内核编译要求 | CONFIG_BLK_DEV_INITRD=y | 开设 RAM 驱动(CONFIG_BLK_DEV_RAM) | 内置磁盘控制器及 Ext4 驱动 | 内置网卡驱动、IP-PNP 及 NFS 客户端 |
| 挂载点交接机制 | switch_root (销毁旧根) | pivot_root (交换并保留旧根) | 无过渡,直接挂载 | 无过渡,直接挂载 |
| 网卡状态要求 | 无需早期网卡驱动 | 无需早期网卡驱动 | 无需早期网卡驱动 | 必须在内核启动极早期完成网卡初始化并获取 IP |
| 典型应用场景 | 所有主流桌面与服务器发行版 | 2005 年前的老旧 Linux 系统 | 嵌入式设备(OpenWrt/IoT)、极简容器 | 无盘工作站、嵌入式板卡快速开发与调试 |
💡 为什么开发 ARM 时,大家都爱用 NFS 挂载?
在开发嵌入式 Linux(如树莓派、S5P6818、H618 等)时,反复烧写 Ext4 镜像到 SD 卡或 EMMC 中是极其痛苦且损耗硬件寿命的。
开发痛点:每次改动一个 C 语言程序,都要重新编译、打包成镜像、烧录、重启。
NFS 解决方案:将开发板的根系统直接挂载到 Ubuntu 宿主机的共享目录中。我们在宿主机上交叉编译一个程序,直接丢进 NFS 共享目录,开发板上立刻就能看到并执行,完全不需要重新烧写和重启。
生产环境转型:等开发板的所有软件调试完毕,再将该 NFS 目录打包成 initramfs 或烧录进本地 Ext4 分区,即可作为正式产品发布。这就是现代嵌入式开发中,网络挂载与本地挂载完美接力的经典范式。