前面我们学习了如何使用 busybox 来构建根文件系统,但是 busybox 构建的根文件系统不齐全,很多东西需要我们自行添加,比如 lib 库文件。在我们后面的驱动开发中很多第三方软件也需要我们自己去移植,这些第三方软件有很多又依赖其他的库文件,导致移植过程非常的繁琐。本章我们来学习一下另外一种实用的根文件系统构建方法,那就是使用 buildroot 来构建根文件系统。Buildroot 是一个流行的嵌入式 Linux 系统构建工具,它可以帮助开发者自动化地构建完整的嵌入式 Linux 系统,包括文件系统、内核以及各种用户空间应用程序。直接使用busybox也能构建最小rootfs,使用buildroot也需要使用busybox来构建基础的rootfs,区别在于buildroot已经集成了很多第三方package,可以很方便的进行集成,make menuconfig简单的使能一下,就可以快速集成,不需要去研究第三方组建,复杂的编译流程,及其复杂的依赖项。注意:不要以为busybox直接就能构建根文件系统!!!这个误解非常常见,很多刚接触嵌入式 Linux 的人都会这么想,主要是名字和教程给人的错觉,咱们把边界再理清楚一点:核心一句话BusyBox不能直接一键产出完整可用的根文件系统镜像。它只产出一个主程序 busybox+ 配套软链接(ls、sh、cp、date、wpa_cli 等)。它是 rootfs 的核心组件,不是 rootfs 的构建框架。那为什么很多教程会说 “用 BusyBox 做根文件系统”?准确的意思是:以 BusyBox 为基础,手动搭建 rootfs,而不是 BusyBox 自动帮你做完。手动流程大概是这样(没有 Buildroot 的情况下):交叉编译 BusyBox,得到busybox可执行文件;手动创建一套目录:bin、sbin、etc、dev、proc、sys;自己创建软链接:ln -s busybox ls、ln -s busybox sh;自己写启动脚本:etc/inittab、etc/init.d/rcS;拷贝 C 库(libc、ld)到 lib 目录;用工具(genext2fs、cpio)打包成镜像。👉 BusyBox 只负责第 1、3 步;剩下目录、脚本、库、打包,全都要你自己来。如果你只编译了 busybox,丢到板子上,没有/etc/inittab、没有库,板子是启动不起来的。对比 BuildrootBuildroot 把上面所有步骤自动化了:它会自动:编译 BusyBox → 建目录 → 生成软链接 → 生成 inittab/rcS → 拷贝库 → 打包镜像。所以 Buildroot 才是 “构建根文件系统” 的工具,BusyBox 是它里面的一个包。一个好记的区分BusyBox:提供命令和 shell;Buildroot:把 BusyBox、内核、应用、脚本、库拼起来,做成可以烧录的固件。补充一个小坑BusyBox 自己的make menuconfig,只是选择启用哪些命令(要不要 date、要不要 wpa_cli),配置完 make,输出只有 busybox 二进制,不会生成完整 rootfs 目录和镜像。busybox这种一个可执行程序,不同名称软链接进去就执行不同的内容,这个机制是咋实现的,同一个名字还能执行不同的代码BusyBox 这个单文件,启动的时候,程序先拿到自己的「程序名(argv [0])」,根据名字分支跳转到不同的函数执行。Linux 下,进程启动时,main函数的第一个参数argv[0],就是调用它用的文件名。int main(int argc, char *argv[]) { // argv[0] 就是你执行的命令名字:"ls" / "cp" / "date" }举个直观场景你做软链接:ln -s busybox ls ln -s busybox date ln -s busybox wpa_cli文件系统上:ls、date都指向同一个busybox二进制。执行./ls内核加载 busybox 这个程序,但传给 main 的 argv [0] = "ls";BusyBox 内部查表,识别名字是ls,就跑 ls 对应的代码逻辑。执行./date同样加载同一个 busybox 二进制,argv [0] = "date";内部匹配名字,跑 date 的代码。二进制本体没有复制多份,只是入口名字不一样,代码自己分支。BusyBox 内部大概伪代码(简化版)int main(int argc, char *argv[]) { const char *cmd_name = basename(argv[0]); if (strcmp(cmd_name, "ls") == 0) { return ls_main(argc, argv); } else if (strcmp(cmd_name, "date") == 0) { return date_main(argc, argv); } else if (strcmp(cmd_name, "wpa_cli") == 0) { return wpa_cli_main(argc, argv); } // ...一大堆命令分支 else { // 直接运行 busybox 本身,打印帮助 return busybox_main(argc, argv); } }每个命令都有独立的xxx_main()函数,编译时全部链接进同一个 elf。为啥argv[0]存的是软链接的名字,那要是直接执行busybox呢?先说核心argv[0]是调用方(shell/exec 系统调用)传进去的字符串,不是内核从二进制文件里读出来的固定名字。Linux 的execve系统调用原型:int execve(const char *filename, char *const argv[], char *const envp[]);filename:内核要加载的真实文件路径(会解析软链接,找到 busybox 本体)argv数组:由调用者自己填,内核不主动改写它情况 1:直接执行 busybox./busyboxshell 调用execve:filename =./busybox(真实文件)argv[0] =./busyboxBusybox 拿到basename(argv[0])是"busybox",就进入 busybox 主逻辑,打印帮助信息。情况 2:通过软链接执行ln -s busybox ls ./lsshell 调用execve:filename =./ls,内核解析软链接,找到真实文件busybox,加载它argv[0] =./ls(shell 把你敲的命令字符串原样放进 argv [0])Busybox 取名字"ls",匹配 ls 的函数。关键点:
