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

好,咱们接着往下走。上一节我们把BusyBox的配置项都捋了一遍,现在配置已经选好了,接下来就是真刀真枪地编译和安装了。

说实话,编译BusyBox本身并不复杂,但很多人栽在细节上。我见过不少新手,make完了直接make install,结果发现系统都起不来。嗯,这里面的门道,我慢慢跟你说。

4.1 make编译:不是简单的敲个命令

配置完成后,在源码根目录下直接执行:

make

就这么简单?对,但也不完全对。我个人习惯在编译前先看一眼编译环境:

make V=1  # 显示详细的编译信息

为什么要加V=1?说白了,就是让你看到编译器到底在干什么。如果编译报错,你能第一时间定位到是哪个源文件出了问题。我在项目中遇到过好几次,因为交叉编译器版本不对,导致某些系统调用找不到,加了V=1一眼就看出问题所在。

如果你想多核加速编译,可以这样:

make -j$(nproc)

nproc会返回你机器的CPU核心数。比如8核机器,就是make -j8。但要注意,如果你是在虚拟机里编译,或者内存不大,-j开太高反而会卡死。我建议保守一点,用-j4或者-j2。

警告:如果你用的是交叉编译,别忘了在make时指定交叉编译器。比如:
make CROSS_COMPILE=arm-linux-gnueabihf-
否则默认会用gcc编译,生成的是x86的可执行文件,放到ARM板子上根本跑不起来。

4.2 make install:安装到哪是个问题

编译成功后,下一步就是安装。默认情况下:

make install

这个命令会把BusyBox安装到/usr/local目录下。但你想,我们做嵌入式系统,怎么可能装到宿主机上?所以一定要指定安装路径:

make install CONFIG_PREFIX=/path/to/your/rootfs

CONFIG_PREFIX这个变量,就是告诉BusyBox:嘿,把生成的文件都放到这个目录下去。我一般习惯创建一个空目录,比如~/my_rootfs,然后指定到这里。

小技巧:安装前最好先清空目标目录,避免旧文件干扰。可以用:
rm -rf ~/my_rootfs && mkdir ~/my_rootfs
make install CONFIG_PREFIX=~/my_rootfs

安装完成后,你会看到类似这样的输出:

  /home/user/my_rootfs/bin/busybox
  /home/user/my_rootfs/sbin/busybox
  /home/user/my_rootfs/usr/bin/busybox
  ...

嗯,这里要注意,BusyBox其实只生成一个可执行文件——busybox。其他那些ls、cp、cat之类的,都是指向busybox的符号链接。

4.3 _install目录结构分析:麻雀虽小五脏俱全

安装完成后,我们来看看_install目录(如果你没指定CONFIG_PREFIX,默认就是_install)里到底有什么。

先执行tree命令看看整体结构:

tree -L 2 ~/my_rootfs

输出大概长这样:

