欢迎光临
我们一直在努力

RootFS文件系统

大家好,我是良许。

最近在做嵌入式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开发的道路上少走弯路。

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

更多编程学习资源

https://segmentfault.com/a/1190000047652632

未经允许不得转载:IT极限技术分享汇 » RootFS文件系统

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址