RootFS文件系统

7 阅读12分钟

大家好,我是良许。

最近在做嵌入式 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)。

这个过程是系统从内核空间过渡到用户空间的关键步骤。

内核启动的大致流程是这样的:

  1. Bootloader 加载内核到内存并启动
  2. 内核初始化硬件、内存管理、进程调度等
  3. 内核挂载 RootFS
  4. 内核启动 init 进程(PID 为 1)
  5. 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"错误。

可能原因

  1. 内核配置中没有编译对应的文件系统支持
  2. 内核启动参数中 root 设备指定错误
  3. 文件系统损坏
  4. 设备驱动未正确加载

解决方法

  • 检查内核配置,确保编译了对应的文件系统支持(如 ext4、ubifs 等)
  • 检查 U-Boot 传递给内核的 bootargs 参数,确保 root=/dev/xxx 正确
  • 重新制作文件系统镜像
  • 检查存储设备驱动是否正常工作

6.2 init 进程启动失败

问题现象:内核成功挂载 RootFS,但无法启动 init 进程。

可能原因

  1. /sbin/init 或/init 文件不存在或没有执行权限
  2. init 程序依赖的库文件缺失
  3. 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 开发的道路上少走弯路。

如果有任何问题,欢迎在评论区留言交流!

更多编程学习资源