my_rootfs/
├── bin
│   ├── busybox
│   ├── cat -> busybox
│   ├── chmod -> busybox
│   ├── cp -> busybox
│   ├── date -> busybox
│   ├── echo -> busybox
│   ├── grep -> busybox
│   ├── gzip -> busybox
│   ├── kill -> busybox
│   ├── ln -> busybox
│   ├── ls -> busybox
│   ├── mkdir -> busybox
│   ├── mount -> busybox
│   ├── mv -> busybox
│   ├── ps -> busybox
│   ├── rm -> busybox
│   ├── sh -> busybox
│   ├── sleep -> busybox
│   ├── tar -> busybox
│   ├── touch -> busybox
│   └── ...
├── sbin
│   ├── busybox
│   ├── fdisk -> busybox
│   ├── fsck -> busybox
│   ├── halt -> busybox
│   ├── ifconfig -> busybox
│   ├── init -> busybox
│   ├── insmod -> busybox
│   ├── mdev -> busybox
│   ├── mkfs.ext2 -> busybox
│   ├── reboot -> busybox
│   ├── route -> busybox
│   ├── swapon -> busybox
│   └── ...
├── usr
│   ├── bin
│   │   ├── busybox
│   │   ├── [ -> busybox
│   │   ├── awk -> busybox
│   │   ├── cut -> busybox
│   │   ├── find -> busybox
│   │   ├── head -> busybox
│   │   ├── hexdump -> busybox
│   │   ├── tail -> busybox
│   │   ├── test -> busybox
│   │   ├── top -> busybox
│   │   ├── uniq -> busybox
│   │   ├── wc -> busybox
│   │   ├── wget -> busybox
│   │   └── ...
│   └── sbin
│       ├── busybox
│       ├── chroot -> busybox
│       ├── telnetd -> busybox
│       ├── udhcpd -> busybox
│       └── ...
└── linuxrc -> bin/busybox

这个结构,说白了就是标准Linux文件系统的精简版。我们来拆解一下:

4.3.1 bin/ 目录

存放的是最基本的用户命令。比如ls、cp、mv、cat这些,系统启动后用户马上就会用到的。注意看,sh也在这里,也就是说BusyBox自带了一个shell,虽然功能比不上bash,但嵌入式环境完全够用。

4.3.2 sbin/ 目录

系统管理命令,普通用户一般用不到,需要root权限。比如init(系统初始化进程)、ifconfig(网络配置)、fdisk(磁盘分区)、reboot(重启)等。我刚开始做嵌入式时,经常搞混bin和sbin的区别,后来记住一句话:sbin是给系统用的,bin是给人用的。

4.3.3 usr/ 目录

这个目录下又分了bin和sbin。从功能上讲,usr/bin放的是用户级的附加命令,比如awk、find、top这些;usr/sbin放的是系统级的附加服务,比如telnetd(telnet服务器)、udhcpd(DHCP服务器)。

你可能会问:为什么要把命令分到bin和usr/bin两个地方?其实这是历史遗留问题。早期Unix系统磁盘很小,/usr可以挂载到另一块磁盘上。现在嵌入式系统里,很多人干脆把bin和usr/bin合并了。我个人习惯是,如果根文件系统是只读的,就把所有命令都放到bin里,省事。

4.3.4 linuxrc 文件

这个文件很特殊。它是一个符号链接,指向bin/busybox。在系统启动时,内核会先执行linuxrc(如果存在),而不是/sbin/init。这通常用于initramfs或initrd场景,做一些早期的设备初始化工作。

核心要点:整个_install目录,真正占磁盘空间的只有busybox这一个文件。其他所有命令都是符号链接。这就是BusyBox的精髓——一个二进制文件,模拟上百个Linux命令。

4.4 验证安装是否成功

安装完了,怎么知道对不对?我一般会做两个检查:

  1. 检查busybox文件本身:用file命令看看是不是目标架构的可执行文件。
  2. 检查符号链接:随机挑几个命令,用ls -l看看它们是否指向了busybox。
# 检查文件类型
file ~/my_rootfs/bin/busybox

# 检查符号链接
ls -l ~/my_rootfs/bin/ls
ls -l ~/my_rootfs/sbin/init
ls -l ~/my_rootfs/usr/bin/top

如果一切正常,你会看到类似这样的输出:

bin/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

bin/ls -> busybox
sbin/init -> busybox
usr/bin/top -> busybox

嗯,看到ARM字样,说明交叉编译成功了。如果显示的是x86-64,那你就要回头检查CROSS_COMPILE有没有设置对了。

避坑指南:我曾经有一次编译完,怎么都启动不了。查了半天,发现是busybox是动态链接的,但根文件系统里没有放动态链接库。解决办法有两个:要么编译成静态链接(配置时选上CONFIG_STATIC),要么把对应的.so文件拷到/lib目录下。

4.5 本章知识结构图

下面这张图,帮你理清编译安装的整个流程和目录结构的关系:

BusyBox编译安装与目录结构 1. make menuconfig 2. make -j$(nproc) 3. make install CONFIG_PREFIX=... 生成 _install 目录 bin/ ls, cp, mv, cat, sh... 用户基本命令 sbin/ init, ifconfig, reboot... 系统管理命令 usr/ awk, find, top, telnetd... 附加命令与服务 linuxrc → bin/busybox 早期初始化入口 核心要点 • 所有命令都是 busybox 的符号链接,仅占用一份磁盘空间 • 编译时务必指定 CROSS_COMPILE,否则生成 x86 可执行文件 • 安装时用 CONFIG_PREFIX 指定目标目录,避免污染宿主机

这张图把整个流程串起来了。从配置到编译,再到安装,最后生成四个核心目录/文件。你每次做根文件系统时,都可以拿这张图对照着检查,看自己走到哪一步了。

好了,编译安装这一步就讲到这里。记住:make只是手段,_install目录才是我们真正要的东西。下一节,我们会基于这个目录,开始构建完整的根文件系统。

课后小练习:试着用ls -li命令查看_install目录下所有文件的inode号。你会发现,所有命令的inode号都是一样的——因为它们指向的是同一个busybox文件。这就是硬链接和符号链接的妙用。

可可精品资料库