4、BusyBox编译与安装:make与make install,生成_install目录,分析生成的文件结构

好,咱们接着往下走。上一节我们把BusyBox的配置菜单给捋了一遍,你大概知道该勾什么、不该勾什么了。那配置完了,接下来就是真刀真枪地编译和安装了。

说实话,我第一次编译BusyBox的时候,心里还挺没底的。那时候刚接触嵌入式,总觉得「make」一下就能出个文件系统,哪有这么简单?结果还真就这么简单。不过,简单归简单,里面的门道可不少。我今天就把我踩过的坑、积累的经验,一股脑倒给你。

4.1 编译:make 命令的两种用法

配置完成后,执行 make 就能开始编译。但这里有个小细节——你得先确认你的交叉编译工具链已经设置好了。

注意: 如果你是在PC上编译给ARM板子用,千万别忘了设置 CROSS_COMPILE 环境变量。我见过太多人直接敲 make,结果编译出一堆x86的二进制文件,放到板子上直接Segmentation fault。

我个人习惯这样编译:

make CROSS_COMPILE=arm-linux-gnueabihf- -j4

解释一下:

  • CROSS_COMPILE:指定交叉编译工具链的前缀。比如你的工具链叫 arm-linux-gnueabihf-gcc,那前缀就是 arm-linux-gnueabihf-。
  • -j4:并行编译,4个任务同时跑。这个数字一般设为你CPU核心数的1.5到2倍。我笔记本是4核8线程,通常用 -j6 或 -j8。

如果你不想每次敲命令都带 CROSS_COMPILE,也可以在Makefile里直接写死。但我建议你别这么干——万一你换个平台,改起来麻烦。

编译过程大概持续一两分钟。你会看到屏幕上刷刷地滚过一堆编译信息。如果一切顺利,最后会生成一个 busybox 可执行文件。你可以用 file 命令看看它是不是ARM架构的:

file busybox
busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, stripped

看到「ARM」字样,心里就踏实了。

4.2 安装:make install 干了什么?

编译完 busybox 可执行文件,下一步就是安装。注意,这里的「安装」不是装到你的PC系统里,而是生成一个根文件系统的骨架。

执行:

make install CONFIG_PREFIX=./_install

CONFIG_PREFIX 指定安装的目标目录。如果不指定,默认就是当前目录下的 _install。我个人习惯显式指定,避免搞混。

安装过程很快,几秒钟就完事。然后你会看到一个 _install 目录被创建出来。这个目录,就是咱们根文件系统的雏形。

小技巧: 如果你用的是静态编译(配置里选了 CONFIG_STATIC),那 busybox 二进制文件会大一些,但好处是不需要额外的动态库。我在做最小系统时,特别喜欢用静态编译,省去一堆库文件的麻烦。

4.3 分析 _install 目录的文件结构

好,现在咱们来看看这个 _install 目录里到底有什么。用 tree 命令看一下:

tree -L 2 _install/
_install/
├── bin
│   ├── busybox
│   ├── cat
│   ├── chmod
│   ├── cp
│   ├── date
│   ├── echo
│   ├── grep
│   ├── kill
│   ├── ln
│   ├── ls
│   ├── mkdir
│   ├── mount
│   ├── mv
│   ├── ps
│   ├── rm
│   ├── sh
│   ├── sleep
│   ├── sync
│   ├── tar
│   ├── touch
│   └── ... (还有很多)
├── sbin
│   ├── halt
│   ├── init
│   ├── insmod
│   ├── reboot
│   ├── swapon
│   └── ...
├── usr
│   ├── bin
│   │   ├── [ 
│   │   ├── awk
│   │   ├── cut
│   │   ├── find
│   │   ├── head
│   │   ├── tail
│   │   ├── test
│   │   ├── wc
│   │   └── ...
│   └── sbin
│       ├── chroot
│       ├── telnetd
│       ├── udhcpc
│       └── ...
└── linuxrc -> bin/busybox

嗯,结构很清晰。我来逐个分析一下:

4.3.1 bin/ 目录

这是最核心的目录。里面放着最基本的用户命令。注意看,这些命令其实都是指向 busybox 的符号链接。不信你试试:

ls -l _install/bin/ls
lrwxrwxrwx 1 root root 14 Mar 15 10:00 _install/bin/ls -> /bin/busybox

看到了吧?ls 其实是个软链接,指向 /bin/busybox。当你执行 ls 时,实际执行的是 busybox,然后 busybox 根据 argv[0] 来判断你要执行哪个命令。

这种设计有什么好处?说白了就是省空间。一个 busybox 二进制文件包含了上百个命令,比每个命令单独一个文件省下好几MB。对于只有几MB Flash的嵌入式设备来说,这太重要了。

4.3.2 sbin/ 目录

这个目录放的是系统管理命令。比如 init、halt、reboot 这些。注意,init 是系统启动的第一个用户进程,它的作用可大了。我后面会专门讲 init 进程的启动流程。

4.3.3 usr/ 目录

这个目录下又分了 bin 和 sbin。按照传统的Unix文件系统层次结构,/usr 是放用户程序和数据的地方。但在BusyBox里,它其实也是符号链接到 busybox 的。

你可能会问:为什么要把命令分散到 bin、sbin、usr/bin、usr/sbin 这么多目录?

嗯,这其实是历史遗留问题。早期Unix系统,/bin 放的是系统启动时必须的命令,/sbin 放的是系统管理命令,/usr/bin 放的是用户安装的应用程序。虽然现在界限没那么严格了,但BusyBox还是保留了这种结构,为了兼容性。

4.3.4 linuxrc 文件

这个文件比较特殊。它是一个指向 bin/busybox 的符号链接。在嵌入式系统中,linuxrc 通常作为init进程的替代品,用于早期系统初始化。我见过有些系统直接用 linuxrc 来挂载根文件系统,然后才启动真正的 init。

4.4 文件结构背后的设计思想

咱们来总结一下。BusyBox的安装过程,本质上就是:

  1. 编译出一个 busybox 可执行文件
  2. 在 _install 目录下创建 bin/、sbin/、usr/ 等目录
  3. 为每个启用的命令创建指向 busybox 的符号链接

这种「一个二进制文件干所有事」的设计,我特别喜欢。它让根文件系统变得极其精简。你想想看,一个完整的Linux系统,光 /bin 和 /sbin 里的命令加起来就有上百个,每个都是独立的二进制文件,加起来怎么也得几十MB。而BusyBox把所有命令打包成一个文件,才几百KB。

我曾经在一个只有4MB Flash的板子上做项目。用完整的GNU工具链,根文件系统塞都塞不进去。换成BusyBox后,不仅塞进去了,还留了1MB多的空间给应用程序。这就是BusyBox的魅力。

4.5 避坑指南

我曾经踩过的坑:
  • 符号链接路径问题: 如果你在 make install 时指定了 CONFIG_PREFIX 为绝对路径,那生成的符号链接也是绝对路径。但如果你把 _install 目录打包后放到板子上,路径可能就不对了。我建议用相对路径,或者安装后手动检查一下链接。
  • 缺少动态库: 如果你用的是动态编译,别忘了把 busybox 依赖的 libc.so、ld-linux.so 等库文件也复制到根文件系统的 /lib 目录下。否则板子启动时会报 No such file or directory。
  • 权限问题: busybox 可执行文件需要有可执行权限。默认情况下 make install 会设置好,但如果你手动复制,别忘了 chmod +x。

4.6 本章小结

好了,这一章的内容就这些。我们完成了BusyBox的编译和安装,生成了 _install 目录,并分析了它的文件结构。你现在应该能回答这几个问题了:

  • make 和 make install 分别干了什么?
  • _install 目录下有哪些子目录?每个目录的作用是什么?
  • 为什么 ls、cat 这些命令都是符号链接?
  • linuxrc 文件是干什么用的?

下一章,我们会把这个 _install 目录变成一个真正能启动的根文件系统。到时候,咱们要添加设备节点、配置文件、初始化脚本……嗯,内容不少,但每一步都很有意思。

可可精品资料库