大家好,我是良许。
最近在做嵌入式 Linux 项目的时候,经常有朋友问我关于 RootFS 的问题。
比如"为什么我的板子启动不了?""文件系统怎么制作?""initramfs 和 rootfs 有什么区别?"等等。
今天我就系统地给大家讲讲 RootFS 文件系统,从原理到实践,让你彻底搞懂这个嵌入式 Linux 开发中的核心概念。
1. 什么是 RootFS
1.1 RootFS 的基本概念
RootFS,全称 Root File System,即根文件系统。
它是 Linux 系统启动后挂载的第一个文件系统,也是整个文件系统树的根节点。
所有其他的文件系统都会挂载在 RootFS 的某个目录下,形成一个统一的目录树结构。
在嵌入式 Linux 系统中,RootFS 的地位尤为重要。
它包含了系统启动所需的所有基本文件,包括:
- 系统初始化程序(如 init、systemd 等)
- 基本的系统命令和工具(如 ls、cp、cat 等)
- 系统库文件(如 libc.so 等)
- 设备文件(/dev 目录下的设备节点)
- 配置文件(/etc 目录下的各种配置)
- 应用程序及其依赖
1.2 RootFS 在系统启动中的作用
当 Linux 内核启动完成后,它会尝试挂载 RootFS,然后执行 RootFS 中的第一个用户空间程序(通常是/sbin/init 或/init)。
这个过程是系统从内核空间过渡到用户空间的关键步骤。
内核启动的大致流程是这样的:
- Bootloader 加载内核到内存并启动
- 内核初始化硬件、内存管理、进程调度等
- 内核挂载 RootFS
- 内核启动 init 进程(PID 为 1)
- init 进程根据配置启动其他系统服务和应用程序
如果 RootFS 挂载失败,内核会 panic,系统无法正常启动。
这也是为什么很多初学者在移植 Linux 时,经常遇到"Kernel panic - not syncing: VFS: Unable to mount root fs"这样的错误。
2. RootFS 的类型
2.1 基于存储介质的分类
根据存储介质的不同,RootFS 可以分为以下几种类型:
2.1.1 基于 Flash 的 RootFS
在嵌入式系统中,最常见的是将 RootFS 存储在 Flash 中。
根据 Flash 类型的不同,又可以细分为:
- NOR Flash RootFS:适合小容量系统,可以直接在 Flash 上执行代码(XIP),但容量小、价格贵。
- NAND Flash RootFS:容量大、价格便宜,但需要文件系统支持坏块管理,常用的有 JFFS2、YAFFS2、UBIFS 等。
- eMMC/SD 卡 RootFS:类似于 PC 的硬盘,可以使用 ext2/ext3/ext4 等传统文件系统。
2.1.2 基于 RAM 的 RootFS
有些场景下,RootFS 会被加载到 RAM 中运行,这种方式称为 initramfs 或 ramfs。
优点是读写速度快,缺点是掉电数据丢失,且占用宝贵的 RAM 资源。
2.1.3 基于网络的 RootFS
通过 NFS(Network File System)挂载远程服务器上的目录作为 RootFS,常用于开发调试阶段,方便快速更新文件系统内容。
2.2 基于文件系统格式的分类
2.2.1 传统文件系统
- ext2/ext3/ext4:Linux 下最常用的文件系统,适合 eMMC、SD 卡等块设备。
- FAT32:兼容性好,但不支持 Linux 权限管理。
2.2.2 Flash 专用文件系统
- JFFS2(Journaling Flash File System 2):较早的 Flash 文件系统,支持压缩和磨损均衡。
- YAFFS2(Yet Another Flash File System 2):专为 NAND Flash 设计,性能较好。
- UBIFS(Unsorted Block Image File System):目前最流行的 Flash 文件系统,配合 UBI 层使用,性能和可靠性都很好。
2.2.3 只读压缩文件系统
- SquashFS:高压缩比的只读文件系统,常用于嵌入式系统以节省存储空间。
- CramFS:较早的压缩文件系统,已逐渐被 SquashFS 取代。
3. RootFS 的目录结构
一个标准的 Linux RootFS 遵循 FHS(Filesystem Hierarchy Standard)规范,主要包含以下目录:
3.1 核心目录
- /bin:存放基本的用户命令,如 ls、cp、cat 等,这些命令在单用户模式下也需要使用。
- /sbin:存放系统管理命令,如 ifconfig、reboot 等,通常只有 root 用户才能执行。
- /lib:存放系统库文件和内核模块,如 libc.so、ld-linux.so 等。
- /etc:存放系统配置文件,如 inittab、fstab、network 配置等。
- /dev:存放设备文件,如/dev/ttyS0、/dev/mtdblock0 等。
3.2 可选目录
- /usr:存放用户程序和数据,包含/usr/bin、/usr/lib、/usr/share 等子目录。
- /var:存放经常变化的文件,如日志文件、临时文件等。
- /tmp:存放临时文件,通常挂载为 tmpfs(基于 RAM)。
- /proc:虚拟文件系统,提供内核和进程信息的接口。
- /sys:虚拟文件系统,提供设备和驱动信息的接口。
- /home:用户主目录。
- /root:root 用户的主目录。
- /mnt:临时挂载点。
- /opt:可选应用程序的安装目录。
在嵌入式系统中,为了节省空间,通常会精简目录结构。
比如将/usr/bin 链接到/bin,将/usr/lib 链接到/lib 等。
4. 制作 RootFS 的方法
4.1 使用 BusyBox 制作最小 RootFS
BusyBox 是嵌入式 Linux 中最常用的工具集,它将数百个常用命令集成到一个可执行文件中,大大减小了文件系统的体积。
4.1.1 编译 BusyBox
# 下载BusyBox源码
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xjf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
# 配置BusyBox
make menuconfig
# 在配置界面中选择:
# Settings -> Build Options -> Build BusyBox as a static binary (选中)
# Settings -> Installation Options -> 设置安装路径
# 编译并安装
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
make install
4.1.2 创建基本目录结构
cd /path/to/rootfs
mkdir -p bin sbin etc dev proc sys tmp lib usr/bin usr/sbin usr/lib var home root mnt
# 复制BusyBox
cp -a /path/to/busybox/_install/* .
# 创建设备节点(需要root权限)
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
4.1.3 添加启动脚本
创建/etc/inittab 文件:
::sysinit:/etc/init.d/rcS
::respawn:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
创建/etc/init.d/rcS 文件:
#!/bin/sh
# 挂载proc和sys文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
# 挂载devtmpfs
mount -t devtmpfs none /dev
# 设置主机名
hostname myboard
# 配置网络(根据实际情况修改)
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1
echo "System initialization completed"
记得给脚本添加执行权限:
chmod +x etc/init.d/rcS
4.2 使用 Buildroot 制作完整 RootFS
Buildroot 是一个自动化构建工具,可以自动下载、编译、安装各种软件包,生成完整的 RootFS。
# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git
cd buildroot
# 选择预配置(以树莓派为例)
make raspberrypi3_defconfig
# 或者自定义配置
make menuconfig
# 开始构建
make
# 生成的文件系统在output/images目录下
Buildroot 的优点是功能强大、软件包丰富,缺点是首次编译时间较长,需要下载大量源码包。
4.3 使用 Yocto 制作 RootFS
Yocto 是更加专业的嵌入式 Linux 构建系统,适合大型项目和产品化开发。
它使用 BitBake 作为构建引擎,支持层(Layer)的概念,可以方便地管理不同的配置和软件包。
# 下载Poky(Yocto的参考发行版)
git clone git://git.yoctoproject.org/poky
cd poky
# 初始化构建环境
source oe-init-build-env
# 编辑配置文件conf/local.conf,设置目标机器
# MACHINE = "qemuarm"
# 构建最小镜像
bitbake core-image-minimal
# 或构建完整镜像
bitbake core-image-full-cmdline
5. RootFS 的打包和烧写
5.1 制作文件系统镜像
根据目标文件系统类型的不同,打包方法也不同。
5.1.1 制作 ext4 镜像
# 创建空镜像文件(大小为100MB)
dd if=/dev/zero of=rootfs.ext4 bs=1M count=100
# 格式化为ext4
mkfs.ext4 rootfs.ext4
# 挂载镜像
sudo mkdir /mnt/rootfs
sudo mount -o loop rootfs.ext4 /mnt/rootfs
# 复制文件系统内容
sudo cp -a /path/to/rootfs/* /mnt/rootfs/
# 卸载
sudo umount /mnt/rootfs
5.1.2 制作 UBIFS 镜像
# 创建UBIFS镜像
mkfs.ubifs -r /path/to/rootfs -m 2048 -e 126976 -c 1024 -o rootfs.ubifs
# 创建UBI镜像
ubinize -o rootfs.ubi -m 2048 -p 128KiB ubinize.cfg
其中 ubinize.cfg 内容如下:
[ubifs]
mode=ubi
image=rootfs.ubifs
vol_id=0
vol_size=50MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize
5.1.3 制作 SquashFS 镜像
mksquashfs /path/to/rootfs rootfs.squashfs -comp xz
5.2 烧写到目标板
5.2.1 使用 U-Boot 烧写
通过 TFTP 下载镜像并烧写到 Flash:
# 在U-Boot命令行中
tftp 0x80000000 rootfs.ubi
nand erase.part rootfs
nand write 0x80000000 rootfs ${filesize}
5.2.2 使用 dd 命令烧写到 SD 卡
sudo dd if=rootfs.ext4 of=/dev/sdb2 bs=1M
sync
5.2.3 使用 fastboot 烧写
fastboot flash rootfs rootfs.ext4
6. 常见问题和解决方案
6.1 内核无法挂载 RootFS
问题现象:系统启动时出现"Kernel panic - not syncing: VFS: Unable to mount root fs"错误。
可能原因:
- 内核配置中没有编译对应的文件系统支持
- 内核启动参数中 root 设备指定错误
- 文件系统损坏
- 设备驱动未正确加载
解决方法:
- 检查内核配置,确保编译了对应的文件系统支持(如 ext4、ubifs 等)
- 检查 U-Boot 传递给内核的 bootargs 参数,确保 root=/dev/xxx 正确
- 重新制作文件系统镜像
- 检查存储设备驱动是否正常工作
6.2 init 进程启动失败
问题现象:内核成功挂载 RootFS,但无法启动 init 进程。
可能原因:
- /sbin/init 或/init 文件不存在或没有执行权限
- init 程序依赖的库文件缺失
- init 程序架构与内核不匹配(如内核是 ARM,但 init 是 x86)
解决方法:
# 检查init文件是否存在
ls -l /sbin/init
# 检查依赖库
arm-linux-gnueabihf-readelf -d /sbin/init | grep NEEDED
# 确保所有依赖库都在/lib目录下
6.3 设备节点无法访问
问题现象:无法访问/dev 下的设备节点,如串口、网卡等。
解决方法:
- 确保内核配置中启用了 devtmpfs
- 在启动脚本中挂载 devtmpfs:
mount -t devtmpfs none /dev
- 或者使用 mdev(BusyBox 提供)动态创建设备节点:
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
6.4 文件系统空间不足
问题现象:系统运行一段时间后,出现"No space left on device"错误。
解决方法:
- 增大文件系统镜像的大小
- 清理/tmp、/var/log 等目录下的临时文件
- 将/tmp 挂载为 tmpfs,避免占用 Flash 空间:
mount -t tmpfs -o size=10M tmpfs /tmp
- 使用只读文件系统(如 SquashFS)+ 可写分区(如 UBIFS)的组合方案
7. RootFS 优化技巧
7.1 减小文件系统体积
在嵌入式系统中,存储空间通常比较紧张,因此需要尽可能减小 RootFS 的体积。
7.1.1 精简 BusyBox
在配置 BusyBox 时,只选择必需的命令和功能。
比如如果不需要网络功能,可以去掉 ping、wget 等命令。
7.1.2 裁剪库文件
使用 strip 命令去除库文件和可执行文件中的调试信息:
arm-linux-gnueabihf-strip lib/*.so
arm-linux-gnueabihf-strip bin/*
arm-linux-gnueabihf-strip sbin/*
7.1.3 使用压缩文件系统
使用 SquashFS 等压缩文件系统,可以将文件系统体积压缩到原来的 1/3 甚至更小。
7.1.4 删除不必要的文件
删除文档、示例、头文件等开发相关的文件:
rm -rf usr/share/doc
rm -rf usr/share/man
rm -rf usr/include
7.2 提高启动速度
7.2.1 使用并行启动
在 init 脚本中,将一些不相互依赖的服务并行启动:
service1 &
service2 &
service3 &
wait
7.2.2 延迟加载非关键服务
将一些非关键服务延迟到系统启动完成后再加载,优先保证核心功能可用。
7.2.3 使用 initramfs
将 RootFS 打包成 initramfs,直接加载到 RAM 中运行,可以大幅提高启动速度和运行性能。
7.3 提高系统可靠性
7.3.1 使用只读根文件系统
将 RootFS 挂载为只读,可以防止意外断电导致文件系统损坏:
mount -o remount,ro /
需要写入数据时,可以使用 tmpfs 或单独的可写分区。
7.3.2 使用 UBIFS 的原子操作
UBIFS 支持原子操作,可以保证在断电时数据的一致性。
在关键数据写入后,调用 sync 确保数据已写入 Flash:
int fd = open("/data/config.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, data, len);
fsync(fd); // 确保数据写入存储设备
close(fd);
sync(); // 同步整个文件系统
7.3.3 实现看门狗机制
在应用程序中实现看门狗功能,定期喂狗,防止系统死机:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>
int main() {
int fd = open("/dev/watchdog", O_WRONLY);
if (fd < 0) {
perror("open watchdog failed");
return -1;
}
// 设置超时时间为30秒
int timeout = 30;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
while (1) {
// 执行业务逻辑
do_business();
// 喂狗
ioctl(fd, WDIOC_KEEPALIVE, 0);
sleep(10); // 每10秒喂一次狗
}
close(fd);
return 0;
}
8. 总结
RootFS 是嵌入式 Linux 系统的核心组成部分,理解和掌握 RootFS 的制作、优化和调试技巧,对于嵌入式 Linux 开发至关重要。
本文从 RootFS 的基本概念出发,详细介绍了 RootFS 的类型、目录结构、制作方法、打包烧写以及常见问题的解决方案,最后给出了一些实用的优化技巧。
在实际项目中,我们需要根据具体的硬件平台、应用场景和性能要求,选择合适的文件系统类型和制作方法。
对于初学者,建议从 BusyBox 开始,手动制作一个最小的 RootFS,这样可以更深入地理解 Linux 系统的启动过程和文件系统的组织结构。
随着经验的积累,再逐步使用 Buildroot、Yocto 等自动化工具,提高开发效率。
希望这篇文章能帮助大家更好地理解和使用 RootFS,在嵌入式 Linux 开发的道路上少走弯路。
如果有任何问题,欢迎在评论区留言交流!
更多编程学习资源