2026年正点原子开发板移植方案——从0开始的Rootfs之路4Rootfs 目录结构创建Linux 文件系统的骨架前面的话在上一章中我们成功编译并安装了 BusyBox。如果你现在去看rootfs/nfs/目录会发现里面已经有了bin/、sbin/、usr/这些目录以及一大堆指向 BusyBox 的软链接。看起来挺热闹的对吧但是别高兴得太早。如果你现在就把这个目录扔给开发板去启动你会得到一个令人失望的结果——系统可能连启动都启动不了或者启动后什么都做不了。为什么因为一个真正可用的 Linux 系统光有命令是不够的还需要一个完整的目录结构骨架来支撑。这就好比盖房子你有了砖头和水泥BusyBox 提供的命令但还得有地基、承重墙、水电管道这些基础设施房子才能真正住人。今天这篇文章我们就来一步步搭建这个骨架。Linux 标准目录结构FHS 是什么在开始动手之前我们需要先了解一下 Linux 目录结构的标准——FHSFilesystem Hierarchy Standard文件系统层次结构标准。这个标准定义了 Linux 系统中各个目录应该放什么内容目的是让不同的 Linux 发行版有一个统一的目录结构这样软件在各个发行版之间移植起来就方便多了。FHS 定义的目录有很多但对于嵌入式 Rootfs 来说我们只需要关注其中最核心的一部分目录作用必需性/bin基本用户命令必需/sbin系统管理命令必需/etc配置文件必需/lib共享库文件必需/dev设备文件必需/proc虚拟文件系统进程信息必需/sys虚拟文件系统内核信息必需/tmp临时文件推荐/var可变数据可选/home用户主目录可选/rootroot 用户主目录可选/usr次要层级更多命令和库可选/mnt临时挂载点可选逐个目录详解每个目录是干什么的/bin基本命令目录这个目录存放的是所有用户包括普通用户和 root 用户都能使用的基本命令。BusyBox 安装后这里会有一个busybox可执行文件以及一大堆指向它的符号链接比如ls、cat、cp、mv等等。踩坑经验/bin必须独立存在不能和/usr/bin合并。为什么因为系统启动的早期阶段/usr可能还没有挂载比如usr是单独分区的时候如果基本命令放在/usr/bin里系统就没法启动了。对于嵌入式系统通常/usr不会单独分区但保持这个结构是个好习惯。/sbin系统管理命令目录sbin是 “system binary” 的缩写存放的是系统管理命令比如ifconfig、route、reboot、halt等。这些命令通常只有 root 用户才需要使用。在 BusyBox 的安装结果中/sbin和/bin里其实都是指向同一个busybox的软链接。BusyBox 不区分用户命令和系统命令但为了保持目录结构的标准性我们还是把它们分开存放。/etc配置文件目录这是整个 Rootfs 中最重要的目录之一存放系统的所有配置文件。没有配置文件很多程序就没法正常工作。对于最小化的 Rootfs/etc目录下至少需要这些文件/etc/ ├── inittab # init 进程配置BusyBox init 专用 ├── fstab # 文件系统挂载表 ├── init.d/ # 启动脚本目录 │ └── rcS # 系统初始化脚本 ├── profile # Shell 环境变量配置 ├── passwd # 用户数据库 ├── group # 用户组数据库 ├── shadow # 用户密码可选 └── networks # 网络名称数据库可选其中inittab、fstab和rcS是系统启动必需的我们后面会详细讲解。/lib共享库目录这个目录存放的是程序运行时需要的动态链接库文件。如果你编译的程序是动态链接的就需要把依赖的库文件复制到这个目录里。如何判断一个程序需要哪些库用arm-none-linux-gnueabihf-readelf命令arm-none-linux-gnueabihf-readelf-drootfs/nfs/bin/busybox|grepNEEDEDBusyBox 如果编译成静态链接CONFIG_STATICy就不需要任何库文件。但如果你要添加其他程序大概率需要库文件支持。/dev设备文件目录这个目录存放的是设备文件是 Linux 和 Unix 系统的一个特色——“一切皆文件”。在 Linux 中硬件设备也被抽象成文件程序可以通过读写这些文件来操作硬件。/dev目录下有一些关键的设备文件是系统启动必需的我们稍后会详细讲解如何创建它们。/proc进程信息虚拟文件系统/proc是一个虚拟文件系统它不是存储在硬盘上的真实文件而是内核在内存中动态生成的。通过读取/proc下的文件可以查看系统的各种信息比如 CPU 信息、内存信息、进程列表等。这个目录不需要手动创建任何文件只需要挂载proc文件系统即可mount-tproc proc /proc/sys内核对象虚拟文件系统/sys也是一个虚拟文件系统用于导出内核对象kobjects的层次结构。它是 Linux 2.6 内核引入的主要用于设备管理和电源管理。同样这个目录不需要手动创建文件只需要挂载mount-tsysfs sysfs /sys/tmp临时文件目录这个目录存放临时文件。由于嵌入式系统通常使用 Flash 存储频繁写入会缩短 Flash 寿命所以建议把/tmp挂载成tmpfs内存文件系统mount-ttmpfs tmpfs /tmp这样/tmp里的文件实际上存储在内存中重启后会丢失但不会损耗 Flash。/usr次要层级/usr目录结构是 FHS 中比较复杂的一部分。它的设计理念是/下面的内容是系统启动必需的而/usr下面的内容是系统启动后用户空间程序需要的。对于嵌入式 Rootfs/usr通常包含/usr/ ├── bin/ # 非必需的用户命令 ├── sbin/ # 非必需的系统命令 └── lib/ # 非必需的共享库BusyBox 安装后会在/usr下面创建一些目录但大多数情况下是空的。设备文件创建mknod 命令详解设备文件的创建使用mknod命令基本语法是mknod[选项]设备文件名{b|c|p}主设备号 次设备号b块设备block device比如硬盘、Flashc字符设备character device比如串口、键盘pFIFO 管道主设备号标识设备驱动程序次设备号标识同一个驱动程序下的不同设备踩坑经验在支持devtmpfs的内核2.6.32 以后中内核会自动创建大部分基础设备文件。但对于嵌入式系统尤其是最小化配置的内核手动创建一些关键设备文件是个好习惯。/dev 目录下的关键设备下面这些设备文件是系统启动和运行时最常用的控制台设备/dev/consolemknod-m600console c51这是系统控制台设备内核日志和启动信息会输出到这里。如果缺少这个设备系统启动时可能会看到 “Warning: unable to open an initial console” 的错误。null 设备/dev/nullmknod-m666null c13这是一个黑洞设备写入的数据会被丢弃。经常用来丢弃不需要的输出或者作为需要输出但不关心结果的程序的输出目标。zero 设备/dev/zeromknod-m666zero c15这个设备会源源不断地输出零字节。经常用来清空文件或者初始化数据。随机数设备/dev/random和/dev/urandommknod-m444random c18mknod-m444urandom c19这两个设备提供随机数。/dev/random是真随机数从硬件熵池获取熵耗尽会阻塞/dev/urandom是伪随机数不会阻塞。对于嵌入式系统通常使用/dev/urandom。内存设备/dev/memmknod-m640mem c11这个设备提供对物理内存的访问。某些调试工具如devmem需要它。tty 设备/dev/tty*mknod-m666ttyc50mknod-m620tty0 c40mknod-m620tty1 c41# ... 更多虚拟终端tty是控制台终端设备的统称tty0、tty1等是虚拟终端设备。loop 设备/dev/loop*mknod-m640loop0 b70mknod-m640loop1 b71# ... 更多 loop 设备loop 设备用来把普通文件模拟成块设备比如挂载 ISO 镜像文件。零内存设备/dev/zeromknod-m666full c17这是一个永远满的设备写入永远不会失败读取永远得到满的数据。可以用来测试磁盘满的情况。配置文件模板/etc/inittabinit 进程配置inittab是 BusyBoxinit进程的配置文件定义了系统启动时的各种行为。格式是id:runlevels:action:process一个最小化的配置示例# /etc/inittab - init process configuration# 系统初始化::sysinit:/etc/init.d/rcS# 启动 gettyaskfirst 启动前提示用户按回车console::askfirst:-/bin/sh# 重启处理::restart:/sbin/init# CtrlAltDel 处理::ctrlaltdel:/sbin/reboot# 关机时的操作::shutdown:/bin/umount-a-r::shutdown:/sbin/swapoff-a各字段解释id设备名比如console现代 init 通常忽略这个字段runlevels运行级别BusyBox init 不支持留空action触发条件sysinit系统启动时最先执行的脚本wait执行后等待进程结束once执行一次不等待respawn进程结束后自动重启askfirst类似respawn但启动前提示用户按回车shutdown关机时执行restart重启 init 时执行ctrlaltdel按下 CtrlAltDel 时执行process要执行的程序或脚本踩坑经验askfirst这个选项很实用它可以防止启动时的日志输出把登录提示淹没。用户需要按一下回车才会看到 shell 提示符。/etc/fstab文件系统挂载表fstab定义了系统启动时需要挂载的文件系统#file system mount point type options dump passproc /proc proc defaults00tmpfs /tmp tmpfs defaults00sysfs /sys sysfs defaults00devpts /dev/pts devpts defaults00各字段解释file system要挂载的文件系统或设备mount point挂载点目录type文件系统类型proc、sysfs、tmpfs、devpts等options挂载选项多个选项用逗号分隔dumpdump 备份工具的设置0 表示不备份passfsck 检查顺序0 表示不检查/etc/init.d/rcS系统初始化脚本这是系统启动时执行的第一个脚本负责基本的初始化工作#!/bin/sh## System initialization script#PATH/sbin:/bin:/usr/sbin:/usr/bin:$PATHLD_LIBRARY_PATH$LD_LIBRARY_PATH:/lib:/usr/libexportLD_LIBRARY_PATH# 挂载 fstab 中定义的所有文件系统mount-a# 创建并挂载 devpts伪终端支持mkdir-p/dev/ptsmount-tdevpts devpts /dev/pts# 使用 mdev 创建设备节点mdev-s# 配置网络接口可选ifconfiglo127.0.0.1 up# 打印欢迎信息echoechoWelcome to i.MX6ULL Embedded Linux!echo脚本解释设置PATH和LD_LIBRARY_PATH环境变量执行mount -a挂载fstab中定义的所有文件系统创建并挂载/dev/pts这是伪终端文件系统SSH 等程序需要执行mdev -smdev是 BusyBox 的设备管理工具-s选项表示扫描/sys目录并创建相应的设备文件启动本地回环网络接口打印欢迎信息踩坑经验别忘了给这个脚本添加可执行权限chmodx rootfs/nfs/etc/init.d/rcS/etc/profileShell 环境配置这个文件在用户登录时被执行用于设置 Shell 环境# /etc/profile - System-wide environment settings# 设置路径exportPATH/bin:/sbin:/usr/bin:/usr/sbin# 设置库路径exportLD_LIBRARY_PATH/lib:/usr/lib# 设置提示符exportPS1\u\h:\w\$ # 设置语言环境exportLANGC# 定义一些有用的别名aliasllls -laliaslals -Aalias..cd ..# 用户登录时显示系统信息echoechoSystem uptime:uptimeecho/etc/passwd用户数据库即使是最小化的嵌入式系统也建议有一个基本的passwd文件root:x:0:0:root:/root:/bin/sh nobody:x:65534:65534:nobody:/var/empty:/bin/false格式用户名:密码:UID:GID:用户信息:主目录:Shell密码字段存放x表示密码在/etc/shadow文件中如果不用 shadow可以存放加密后的密码对于最小化系统可以直接把密码字段留空或设置为*这样就不需要密码了/etc/group用户组数据库root:x:0: nogroup:x:65534:完整的目录创建脚本下面是一个完整的脚本可以自动创建所有必需的目录和文件#!/bin/bash## create_rootfs_structure.sh# 创建最小化 Rootfs 目录结构#set-eROOTFS_DIRrootfs/nfsechoCreating Rootfs directory structure...# 创建基础目录结构mkdir-p${ROOTFS_DIR}/binmkdir-p${ROOTFS_DIR}/sbinmkdir-p${ROOTFS_DIR}/etc/init.dmkdir-p${ROOTFS_DIR}/libmkdir-p${ROOTFS_DIR}/usr/binmkdir-p${ROOTFS_DIR}/usr/sbinmkdir-p${ROOTFS_DIR}/usr/libmkdir-p${ROOTFS_DIR}/devmkdir-p${ROOTFS_DIR}/procmkdir-p${ROOTFS_DIR}/sysmkdir-p${ROOTFS_DIR}/tmpmkdir-p${ROOTFS_DIR}/mntmkdir-p${ROOTFS_DIR}/varmkdir-p${ROOTFS_DIR}/rootmkdir-p${ROOTFS_DIR}/homemkdir-p${ROOTFS_DIR}/dev/ptsechoCreating device files...# 创建关键设备文件mknod-m600${ROOTFS_DIR}/dev/consolec51mknod-m666${ROOTFS_DIR}/dev/nullc13mknod-m666${ROOTFS_DIR}/dev/zeroc15mknod-m444${ROOTFS_DIR}/dev/randomc18mknod-m444${ROOTFS_DEV}/dev/urandomc19mknod-m640${ROOTFS_DIR}/dev/memc11mknod-m666${ROOTFS_DIR}/dev/ttyc50mknod-m620${ROOTFS_DIR}/dev/tty0c40mknod-m666${ROOTFS_DIR}/dev/fullc17echoCreating configuration files...# 创建 inittabcat${ROOTFS_DIR}/etc/inittabEOF # /etc/inittab - init process configuration # System initialization ::sysinit:/etc/init.d/rcS # Console getty (askfirst prompt before starting shell) console::askfirst:-/bin/sh # Restart handling ::restart:/sbin/init # CtrlAltDel handling ::ctrlaltdel:/sbin/reboot # Shutdown actions ::shutdown:/bin/umount -a -r ::shutdown:/sbin/swapoff -a EOF# 创建 fstabcat${ROOTFS_DIR}/etc/fstabEOF #file system mount point type options dump pass proc /proc proc defaults 0 0 tmpfs /tmp tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 devpts /dev/pts devpts defaults 0 0 EOF# 创建 rcScat${ROOTFS_DIR}/etc/init.d/rcSEOF #!/bin/sh # # System initialization script # PATH/sbin:/bin:/usr/sbin:/usr/bin:$PATH LD_LIBRARY_PATH$LD_LIBRARY_PATH:/lib:/usr/lib export LD_LIBRARY_PATH # Mount all filesystems specified in fstab mount -a # Create and mount devpts for pseudo-terminal support mkdir -p /dev/pts mount -t devpts devpts /dev/pts # Populate /dev with device nodes mdev -s # Configure loopback interface ifconfig lo 127.0.0.1 up # Print welcome message echo echo Welcome to i.MX6ULL Embedded Linux! echo EOFchmodx${ROOTFS_DIR}/etc/init.d/rcS# 创建 profilecat${ROOTFS_DIR}/etc/profileEOF # /etc/profile - System-wide environment settings export PATH/bin:/sbin:/usr/bin:/usr/sbin export LD_LIBRARY_PATH/lib:/usr/lib export PS1\u\h:\w\$ export LANGC # Useful aliases alias llls -l alias lals -A alias ..cd .. EOF# 创建 passwdcat${ROOTFS_DIR}/etc/passwdEOF root:x:0:0:root:/root:/bin/sh nobody:x:65534:65534:nobody:/var/empty:/bin/false EOF# 创建 groupcat${ROOTFS_DIR}/etc/groupEOF root:x:0: nogroup:x:65534: EOFechoRootfs directory structure created successfully!echoechoDirectory structure:ls-lR${ROOTFS_DIR}把这个脚本保存为create_rootfs_structure.sh然后运行chmodx create_rootfs_structure.sh ./create_rootfs_structure.sh验证目录结构创建完成后可以验证一下目录结构是否正确# 查看目录结构tree-L2rootfs/nfs/# 如果没有 tree 命令用 find 也可以findrootfs/nfs/-typed|sort# 查看设备文件ls-lrootfs/nfs/dev/# 查看配置文件catrootfs/nfs/etc/inittabcatrootfs/nfs/etc/fstab下一步NFS 网络启动踩坑现在我们已经创建了一个完整的 Rootfs 目录结构。但是这个 Rootfs 还在我们的开发机上怎么让开发板使用它呢最简单的方法是使用 NFS网络文件系统让开发板通过网络挂载这个目录作为根文件系统。这样我们就不需要每次修改后都把 Rootfs 烧录到开发板的存储设备上开发效率大大提高。但是NFS 网络启动的配置过程也是各种坑——U-Boot 的bootargs怎么写、NFS 服务端怎么配置、Windows 防火墙怎么设置、各种挂载失败的错误怎么排查……下一章我们将详细讲解 NFS 网络启动的完整配置过程以及我踩过的各种坑和对应的解决方案。准备好了吗我们继续