尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

BusyBox实战:手工搭建嵌入式Linux根文件系统

发布时间:2026/9/8 17:33:40

资讯中心
01
ARTICLE

BusyBox实战:手工搭建嵌入式Linux根文件系统

BusyBox实战:手工搭建嵌入式Linux根文件系统
我从一个实际项目说起吧。早些年我帮客户做一款工业网关用的主控是ARM Cortex-A7内存只有64MBFlash 256MB。系统跑起来总共占用不到15MB的空间启动时间控制在5秒以内这就是BusyBox的功劳。当时团队里有个刚从桌面Linux转过来的同事第一反应是想在板子上跑Ubuntu的core版本我直接劝退了。倒不是说Ubuntu不好而是对这个场景来说一套完整的GNU工具链和systemd体系带来的收益远小于开销。而BusyBox这一个程序几百个命令占空间小、行为可预期、定制灵活很多时候它才是嵌入式Linux里最合适的那个“操作系统外壳”。这篇博文我会把BusyBox的原理拆开讲清楚再带大家从零到一用它手工搭一套根文件系统。内容包括BusyBox的applet工作方式、编译配置的关键选项、根文件系统的目录骨架、init启动流程、集成Dropbear实现SSH登录、用NFS挂载根文件系统调试以及我在实际项目中踩过的各种坑。1. 从“跑不动的Linux”说起BusyBox到底解决什么问题1.1 一套完整的GNU工具链在嵌入式板子上有多贵你在PC上敲ls、cp、grep的时候背后对应的是coreutils、procps、grep这些独立软件包里的程序。每个程序是独立的ELF可执行文件自己带着一堆动态库依赖和配置文件。在x86服务器上这无所谓硬盘按TB算内存按GB算。但放到嵌入式板子上情况就完全不同了。拿我那块64MB内存的板子来说。一个完整的coreutils包装上去大概需要几十MB的空间而grep、sed、awk这些再来一圈Flash空间就快见底了。更重要的是这些程序在启动阶段逐个加载每个都要经过动态链接器解析、映射、初始化消耗的时间和内存积少成多对启动速度的影响非常明显。BusyBox的做法完全不同。它把几百个命令的代码逻辑塞进了一个可执行文件里在系统里你只看到一个/bin/busyboxls、cp、cat这些命令都是指向它的符号链接。当你在终端敲ls的时候内核加载的是同一个busybox程序只是启动参数不同而已。1.2 BusyBox能干什么一张命令清单看清边界BusyBox能提供的命令数量与配置选项有关完整的配置打开后大约有400多个applet。我这里整理了一份常用的命令清单方便新手对照理解它的能力边界。分类常用命令文件操作ls, cp, mv, rm, mkdir, touch, cat, tail, head, find, tar, gzip文本处理grep, sed, awk, cut, sort, uniq, wc, tr, vi进程管理ps, top, kill, killall, nice, renice, nohup, pgrep网络工具ifconfig, route, ping, wget, telnetd, netstat, nc, udhcpc, httpd系统管理mount, umount, dmesg, free, df, du, sync, mdev, init, reboot, poweroffshell环境ash, sh, msh其他实用crond, ntpd, syslogd, klogd, watchdog, fdisk, mkfs.vfat, hwclock这份清单覆盖了嵌入式Linux日常开发和运维的绝大部分需求。有一些命令我是建议额外使用完整版工具的比如dd、parted这些涉及数据恢复和分区操作的BusyBox的实现虽然能用但容错性比GNU原版弱一些。我在实际项目中如果设备上空间允许通常会单独放一个完整的e2fsprogs的e2fsck进去用于文件系统修复。1.3 不是只有小系统才用BusyBox很多人的印象是BusyBox只给单片机级别的设备用其实不然。Docker容器里的大量精简镜像底层就是BusyBox。安卓系统的调试模式里也有它的身影。工业路由器、智能汽车的车机系统、甚至一些桌面Linux的initramfs阶段都会用BusyBox作为过渡工具。原因很简单initramfs要在极小的内存环境里完成挂载真正根文件系统的任务它需要一套完整的、依赖极少的基本工具。BusyBox静态编译之后对系统几乎零依赖扔进去就能用。这种“一个二进制打天下”的特性让它在很多对体积和依赖敏感的场合成了首选。2. 瘦身成功的秘密applet机制与编译选型的底层逻辑2.1 从main函数到符号链接BusyBox的设计哲学BusyBox的源码根目录下有个文件叫libbb/appletlib.c里面藏着一个非常精巧的分发机制。当内核执行busybox的时候main函数第一件事就是检查argv[0]也就是调用这个程序时使用的名字。如果是ls就走ls的applet_main如果是cp就走cp的applet_main。这就是符号链接能工作的根本原因。源码中每个applet都有一个类似applet_main的入口点它们被统一注册在applet表里。编译的时候通过CONFIG_FEATURE_SH_STANDALONE这类选项还可以让BusyBox里的shell在找不到外部命令时回退到内置applet实现“无外部依赖”运行。有个细节值得注意BusyBox并不只靠符号链接来触发。当argv[0]显示是busybox本身的时候它会把第一个参数当作要执行的applet名比如busybox ls -l的效果和ls -l一样。这在一些特殊情况很好用——比如某个脚本里硬编码了/bin/busybox cat /etc/passwd即使cat的符号链接坏了命令依然能执行。2.2 动态链接还是静态链接一个影响一生的决定编译BusyBox时最重要的一个选择是CONFIG_STATIC是否打开。这个选项直接决定了最终生成的二进制文件是否依赖动态库。对比项动态链接默认静态链接二进制体积约800KB-1MB约1.5MB-2.5MB运行依赖需要glibc/musl等C库无任何外部依赖调试便利性可用gdb正常调试需注意strip后符号丢失适用场景Flash充足、需要节省内存initramfs、极小系统、chroot环境我个人的建议是如果不是空间特别紧张尽量使用静态链接。原因很实在嵌入式系统里动态库版本一旦升级很容易出现busybox与新libc兼容性出问题的情况。静态链接的busybox像一块砖头一样扎实扔到哪个板子上都能跑少了很多“幽灵式”的依赖困扰。有一个坑必须提醒如果你的系统里所有用户程序都依赖glibc而busybox用了musl静态编译那么busybox调用外部程序时glibc的程序依然会正常执行这不冲突。但反过来如果你用busybox的shell写脚本调用外部工具外部工具的依赖项得自己保证。所以静态编译busybox本质上不解决整个系统的依赖问题它只解决busybox自身的依赖问题。2.3 要不要开启的隐藏配置项打开make menuconfig之后密密麻麻的配置项容易让人眼花缭乱。我梳理了几个实际项目中最容易出问题的选项做一个说明。CONFIG_FEATURE_TOP_SMP_CPUtop命令显示多核CPU使用率。默认不开启时多核设备上的CPU使用率显示可能不准。CONFIG_FEATURE_MOUNT_HELPERS挂载时启用辅助工具。对NFS、FUSE这类文件系统挂载有影响建议开启。CONFIG_FEATURE_VI_REGEX_SEARCHvi编辑器支持正则搜索。不开启的话vi的搜索功能会弱很多。对在板子上临时改文件来说这个功能还挺重要的。CONFIG_ASH_JOB_CONTROLash shell的工作控制。关闭后你在终端里按CtrlC可能会失灵任务前后台切换也有问题。这个选项默认打开建议保持。CONFIG_FEATURE_EDITING_SAVEHISTORY保存shell历史记录。在调试时很实用能减少重复输入命令的烦恼。还有个值得一提的坑有些开发板厂商会给BusyBox打一堆自己的补丁然后在配置里开一些奇奇怪怪的选项。交叉编译环境的CONFIG_CROSS_COMPILER_PREFIX要填对不然后面链接阶段会莫名其妙报错。这个错误信息通常还不直观我第一次遇到的时候查了半天最后发现是工具链前缀写错了。3. 交叉编译BusyBox一套能用的配置和避坑记录3.1 用Buildroot还是纯手动两条路线怎么选搭建BusyBox环境有两条常用路线。如果你还在项目初期对工具链、内核、根文件系统的版本搭配没有把握建议直接上Buildroot。它把工具链、内核、BusyBox、整个用户空间全打包管理版本之间的兼容性被严格验证过出问题概率低很多。如果你是想深入理解BusyBox本身或者你的工具链是芯片厂商定制的、无法用Buildroot统一管理那就手动来。手动编译的流程也不复杂基本步骤就是交叉编译工具链准备好解压BusyBox源码配置编译安装到一个临时目录再把临时目录里的内容整合进根文件系统。我个人的经验是第一次玩BusyBox不建议直接上Buildroot最好是手动编译一遍。原因在于Buildroot帮你屏蔽了太多细节你最后发现自己只是敲了个make整个过程黑盒化非常严重。而手动编译一次你能清晰感知到交叉编译器、sysroot、链接过程分别做了什么这对接下来的根文件系统调试非常有帮助。3.2 手动编译的完整流程以下命令基于x86主机目标平台是ARM Cortex-A7使用通用的arm-linux-gnueabihf-工具链。实际操作时工具链前缀换成你板子对应的名字即可。# 解压源码 tar -xvf busybox-1.36.1.tar.bz2 cd busybox-1.36.1 # 先生成默认配置再在menuconfig中调整 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- menuconfig进入menuconfig之后我通常会检查这几处Settings - Build static binary (no shared libs)选中除非你有特殊理由用动态链接。Settings - Cross compiler prefix填arm-linux-gnueabihf-。Settings - Destination path for make install先空着或者填一个临时目录之后手动拷贝。Busybox Settings - Busybox Library Tuning按需微调。Networking Utilities勾选你需要的网络工具比如udhcpc。# 编译并安装到临时目录 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j8 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- install CONFIG_PREFIX/tmp/rootfs # 确认生成的关键文件 ls -l /tmp/rootfs/bin/busybox file /tmp/rootfs/bin/busyboxfile命令的输出里应该能看到statically linked说明静态编译生效了。与此同时/tmp/rootfs下会有bin、sbin、usr等目录里面全部是指向busybox的符号链接。3.3 编译阶段最容易迷惑的三类报错第一类是找不到头文件比如stdio.h: No such file or directory。这是交叉编译器没有安装到系统路径或者CONFIG_CROSS_COMPILER_PREFIX填错导致编译器直接退化成宿主编译。第二类是链接阶段报cannot find -lc说明你的工具链缺少libc库文件安装完整工具链或者配置好sysroot路径就可以了。第三类是编译过程中突然出现与时间相关的警告clock_gettime相关符号找不到这多发生在musl工具链和某些老版本BusyBox搭配的时候升级BusyBox版本或工具链版本基本能解决。如果你用芯片厂商提供的工具链可能还带有一个独立的sysroot路径。在Makefile或者环境变量里加上CFLAGS指向sysroot具体方法各家不一样。我的习惯是写进Makefile的EXTRA_CFLAGS里避免每次make clean后重配。4. 用BusyBox手工搭建根文件系统从目录骨架到启动成功4.1 根文件系统的目录骨架到底怎么搭有一句话要刻在脑子里根文件系统不是为busybox准备的是为整个Linux系统准备的。busybox只是其中的一部分。最终设备上完整的根文件系统至少要包含这些目录目录用途说明/bin基本用户命令/sbin系统管理命令/usr/bin扩展的用户命令/usr/sbin扩展的系统命令/etc配置文件/lib动态库和内核模块/dev设备节点/procproc虚拟文件系统挂载点/syssysfs虚拟文件系统挂载点/tmp临时文件/var运行时数据/rootroot用户的home目录/home普通用户home目录/mnt手动挂载点/opt可选的附加软件目录有一个细节我强烈建议新手注意在做成镜像之前先把这些目录在PC上建好再逐一核对。很多根文件系统起不来的问题不是配置错了而是缺了某个目录。比如/var下缺了/var/logsyslogd会启动失败/tmp没有可写权限很多临时文件操作会报错。ROOTFS/tmp/rootfs mkdir -p $ROOTFS/{bin,sbin,etc,lib,usr,proc,sys,tmp,var,root,home,mnt,opt,dev} chmod 1777 $ROOTFS/tmp/tmp设置1777权限属于强烈的推荐操作。不设的话普通用户进程往/tmp写文件时会以Permission denied告终。4.2 busybox --install与符号链接手动建还是自动建前面make install已经生成了一套符号链接。但如果你拿到的是一个现成的busybox二进制而没有那套链接在设备上可以直接运行/bin/busybox --install -s-s参数表示用符号链接方式安装不消耗额外Flash空间。不带-s的话busybox会把每个applet的硬链接创建出来或者直接复制一份完整busybox二进制——这显然更浪费空间。这个命令一般在最早期的手工搭阶段非常有用。你甚至可以在设备启动后在rootfs挂载阶段调用它重建全套命令。但要注意在chroot或系统尚未完全启动时--install的目标目录必须存在并且busybox自身要能被找到。有一种鸡生蛋蛋生鸡的场景你想用ls查看/bin目录但/bin/ls这个符号链接还没建——这时候直接/bin/busybox ls /bin就够了。4.3 设备节点最容易被“失踪”的基础设施设备节点的处理是老一代嵌入式工程师和新手之间一个很重要的认知分水岭。如果你习惯桌面Linux你几乎感知不到设备节点的存在因为udev自动帮你创建了。但在一个精简的嵌入式系统里udev是一个比较重的组件很多时候我们用mdev或者干脆手动创建关键节点。至少这四个设备节点是内核启动后立即需要的mknod -m 600 $ROOTFS/dev/console c 5 1 mknod -m 666 $ROOTFS/dev/null c 1 3 mknod -m 666 $ROOTFS/dev/ttyS0 c 4 64 mknod -m 666 $ROOTFS/dev/zero c 1 5/dev/console是内核打印信息的输出目标权限设置成600是常见做法。/dev/null和/dev/zero的作用不用多说很多命令和脚本依赖它们。/dev/ttyS0是串口控制台设备节点如果你的板子串口对应的是ttyS0就用这个。如果你还需要访问SD卡、USB设备可以提前放好mmcblk0、sda等节点也可以依赖后面的mdev自动创建。不过从调试的角度看我建议在根文件系统里提前把SD卡节点建好因为mdev需要内核事件触发如果开机阶段还没有uevent设备节点不会凭空出现。4.4 内核启动参数的传递从uImage到root的完整链路在引导加载U-Boot的阶段内核启动参数一般由环境变量bootargs传递。典型的bootargs长这样consolettyS0,115200 root/dev/mmcblk0p2 rootwait rwconsolettyS0,115200告诉内核串口控制台用哪个设备、什么波特率。root/dev/mmcblk0p2表示根文件系统在SD卡的第二分区。rootwait表示等待设备节点出现如果省掉这个参数早期启动阶段内核可能因为找不到根设备直接kernel panic。rw表示以读写方式挂载根文件系统这在调试时非常有用。我见过不少人卡在“内核启动到一半panic”的状态最后发现是bootargs里的console参数和实际板子的串口编号对不上。比如高通的板子可能是ttyMSM0全志的板子可能是ttyS0这个不能一概而论得看内核里的串口驱动注册名。如果内核本来就支持多个串口可以先用earlyprintk把早期日志打到所有串口上一次就能定位到底该用哪个别名。5. 启动流程的完整解剖inittab、rcS与控制台的那点事5.1 BusyBox init和SysVinit的异同当内核启动并挂载好根文件系统后它会执行/sbin/init。在BusyBox体系里init就是busybox的符号链接之一。它完成三件主要的事情读取/etc/inittab、执行系统初始化脚本、为每个控制台启动登录shell。BusyBox init的语义是SysVinit的一个子集但有很多细节差异。最典型的是对/etc/inittab的解析方式。SysVinit里可以配置各种复杂的runlevelBusyBox init简化了很多重点支持以下几种actionaction触发时机sysinit系统启动时最先执行respawn进程结束后自动重启askfirst终端上第一次按键后再启动进程wait启动后等待进程结束once启动一次进程结束后不重启ctrlaltdelCtrlAltDelete触发shutdown系统关机时执行5.2 一份可以直接抄的inittab我们项目里用的一份最简inittab长这样::sysinit:/etc/init.d/rcS console::askfirst:-/bin/sh ::restart:/sbin/init ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r解释一下每一行的意思。第一行启动时执行/etc/init.d/rcS脚本这是整个用户空间初始化的起点。第二行在console这个终端上等待用户按一次键然后启动一个login shell。-前缀表示login shell它会执行完profile相关的初始化再交给你交互提示符。第三行是init进程自己重启时的入口。第四行和第五行分别是热键重启和关机时卸载文件系统。有个非常常见的坑如果你在inittab的第一行写的是console::sysinit:/etc/init.d/rcS而你的板子的确存在/dev/console节点那么rcS执行的stdin/stdout会指向consoleshell的输出确实能看到。但如果你不指定设备或者指定了不存在的设备rcS里的所有输出会淹没在/var/log/messages里你盯着串口终端可能什么都不显示还以为系统卡死了。“cant access tty; job control turned off”这句警告为什么会出现因为它试图为当前shell分配一个控制终端却找不到可用的tty。于是shell只能以“无工作控制”模式运行CtrlC和前后台任务切换都会失灵。在开发早期如果板子只有一个串口简单粗暴的做法就是inittab里把console那个条目配上并保证/dev/console存在基本就不会有这个警告了。5.3 rcS脚本里到底该放什么/etc/init.d/rcS是整个用户空间初始化的入口。它通常是一个shell脚本按顺序做几件事挂载虚拟文件系统、配置主机名、启动mdev、初始化网络。#!/bin/sh # 挂载基础虚拟文件系统 mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t tmpfs tmpfs /tmp # 设置主机名 echo mytestboard /etc/hostname hostname -F /etc/hostname # 初始化设备管理 echo /sbin/mdev /proc/sys/kernel/hotplug mdev -s # 配置网络 ifconfig lo 127.0.0.1 up ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up注意mount -t tmpfs tmpfs /tmp这个挂在前面搭建骨架时提过。tmpfs是基于内存的文件系统数据重启即失但对嵌入式系统来说把临时文件放内存里效率高而且不伤Flash寿命是常规操作。这里有一个我要重点提醒的坑mdev -s必须在/sys挂载成功之后再跑否则-s扫描不到sysfs里的设备信息设备节点就创建不全。有些板子内核里没有CONFIG_UEVENT_HELPER那么写/proc/sys/kernel/hotplug这一步可能没有效果这种时候mdev -s就是创建设备节点的主力即使热插拔事件辅助失效冷插拔设备的节点也能靠-s补上。5.4 一个真实的启动debug案例我记得有次调试一块板子内核启动完到用户空间之后串口上只有init started之后就彻底静默了。当时的直觉是rcS卡住了于是我改了bootargs在内核启动参数里加init/bin/sh直接绕过init跳过inittab进shell。进去之后手动挂载/proc、/sys一步步排查最终发现是rcS里有个mount NFS的语句服务器没响应mount命令卡在TCP重传上。这个经验很直接当用户空间启动卡住时第一件事情不是猜而是用init/bin/sh绕过整个init体系手工逐条执行脚本命令。这让你能在最小环境里快速定位是哪一步在阻塞。6. 给BusyBox配上SSH集成Dropbear的关键细节6.1 为什么是Dropbear而不是OpenSSH嵌入式系统上做远程登录Dropbear和OpenSSH都可以但我实际项目里几乎全部用Dropbear。原因有四条。第一Dropbear的二进制体积大约是OpenSSH的三分之一左右。第二它会话建立速度更快握手时间在嵌入式设备上感知明显。第三配置项精简适合在固定环境里使用。第四它的授权协议是MIT对商用产品比较友好。Dropbear包含两部分dropbear是服务端dropbearkey是密钥生成工具。在嵌入式系统里一套最简单的部署策略是在主机上生成host key然后拷贝到开发板的/etc/dropbear目录之后启动dropbear进程即可。6.2 静态编译与libc依赖问题如果你在主机的交叉编译环境里编译Dropbear最稳妥的方式还是静态链接。注意Dropbear默认是链接动态库的配置时加--enable-static可以对可执行文件启用静态链接。此外还需要把--disable-zlib打开因为zlib的依赖对嵌入式设备来说也是一种负担而且Dropbear在无zlib情况下能正常使用。一个比较阴间的坑是如果你的整个系统都是glibc环境而Dropbear用了musl静态编译那么在上层应用通过PAM或者nsswitch查询用户信息的时候可能会遇到读取不了shadow密码的问题。Dropbear默认不依赖PAM默认用/etc/passwd和/etc/shadow验证所以纯静态编译一般问题不大。但假如你启用了--enable-pam就不得不考虑和系统的PAM模块以及动态库之间的互动这时候静态编译反而不一定是好选择。6.3 密钥、首次启动与开机自启生成host key的命令dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_keyed25519的密钥比RSA小生成速度也快适合嵌入式。生成一次以后这个密钥文件要保留好。设备重启后如果密钥变了客户端的known_hosts会提示host key不匹配影响使用体验。在rcS中集成启动逻辑# 在rcS脚本中添加 mkdir -p /etc/dropbear if [ ! -f /etc/dropbear/dropbear_ed25519_host_key ]; then /usr/sbin/dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key fi /usr/sbin/dropbear -R-R参数表示如果没有指定的host key就自动生成一个随机的临时密钥。这个选项对调试很方便因为省去了首次启动前手工生成密钥的操作。但生产环境建议还是用持久化的host key不然每次重启客户端都要确认指纹变化。6.4 用scp传文件的小细节Dropbear提供了dbclient但它和OpenSSH的scp不直接兼容。跨端传文件我一般用两种方案。方案一在PC端装一个pscpPuTTY工具集或者直接用scp -O命令但可能需要在PC端安装dropbear兼容工具。方案二在板子上跑scp命令如果BusyBox自带的scpapplet和另一端Dropbear服务端配合有问题就改用busybox nc手动传。我的实测结果是PC端用OpenSSH的scp往Dropbear服务端传偶尔能通偶尔卡住跟Dropbear版本和算法协商有关。最省事的其实是直接在PC上执行dropbear的dbclient来连板子或者用sftp的替代工具。过程有点绕但胜在稳定。7. NFS挂载根文件系统调试效率翻倍的工作笔记7.1 为什么NFS根文件系统是嵌入式开发者的加速器嵌入式开发中反复烧写Flash是个非常折磨人的过程。每改一个小脚本都要重新制作镜像、烧录、重启一次循环下来十分钟没了。NFS根文件系统的思路是板子通过以太网把PC上某个目录当作根文件系统挂载起来。这样你在PC上改代码、改配置板子重启后直接看到最新状态整个过程省掉了烧写的等待。我自己的项目里一旦板子和PC的以太网都能通调试阶段几乎都会把NFS根文件系统作为默认启动方式。只有到了发布阶段才切回Flash或者SD卡上的正式文件系统。7.2 服务器端配置的完整步骤主机PC上安装NFS服务sudo apt install nfs-kernel-server sudo mkdir -p /srv/nfs/rootfs # 将制作好的根文件系统内容拷贝到该目录 sudo cp -a /tmp/rootfs/* /srv/nfs/rootfs/修改/etc/exports/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)关键参数解释一下rw板子可读写挂载。sync同步写保证每次写操作都直接落到主机磁盘避免掉电丢数据。如果调试时不追求极限性能还是开着sync安心。no_root_squash允许板子上的root用户以root权限访问NFS文件否则默认会映射为nobody用户板子上很多操作会报Permission denied。sudo exportfs -ra sudo systemctl restart nfs-kernel-serverexportfs -ra的作用是重新导出共享目录让配置立即生效。有一种很常见的报错是mount.nfs: Operation not permitted多半是no_root_squash没写对或者NFS版本协商有问题。建议在exports里强制指定版本比如加上vers3可以避开一些较新的NFSv4权限模型导致的困惑。7.3 板子端的内核命令行配置板子的bootargs需要指定根文件系统走NFSconsolettyS0,115200 root/dev/nfs nfsroot192.168.1.100:/srv/nfs/rootfs,v3,tcp rw ip192.168.1.50:192.168.1.100::255.255.255.0::eth0:off其中root/dev/nfs是固定写法告诉内核根文件系统由NFS提供。nfsroot后面跟的是服务器IP、共享目录、NFS版本和传输协议。ip后面的格式是板子IP:服务器IP:网关:子网掩码::网卡名:off。关于ip参数有一个坑是格式里的::eth0:off最后的off表示不自动配置IP。如果你省掉整个ip参数内核会尝试用DHCP自动获取IP如果局域网里没有DHCP服务器就会卡在“Waiting for root device”上。7.4 sync、VFS和掉电安全的那点事NFS根文件系统调试最频繁出现的问题之一就是板子运行着的某些程序一直往根文件系统写数据而此时如果板子掉电数据可能丢失。VFS虚拟文件系统层位于具体文件系统之上负责把应用的write请求缓存到Page Cache里然后再由pdflush/ writeback线程择机刷入实际存储介质。这种缓冲机制对性能很有帮助但也意味着掉电瞬间数据不一定落盘。你调用sync命令能强制刷盘但你的应用如果不主动sync内核的数据要等一段时间才会真正写下去。在NFS调试时如果你发现板子上跑了一个应用改完配置文件后立即断电再上电后配置还是旧的那多半是Page Cache里的数据没来得及刷到主机。这不一定非得改程序你可以调整内核参数echo 1 /proc/sys/vm/dirty_background_ratio echo 2 /proc/sys/vm/dirty_ratio echo 100 /proc/sys/vm/dirty_writeback_centisecsdirty_writeback_centisecs单位是百分之一秒100表示每1秒唤醒一次writeback线程。这个值越小数据驻留内存的时间越短掉电丢失的风险越低但写性能会下降。实际情况中嵌入式设备掉电频繁的场景建议把dirty_writeback_centisecs调到500.5秒。7.5 NFS根文件系统的权限问题NFS挂载后板子上root用户的uid是0PC端共享目录里的文件如果属于另一个用户权限映射就可能会出现奇怪的现象。例如PC端某个目录的所有者是uid 1000板子上root用户访问这个目录由于no_root_squash的存在它能读能写但创建的新文件owner会变成uid 1000对应的用户。这在实际调试中很吓人——明明我是root创建出来的文件却属于别人。解决办法是在PC端把目录所有者也改成root或者调整共享参数加入all_squash加上anonuid、anongid的映射。8. 从实战收尾版本差异、Android对比与断舍离经验8.1 老版本BusyBox在新时代的兼容性问题项目中如果你拿到的是开发板厂商提供的旧源码包BusyBox版本还停留在1.22甚至更老这时候最好看一下里面的补丁历史。老版本的BusyBox比如1.22,在某些新内核对mount的解析支持有问题或者对新的文件系统类型如f2fs支持不完整。比较典型的是busybox v1.22.1在较新内核上ps命令读取/proc信息时可能会显示不完整某些内核版本下还会出现process information unavailable之类的输出。如果遇到这种情况优先考虑把BusyBox单独升级到比较新的稳定版比如1.36.x系列而不是去修它。旧版BusyBox另一个常见问题是对mdev的固件加载支持不完善。很多设备需要内核加载固件时hotplug事件触发/sbin/mdev但老版本mdev对firmware请求处理有bug固件永远加载不进去设备初始化失败。新版本在这方面修复很多。8.2 Android的根文件系统为什么“不用”BusyBox了顺着热词里提到Android的根文件系统这里多说一句。Android早期确实使用过BusyBox的方案但后来它构建了一个自己专门的操作系统用户空间层底层启动文件系统由system/core里的init、toolbox按需构建。Android 10之后toolbox逐渐被toybox取代。toybox的思路和BusyBox很像但它的代码更专注于Android环境下的命令实现且因为许可协议是BSD风格对于Google的AOSP整合更友好。对我们做嵌入式Linux的人有什么启发我觉得最重要的是BusyBox不是唯一的applet聚合工具也不是永远的最佳选择。如果你的产品对许可合规有特别要求或者你需要命令行为与Android生态高度一致可以考虑toybox。但就通用嵌入式Linux开发调试来说BusyBox依旧是最成熟、资料最多、社区经验最充沛的方案。8.3 我踩过几次坑之后养成的使用习惯构建根文件系统我的固定流程是先跑make defconfig再手动开CONFIG_STATIC和几个必要的feature而不是直接从别人的配置文件开始。原因是你不知道别人的配置里开了哪些私有patch出了问题无从排查。从最保守配置逐步开每开一项就做一次验证有问题能准确定位到是哪个特性引起的。编译阶段我的习惯是把CONFIG_PREFIX指到一个干净的临时目录这一点已经强调过。注意顺序make clean之后重新编译一定要把目标目录清空否则旧符号链接残留会导致奇怪的问题。我遇到过在目标目录里运行ls命令报“No such file or directory”排查半天才发现是上次残留的符号链接指向了一个不存在的位置。设备节点方面我的习惯是手动创建console、null、zero、ttyS0四个节点其余的交给mdev。但USB设备这种需要枚举才能确定的节点光靠提前创建没用还是要跑mdev -s或者依赖内核的uevent回调。很多人说“为什么我的U盘插上去没有/dev/sda”十有八九是mdev的hotplug链路没有配置好。还有一个小经验在脚本调试阶段把/etc/init.d/rcS里的命令逐条加上echo running xxx 这类日志输出能大大缩短定位时间。脚本一旦多了没有进度提示的启动过程非常煎熬。这些日志最后在一两轮调试后可以删掉但调试阶段它们价值巨大。8.4 最后的建议给新手的启动路径如果你完全没接触过BusyBox我给的建议路径是这样的先在一台x86虚拟机上手动编译并运行一个基于BusyBox的最小系统用qemu-system-x86_64跑起来。这一步没有硬件调试的干扰能让你把Linux启动本质——内核加载、根文件系统挂载、init执行、shell交互——整条链路彻底理解清楚。然后再上到ARM板子这时候你已经知道哪些步骤属于通用Linux哪些属于板子特有逻辑问题定位会快非常多。一味拿开发板从头试错很容易把“我网络没配好”和“我根文件系统有问题”混在一起排查起来非常痛苦。我个人在实际操作中最深的感受是BusyBox这个工具看起来只是一个“小型命令集合”但只要深挖到它的init机制、applet实现、编译方式、和内核的配合关系你几乎能把整个嵌入式Linux系统的启动链路完整串一遍。这只瑞士军刀真正值钱的地方不是它有多锋利而是你拿着它能把一台“裸奔”的Linux设备从零“盘活”。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。