2、构建最小rootfs:使用BusyBox构建基础文件系统、目录结构详解(/bin, /sbin, /etc, /lib, /usr, /var)
好,咱们直接进入正题。上一章聊了包依赖的底层逻辑,这一章我们来点实在的——亲手搭一个最小rootfs。说白了,就是给Linux内核准备一个“家”,让它启动后有个地方待着,能跑命令、能挂载文件系统。
我个人习惯,做嵌入式系统时,第一步永远是拿BusyBox开刀。为什么?因为它够小、够全、够灵活。你想想看,一个几兆的二进制文件,就能模拟出上百个Linux命令,这在资源受限的嵌入式环境里简直是神器。
2.1 BusyBox:一个二进制,百种命令
BusyBox的核心理念很简单:把所有常用命令打包成一个可执行文件,通过符号链接来区分调用哪个命令。比如/bin/ls其实是指向/bin/busybox的软链接,当你执行ls时,BusyBox会判断argv[0]是"ls",然后执行对应的功能。
我记得第一次接触BusyBox时,还纳闷为什么一个文件能当这么多命令用。后来看了源码才明白,它内部维护了一个命令表,每个命令对应一个函数指针。嗯,设计得相当巧妙。
2.2 构建最小rootfs的步骤
咱们一步步来。先准备好环境,然后编译BusyBox,最后手动搭建目录结构。
2.2.1 下载与配置BusyBox
# 下载源码
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xjf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
# 配置(使用默认配置,然后手动调整)
make defconfig
make menuconfig
在menuconfig里,我建议你重点关注这几项:
- Settings → Build static binary (no shared libs):选上。静态编译可以避免动态库依赖,生成的rootfs更独立。代价是体积大一点,但省心。
- Linux System Utilities → mount, umount:必选。没有它们,文件系统挂载都成问题。
- Coreutils → 常用命令:ls, cp, mv, rm, cat, echo等,按需勾选。
2.2.2 编译与安装
# 编译(-j参数根据你的CPU核心数调整)
make -j4
# 安装到指定目录
make install CONFIG_PREFIX=../rootfs
执行完make install后,../rootfs目录下就会出现bin、sbin、usr等目录。但别高兴太早,这只是一个“骨架”,很多关键目录和文件还需要手动创建。
2.3 目录结构详解
Linux的目录结构,说白了就是一套约定。每个目录该放什么,都有不成文的规矩。咱们一个一个来看。
2.3.1 /bin — 用户必备命令
/bin存放的是所有用户都能用的基本命令。比如ls、cp、mv、cat、echo等。这些命令在系统启动时就需要可用,所以不能放在挂载点之后的分区上。
BusyBox安装后,/bin里全是符号链接,指向/bin/busybox。你可以用ls -l看看:
lrwxrwxrwx 1 root root 14 Mar 15 10:00 cat -> /bin/busybox
lrwxrwxrwx 1 root root 14 Mar 15 10:00 chmod -> /bin/busybox
lrwxrwxrwx 1 root root 14 Mar 15 10:00 cp -> /bin/busybox
...
/bin/busybox这个文件,否则所有命令都会失效。我曾经在调试时手滑删过一次,结果整个系统直接瘫痪,只能重新烧录。
2.3.2 /sbin — 系统管理命令
/sbin存放的是系统管理员用的命令,比如fdisk、mkfs、mount、ifconfig等。普通用户一般用不到这些。
在最小rootfs里,/sbin通常只需要init和mount。BusyBox的init程序会作为PID 1运行,负责启动其他进程。
2.3.3 /etc — 配置文件
/etc是Linux系统的“神经中枢”。几乎所有配置文件都放在这里。对于最小rootfs,你至少需要:
- inittab:init进程的配置文件,定义运行级别和启动脚本。
- fstab:文件系统挂载表,告诉系统启动时自动挂载哪些分区。
- passwd、group:用户和组信息,即使只有一个root用户也需要。
一个最简单的inittab示例:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
这个配置的意思是:系统初始化时执行/etc/init.d/rcS脚本,然后启动一个shell等待用户输入。嗯,够简单吧?
2.3.4 /lib — 共享库
/lib存放的是动态链接库和内核模块。如果你编译BusyBox时选择了静态编译,那/lib可以很干净,只需要放内核模块就行。
但如果你用了动态编译,那就得把glibc或uClibc的库文件拷进来。怎么知道需要哪些库?用ldd命令查看:
$ ldd bin/busybox
linux-vdso.so.1 (0x00007ffe3b9f0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a1c400000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a1c800000)
看到没?libc.so.6和ld-linux-x86-64.so.2是必须的。把它们复制到rootfs的/lib目录下。
ld-linux,结果系统启动后直接报"无法加载动态链接器"的错误。记住,ld-linux是动态链接的“入口”,缺了它什么都跑不起来。
2.3.5 /usr — 用户程序和数据
/usr是Unix System Resources的缩写,但现在已经演变成存放用户级程序和数据的地方。它下面通常有/usr/bin、/usr/sbin、/usr/lib、/usr/share等子目录。
在最小rootfs里,/usr可以很精简。BusyBox默认会把一些命令安装到/usr/bin和/usr/sbin里。你只需要确保这些目录存在即可。
2.3.6 /var — 可变数据
/var存放的是运行时数据,比如日志、缓存、锁文件等。这些数据在系统运行过程中会不断变化。
对于最小rootfs,/var通常只需要几个空目录:
mkdir -p rootfs/var/log
mkdir -p rootfs/var/run
mkdir -p rootfs/var/tmp
mkdir -p rootfs/var/lock
有些系统会把/var做成tmpfs挂载点,这样重启后数据自动清空,省得手动清理。
2.4 完整的目录结构一览
好了,咱们把上面说的整合一下。一个最小rootfs的目录结构大概长这样:
rootfs/
├── bin/
│ ├── busybox
│ ├── ls -> /bin/busybox
│ ├── cp -> /bin/busybox
│ └── ... (其他符号链接)
├── sbin/
│ ├── init -> /bin/busybox
│ └── mount -> /bin/busybox
├── etc/
│ ├── inittab
│ ├── fstab
│ ├── passwd
│ └── group
├── lib/
│ ├── libc.so.6
│ └── ld-linux-x86-64.so.2
├── usr/
│ ├── bin/
│ └── sbin/
├── var/
│ ├── log/
│ ├── run/
│ ├── tmp/
│ └── lock/
├── dev/ (设备节点,通常由devtmpfs自动创建)
├── proc/ (procfs挂载点)
└── sys/ (sysfs挂载点)
你可能会问,/dev、/proc、/sys这些目录呢?它们通常由内核在启动时自动挂载,不需要手动创建。但如果你用的是静态设备节点,那就得在/dev下用mknod命令创建了。
2.5 知识体系图
下面这张SVG图,帮你理清最小rootfs的构建逻辑:
2.6 启动验证
目录搭好了,怎么验证它能不能用?最简单的方法是用chroot切换进去:
sudo chroot rootfs /bin/sh
如果一切正常,你会看到一个shell提示符。试试ls、pwd这些命令,看看能不能正常工作。
/dev、/proc这些虚拟文件系统,有些命令可能会报错。这很正常,别慌。
嗯,到这里,一个最小rootfs就搭建完成了。虽然它很简陋,但五脏俱全。你可以在这个基础上,根据需要添加各种包和工具,逐步完善你的嵌入式系统。
记住,rootfs的构建没有标准答案。不同的硬件平台、不同的应用场景,目录结构和内容都会有差异。关键是理解每个目录的作用,以及它们之间的依赖关系。掌握了这个,你就能灵活应对各种需求。