1. Rootfs 概述:什么是 Rootfs、它在系统中的作用、与内核的关系
各位同学,咱们今天正式开讲《Rootfs 镜像裁剪与性能优化实战》的第一章。说实话,我每次带新人做嵌入式项目,第一个要讲清楚的就是 Rootfs。很多人觉得它不就是个文件系统嘛,挂上去就能跑。但实际上一旦你开始做裁剪、做优化,就会发现这里面的门道深得很。
我个人习惯把 Rootfs 比作一个房子的「内部装修」。内核是房子的框架和地基,而 Rootfs 就是里面的家具、水电、网络、甚至是你贴在墙上的便利贴。没有装修,房子也能住人,但你什么都干不了。嵌入式系统也是一样——内核启动后,如果没有 Rootfs,它就卡在 panic 状态,啥也做不了。
1.1 什么是 Rootfs
Rootfs,全称 Root Filesystem,根文件系统。它不是一个具体的文件系统类型(比如 ext4、squashfs),而是一个 逻辑概念。它指的是 Linux 内核启动后挂载的第一个文件系统,挂载点就是根目录 /。
你想想看,Linux 系统里所有进程的起点是 /sbin/init 或者 /bin/bash,这些可执行文件放在哪?就在 Rootfs 里。没有 Rootfs,内核连第一个用户态进程都启动不了。
核心定义: Rootfs 是 Linux 系统启动时挂载的第一个文件系统,它包含了系统运行所必需的文件、目录、设备节点、配置文件和工具程序。它是内核从「裸金属」过渡到「可用系统」的桥梁。
我记得刚入行那会儿,有个同事把 Rootfs 和根目录搞混了。他问我:「Rootfs 不就是 / 吗?」我说不对。/ 是挂载点,Rootfs 是挂载上去的那个东西。就好比 USB 接口和 U 盘的关系——接口是固定的,但插上去的 U 盘可以换。Rootfs 可以是一个 ramdisk、一个 squashfs 镜像、一个 NFS 共享目录,甚至是一个 initramfs 包。
1.2 Rootfs 在 Linux 系统中的作用
Rootfs 的作用,我用三个词来概括:启动、运行、管理。咱们一个一个说。
1.2.1 启动系统
内核启动的最后一步,就是挂载 Rootfs 并执行第一个用户态程序。这个程序通常是 /sbin/init,它负责启动所有后续的服务和进程。如果 Rootfs 里没有 /sbin/init,内核会报 Kernel panic - not syncing: No init found,然后死在那。
我曾经在一个 ARM 板子的项目上遇到过这个问题。当时我裁剪 Rootfs 裁得太狠,把 /sbin/init 给删了,结果板子死活起不来。查了半天日志才发现是这个问题。嗯,从那以后我裁剪 Rootfs 都会留一个最小可用的 init 程序。
1.2.2 提供运行时环境
系统启动后,所有用户态进程都依赖 Rootfs 提供的文件和服务。包括但不限于:
- 动态链接库:
/lib和/usr/lib下的 .so 文件,没有它们,任何动态链接的程序都跑不起来。 - 设备节点:
/dev下的设备文件,比如/dev/null、/dev/console,没有这些,程序连标准输入输出都用不了。 - 配置文件:
/etc下的各种配置,比如/etc/inittab、/etc/fstab,决定了系统的行为。 - 临时文件系统:
/tmp、/var等目录,用于运行时数据存储。
我的经验: 在做嵌入式系统时,我建议你把 Rootfs 分成「最小启动集」和「功能扩展集」两部分。最小启动集只包含内核启动后必须的文件,比如 init、sh、基本的 libc 和设备节点。功能扩展集按需添加。这样裁剪起来思路清晰,不容易出错。
1.2.3 提供管理接口
Rootfs 还提供了系统管理的工具和接口。比如 /bin/ls、/bin/ps、/sbin/ifconfig 这些命令,都是放在 Rootfs 里的。没有它们,你连系统里跑了哪些进程、网络通不通都查不了。
说白了,Rootfs 就是用户和内核之间的「翻译官」。用户通过 Rootfs 里的工具程序,向内核发起系统调用,完成各种操作。
1.3 Rootfs 与内核的关系
这个关系我画了一张图,大家先看看,我再细讲。
从这张图可以看得很清楚:
- 内核在最底层,直接管理硬件。它提供系统调用接口,但不关心用户怎么用。
- Rootfs 在中间层,它挂载在内核之上,提供文件系统和用户态环境。
- 用户进程在最上层,它们通过 Rootfs 里的工具和库,间接使用内核的服务。
内核和 Rootfs 之间有一个关键的接口——VFS(虚拟文件系统)。VFS 是内核中的一个抽象层,它屏蔽了不同文件系统(ext4、squashfs、tmpfs 等)的差异。内核通过 VFS 来读写 Rootfs 里的文件,不管底层是 flash、ram 还是网络。
注意: 内核和 Rootfs 是 独立编译 的。内核编译出来是 zImage 或 uImage,Rootfs 编译出来是 .img 或 .squashfs 文件。它们可以分开烧录、分开升级。但内核必须知道 Rootfs 在哪里(通过 bootargs 参数指定),否则启动时会 panic。
我举个例子你就明白了。假设你有一个嵌入式设备,内核放在 flash 的 0x100000 位置,Rootfs 放在 0x200000 位置。bootloader 启动内核时,会传一个参数:root=/dev/mtdblock2 或者 root=31:02。内核根据这个参数找到 Rootfs 的物理位置,挂载它,然后执行 init。
这里有个坑,我曾经踩过。有一次我改了分区表,Rootfs 的偏移地址变了,但忘了改 bootargs。结果内核启动后找不到 Rootfs,直接 panic。查了半天才发现是参数没更新。所以,每次调整分区或镜像位置,一定要同步更新 bootargs。
1.4 Rootfs 的常见形态
在实际项目中,Rootfs 有几种常见的形态。我整理了一个表格,方便你对比:
| 形态 | 存储介质 | 特点 | 适用场景 |
|---|---|---|---|
| initramfs | 内存(cpio 归档) | 启动快,体积小,可定制;但掉电丢失 | 早期启动、救援系统、Live CD |
| squashfs | Flash(只读压缩) | 体积小,压缩率高,只读安全;但不可写 | 嵌入式设备、路由器、IoT 终端 |
| ext4 / ubifs | Flash / 硬盘(读写) | 可读写,支持日志;但体积大,有磨损问题 | 通用 Linux、开发板、需要持久化数据的设备 |
| NFS Rootfs | 网络(远程挂载) | 方便调试,无需烧录;但依赖网络,速度慢 | 开发调试阶段、原型验证 |
我个人在项目中最常用的是 squashfs + overlayfs 的组合。squashfs 作为只读基座,overlayfs 在内存或 flash 上叠加一个可写层。这样既保证了 Rootfs 的稳定性和安全性,又保留了可写能力。后面讲到裁剪的时候,我会详细展开这个方案。
1.5 本章小结
好了,第一章的内容就这些。咱们回顾一下重点:
- Rootfs 是内核启动后挂载的第一个文件系统,它提供了系统运行所需的所有用户态文件。
- Rootfs 的作用是启动系统、提供运行时环境、提供管理接口,缺一不可。
- 内核和 Rootfs 是独立但依赖的关系,内核通过 VFS 访问 Rootfs,Rootfs 通过系统调用为进程服务。
- Rootfs 有多种形态,选择哪种取决于你的硬件、性能和功能需求。