2. 构建最小rootfs

好,咱们直接进入正题。上一章聊了包依赖的底层逻辑,这一章我们来点实在的——亲手搭一个最小rootfs。说白了,就是给Linux内核准备一个“家”,让它启动后有个地方待着,能跑命令、能挂载文件系统。

我个人习惯,做嵌入式系统时,第一步永远是拿BusyBox开刀。为什么?因为它够小、够全、够灵活。你想想看,一个几兆的二进制文件,就能模拟出上百个Linux命令,这在资源受限的嵌入式环境里简直是神器。

2.1 BusyBox:一个二进制,百种命令

BusyBox的核心理念很简单:把所有常用命令打包成一个可执行文件,通过符号链接来区分调用哪个命令。比如/bin/ls其实是指向/bin/busybox的软链接,当你执行ls时,BusyBox会判断argv[0]是"ls",然后执行对应的功能。

我记得第一次接触BusyBox时,还纳闷为什么一个文件能当这么多命令用。后来看了源码才明白,它内部维护了一个命令表,每个命令对应一个函数指针。嗯,设计得相当巧妙。

核心要点: BusyBox的编译配置决定了你的rootfs能支持多少命令。配置越全,体积越大。嵌入式开发讲究“够用就好”,别一股脑全选上。

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的构建逻辑:

最小rootfs构建逻辑 BusyBox /bin 用户必备命令 ls, cp, mv, cat... /sbin 系统管理命令 init, mount, fdisk... /etc 配置文件 inittab, fstab... /lib 共享库 libc.so, ld-linux... /usr 用户程序 /usr/bin, /usr/lib... /var 可变数据 log, run, tmp... 关键:所有命令通过符号链接指向 /bin/busybox 注:/dev、/proc、/sys 通常由内核自动挂载

2.6 启动验证

目录搭好了,怎么验证它能不能用?最简单的方法是用chroot切换进去:

sudo chroot rootfs /bin/sh

如果一切正常,你会看到一个shell提示符。试试ls、pwd这些命令,看看能不能正常工作。

注意: chroot需要root权限。而且别忘了,chroot环境里没有/dev、/proc这些虚拟文件系统,有些命令可能会报错。这很正常,别慌。

嗯,到这里,一个最小rootfs就搭建完成了。虽然它很简陋,但五脏俱全。你可以在这个基础上,根据需要添加各种包和工具,逐步完善你的嵌入式系统。

记住,rootfs的构建没有标准答案。不同的硬件平台、不同的应用场景,目录结构和内容都会有差异。关键是理解每个目录的作用,以及它们之间的依赖关系。掌握了这个,你就能灵活应对各种需求。