RootFS文件系统
大家好,我是良许。 最近在做嵌入式Linux项目的时候,经常有朋友问我关于RootFS的问题。 比如"为什么我的板子启动不了?""文件系统怎么制作?""initramfs和rootfs有什么区别?"等等。 今天我就系统地给大家讲讲RootFS文件系统,从原理到实践,让你彻底搞懂这个嵌入式Linux开发中的核心概念。 RootFS,全称Root File System,即根文件系统。 它是Linux系统启动后挂载的第一个文件系统,也是整个文件系统树的根节点。 所有其他的文件系统都会挂载在RootFS的某个目录下,形成一个统一的目录树结构。 在嵌入式Linux系统中,RootFS的地位尤为重要。 它包含了系统启动所需的所有基本文件,包括: 当Linux内核启动完成后,它会尝试挂载RootFS,然后执行RootFS中的第一个用户空间程序(通常是/sbin/init或/init)。 这个过程是系统从内核空间过渡到用户空间的关键步骤。 内核启动的大致流程是这样的: 如果RootFS挂载失败,内核会panic,系统无法正常启动。 这也是为什么很多初学者在移植Linux时,经常遇到"Kernel panic - not syncing: VFS: Unable to mount root fs"这样的错误。 根据存储介质的不同,RootFS可以分为以下几种类型: 2.1.1 基于Flash的RootFS 在嵌入式系统中,最常见的是将RootFS存储在Flash中。 根据Flash类型的不同,又可以细分为: 2.1.2 基于RAM的RootFS 有些场景下,RootFS会被加载到RAM中运行,这种方式称为initramfs或ramfs。 优点是读写速度快,缺点是掉电数据丢失,且占用宝贵的RAM资源。 2.1.3 基于网络的RootFS 通过NFS(Network File System)挂载远程服务器上的目录作为RootFS,常用于开发调试阶段,方便快速更新文件系统内容。 2.2.1 传统文件系统 2.2.2 Flash专用文件系统 2.2.3 只读压缩文件系统 一个标准的Linux RootFS遵循FHS(Filesystem Hierarchy Standard)规范,主要包含以下目录: 在嵌入式系统中,为了节省空间,通常会精简目录结构。 比如将/usr/bin链接到/bin,将/usr/lib链接到/lib等。 BusyBox是嵌入式Linux中最常用的工具集,它将数百个常用命令集成到一个可执行文件中,大大减小了文件系统的体积。 4.1.1 编译BusyBox 4.1.2 创建基本目录结构 4.1.3 添加启动脚本 创建/etc/inittab文件: 创建/etc/init.d/rcS文件: 记得给脚本添加执行权限: Buildroot是一个自动化构建工具,可以自动下载、编译、安装各种软件包,生成完整的RootFS。 Buildroot的优点是功能强大、软件包丰富,缺点是首次编译时间较长,需要下载大量源码包。 Yocto是更加专业的嵌入式Linux构建系统,适合大型项目和产品化开发。 它使用BitBake作为构建引擎,支持层(Layer)的概念,可以方便地管理不同的配置和软件包。 根据目标文件系统类型的不同,打包方法也不同。 5.1.1 制作ext4镜像 5.1.2 制作UBIFS镜像 其中ubinize.cfg内容如下: 5.1.3 制作SquashFS镜像 5.2.1 使用U-Boot烧写 通过TFTP下载镜像并烧写到Flash: 5.2.2 使用dd命令烧写到SD卡 5.2.3 使用fastboot烧写 问题现象:系统启动时出现"Kernel panic - not syncing: VFS: Unable to mount root fs"错误。 可能原因: 解决方法: 问题现象:内核成功挂载RootFS,但无法启动init进程。 可能原因: 解决方法: 问题现象:无法访问/dev下的设备节点,如串口、网卡等。 解决方法: 问题现象:系统运行一段时间后,出现"No space left on device"错误。 解决方法: 在嵌入式系统中,存储空间通常比较紧张,因此需要尽可能减小RootFS的体积。 7.1.1 精简BusyBox 在配置BusyBox时,只选择必需的命令和功能。 比如如果不需要网络功能,可以去掉ping、wget等命令。 7.1.2 裁剪库文件 使用strip命令去除库文件和可执行文件中的调试信息: 7.1.3 使用压缩文件系统 使用SquashFS等压缩文件系统,可以将文件系统体积压缩到原来的1/3甚至更小。 7.1.4 删除不必要的文件 删除文档、示例、头文件等开发相关的文件: 7.2.1 使用并行启动 在init脚本中,将一些不相互依赖的服务并行启动: 7.2.2 延迟加载非关键服务 将一些非关键服务延迟到系统启动完成后再加载,优先保证核心功能可用。 7.2.3 使用initramfs 将RootFS打包成initramfs,直接加载到RAM中运行,可以大幅提高启动速度和运行性能。 7.3.1 使用只读根文件系统 将RootFS挂载为只读,可以防止意外断电导致文件系统损坏: 需要写入数据时,可以使用tmpfs或单独的可写分区。 7.3.2 使用UBIFS的原子操作 UBIFS支持原子操作,可以保证在断电时数据的一致性。 在关键数据写入后,调用sync确保数据已写入Flash: 7.3.3 实现看门狗机制 在应用程序中实现看门狗功能,定期喂狗,防止系统死机: RootFS是嵌入式Linux系统的核心组成部分,理解和掌握RootFS的制作、优化和调试技巧,对于嵌入式Linux开发至关重要。 本文从RootFS的基本概念出发,详细介绍了RootFS的类型、目录结构、制作方法、打包烧写以及常见问题的解决方案,最后给出了一些实用的优化技巧。 在实际项目中,我们需要根据具体的硬件平台、应用场景和性能要求,选择合适的文件系统类型和制作方法。 对于初学者,建议从BusyBox开始,手动制作一个最小的RootFS,这样可以更深入地理解Linux系统的启动过程和文件系统的组织结构。 随着经验的积累,再逐步使用Buildroot、Yocto等自动化工具,提高开发效率。 希望这篇文章能帮助大家更好地理解和使用RootFS,在嵌入式Linux开发的道路上少走弯路。 如果有任何问题,欢迎在评论区留言交流! 更多编程学习资源1. 什么是RootFS
1.1 RootFS的基本概念
1.2 RootFS在系统启动中的作用
2. RootFS的类型
2.1 基于存储介质的分类
2.2 基于文件系统格式的分类
3. RootFS的目录结构
3.1 核心目录
3.2 可选目录
4. 制作RootFS的方法
4.1 使用BusyBox制作最小RootFS
# 下载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 installcd /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::sysinit:/etc/init.d/rcS
::respawn:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r#!/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/rcS4.2 使用Buildroot制作完整RootFS
# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git
cd buildroot
# 选择预配置(以树莓派为例)
make raspberrypi3_defconfig
# 或者自定义配置
make menuconfig
# 开始构建
make
# 生成的文件系统在output/images目录下4.3 使用Yocto制作RootFS
# 下载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-cmdline5. RootFS的打包和烧写
5.1 制作文件系统镜像
# 创建空镜像文件(大小为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# 创建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[ubifs]
mode=ubi
image=rootfs.ubifs
vol_id=0
vol_size=50MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresizemksquashfs /path/to/rootfs rootfs.squashfs -comp xz5.2 烧写到目标板
# 在U-Boot命令行中
tftp 0x80000000 rootfs.ubi
nand erase.part rootfs
nand write 0x80000000 rootfs ${filesize}sudo dd if=rootfs.ext4 of=/dev/sdb2 bs=1M
syncfastboot flash rootfs rootfs.ext46. 常见问题和解决方案
6.1 内核无法挂载RootFS
6.2 init进程启动失败
# 检查init文件是否存在
ls -l /sbin/init
# 检查依赖库
arm-linux-gnueabihf-readelf -d /sbin/init | grep NEEDED
# 确保所有依赖库都在/lib目录下6.3 设备节点无法访问
mount -t devtmpfs none /devecho /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s6.4 文件系统空间不足
mount -t tmpfs -o size=10M tmpfs /tmp7. RootFS优化技巧
7.1 减小文件系统体积
arm-linux-gnueabihf-strip lib/*.so
arm-linux-gnueabihf-strip bin/*
arm-linux-gnueabihf-strip sbin/*rm -rf usr/share/doc
rm -rf usr/share/man
rm -rf usr/include7.2 提高启动速度
service1 &
service2 &
service3 &
wait7.3 提高系统可靠性
mount -o remount,ro /int fd = open("/data/config.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, data, len);
fsync(fd); // 确保数据写入存储设备
close(fd);
sync(); // 同步整个文件系统#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. 总结