ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从内核到命令行:用BusyBox构建嵌入式Linux根文件系统实战

从内核到命令行:用BusyBox构建嵌入式Linux根文件系统实战 我入行嵌入式Linux那会儿面对第一块要跑起完整系统的开发板最晕的事就是明明内核已经烧进去了启动信息也刷了一屏结果停在“Kernel panic - not syncing: VFS: Unable to mount root fs”上一动不动。后来才知道一个能让系统从“内核”走到“能敲命令”的关键组件就是BusyBox。这个东西到底怎么从原理层面工作又怎么把它做成一个真正的根文件系统我用实际项目里的完整过程给你捋一遍。这篇文章面向的是正在学嵌入式Linux、或者已经会点基础但还没把根文件系统串成闭环的朋友内容覆盖BusyBox的调用机制、交叉编译、根目录搭建、init启动流程、NFS调试和常见故障排查直接对标实际开发板上会遇到的问题。1. 为什么嵌入式Linux离不开BusyBox一个二进制统治一套用户态1.1 嵌入式设备的存储账本Flash和RAM都不允许你“慷慨”先算一笔硬件账。很多工业控制板、路由器、物联网网关Flash容量常年是16MB、32MB这个级别内存64MB或者128MB。这种环境下你要是把桌面Linux那套用户态工具搬过来光coreutils这一个包装上就是几十MB再加上bash、init、net-tools、procps这些整个根文件系统随随便便破百MBFlash根本塞不下。有人可能会说现在几百块钱的开发板不都是512MB起步吗是的但产品量产是要算BOM成本的Flash多一倍的料号单价可能就多好几块美金出货几十万台那就是几十万美金。所以嵌入式的代码习惯永远是“按字节省钱”。BusyBox解决的就是这个问题它把ls、cat、cp、mv、mount、sh、ifconfig、udhcpc、telnetd、httpd这些日常几乎必用的命令全部塞进一个二进制文件。这个文件有多大我交叉编译过v1.30.1版本默认配置、动态链接的情况下busybox二进制本身才700KB到900KB左右如果开静态链接也就1MB出头。加上必要的动态库整个根文件系统可以控制在3MB以内。从几十MB到3MB这就是BusyBox能统治嵌入式用户态的根本原因。1.2 “瑞士军刀”的两种用法先认识BusyBox的自我拆分逻辑很多人第一次接触BusyBox会困惑我明明执行的是ls为什么实际上是BusyBox在干活因为BusyBox采用了一种叫**Applet小程序**的架构。它在内部维护了一张“命令名—处理函数”的映射表ls是一个appletcat是一个appletmount也是一个applet。所有applet编译后都链接进同一个busybox可执行文件里。运行时有两种调用方式第一种直接执行busybox后面跟参数比如busybox ls -l /homeBusyBox会把第一个参数当成子命令名去分发。第二种通过符号链接调用。比如/bin/ls并不是一个真正的程序而是一个指向/bin/busybox的链接。当你敲ls的时候shell通过PATH找到/bin/ls执行后内核把argv[0]传给BusyBoxBusyBox看argv[0]的basename是ls就去调用ls对应的处理函数。我当年第一次执行ls -l /bin/ls看到输出/bin/ls - /bin/busybox的时候确实有种“被骗了”的感觉但这种设计简直是天才——系统里只需要一份可执行文件的副本维护、升级都只用替换一个文件省空间又省事。可以亲手验证一下在装了BusyBox的系统上敲busybox --list会列出当前这个BusyBox支持的所有applet敲busybox --list-long会显示带完整路径的列表。看到上百个命令挤在一个二进制里那种“这不就是一把带了几十种工具的折叠刀吗”的画面感会特别强。1.3 BusyBox不只是“阉割版命令集”它连init和shell都包办了我早期有个误解觉得BusyBox就是一堆小命令的集合直到第一次读到它的doc目录才反应过来BusyBox本身就包含init进程。就这个特性让它从“命令工具箱”升级成了“用户态系统核心”。传统的系统里PID 1是systemd或者sysvinit负责拉起所有服务。BusyBox同样提供init这个applet它会读取/etc/inittab配置文件解析启动流程负责挂载文件系统、执行rcS脚本、在串口上拉起登录shell。也就是说一个BusyBox二进制既当ls、cat又当/sbin/init、/bin/sh。它内置的shell是ash虽然和bash比功能精简了不少没有BASH_REMATCH这类高级特性、对数组支持也弱但日常的脚本逻辑、for循环、case、管道都能正常跑。我踩过一次坑把写给bash的复杂脚本直接丢到板子上执行结果一堆语法错误。后来养成了习惯板子上的脚本一律用POSIX语法写。这个细节在构建根文件系统时非常关键后面讲rcS脚本的时候你会看到实际能跑多简单的语法就写多简单。2. 剥开BusyBox的外壳Applet分发、符号链接与裁剪逻辑2.1 Applet分发机制一张表一个入口函数要真正理解BusyBox得从源码层面看它的主入口。BusyBox的源码根目录有个libbb/appletlib.c这里实现了核心的applet分发逻辑。它内部维护了一个applet_name数组和一个applet_main函数指针数组两个数组通过下标一一对应。这儿的排序不是随意的而是根据使用频率调整过的把最常用的命令排在前面这样在busybox里用第一个参数做线性匹配时平均查找次数能少一些。调用分发的大致流程是BusyBox的main函数先拿到argv[0]取basename。用这个basename在applet名称表里二分或线性查找。找到后根据对应下标找到处理函数的入口把argv整个传过去。如果找不到就会尝试把它当成“带参数的BusyBox调用”即busybox applet [args...]。手动执行busybox --install -s /bin这个命令BusyBox会扫描自己的applet列表在/bin下为每个applet创建符号链接。加上-s表示创建软链接如果去掉-s且具有写权限它会尝试在目标目录下生成硬链接。实际使用中我推荐在根文件系统制作阶段就用make install统一生成链接而不是等开机后再执行--install少一层不确定性。2.2 符号链接方案为什么能大幅省空间绝大多数文件系统比如ubifs、jffs2、FAT存储符号链接只需要记录目标路径字符串不会复制目标文件内容。所以/bin/ls、/bin/cat这些“文件”在Flash上占用的空间几乎可以忽略不计。如果不用符号链接每个命令都放一个独立二进制即使每个只有几十KB几十个命令加起来也是好几MB还不算文件系统为每个文件分配的块元数据开销。另外符号链接方案对升级特别友好要更新整套用户态只需替换/bin/busybox这一个文件其余链接全部自动生效。我做OTA升级脚本的时候就是靠这个特性把“更新命令工具集”从操作几十个文件简化成“只刷一个二进制重启”稳当得很。这里要特别提醒有些设备为了省事把/bin/busybox做成了动态链接然后升级时只刷内核和rootfs的个别文件没管动态库版本是否匹配结果busybox一运行就报version GLIBC_2.34 not found。静态链接的busybox能彻底避开这类“库版本地狱”代价是体积增加几百KB。我的经验是Flash不极度紧张时无脑选静态链接存储实在紧张就上静态把不需要的applet裁掉。2.3 配置裁剪menuconfig背后的三层取舍BusyBox用Kconfig管理配置编译前做make menuconfig就可以按需勾选applet和各项feature。这和内核的配置方式一脉相承刚上手的人可能觉得选项多但它的逻辑很清晰主要分三层第一层applet的增删。比如确定不需要httpd和telnetd直接取消勾选就能省掉对应的代码和进入二进制后占用的字符串表。第二层每个applet内部feature的取舍。比如ls这个applet可以单独控制是否支持颜色输出、是否支持-a、-l之外的扩展参数。每多一个特性代码体积和运行时内存都会有细微增加。第三层全局行为设置。比如CONFIG_STATIC静态链接、CONFIG_FEATURE_INSTALLER是否支持--install、CONFIG_CROSS_COMPILER_PREFIX交叉编译工具链前缀等。我习惯先按默认配置完整编译一遍确认板子能启动、shell能交互之后再回头一项项裁剪。上来就裁得只剩20个命令一旦漏掉某个系统启动依赖的命令排查起来比编译体积大费劲多了。裁剪的基本原则是“删之前先确认调用链”init依赖mount、mdev依赖sh和echo这些隐性依赖最容易被误删。3. 动手编译BusyBox从源码到交叉编译安装的完整流程3.1 环境准备交叉编译链和源码版本选择先说工具链。我用的是arm-linux-gnueabihf-gcc这个前端的工具链套件一般用户空间程序都靠它编。拿到工具链后先验证一下arm-linux-gnueabihf-gcc -v能看到gcc版本和target信息就说明基本可用。不同的SoC厂家有时会提供自己的工具链比如ARM的arm-none-linux-gnueabihf-、或者buildroot整套工具链导出的交叉编译器。关键是确认三件事gcc版本、glibc还是uClibc/musl、软浮点还是硬浮点。这三者不匹配交叉编译的程序在板子上经常出现Illegal instruction或段错误。源码方面BusyBox的版本很多v1.22.1、v1.30.1、v1.36.1都有人在用。我的建议是不要追最新选一个配套文档多、团队验证过的LTS风格版本。比如v1.30.1不少开发板厂商的官方SDK用的就是它。版本太老比如1.20.x对新的内核特性、devtmpfs支持不完整版本太新配置项变化大网上经验贴对不上号时反而费时间。3.2 make menuconfig里的关键项不止是勾勾选选进入BusyBox源码目录后这样操作make menuconfig这里必须改的项主要在Settings - Build Options下配置项推荐值说明CONFIG_STATIC选中静态链接避免板子缺库CONFIG_CROSS_COMPILER_PREFIXarm-linux-gnueabihf-交叉编译前缀必须改CONFIG_PREFIX./_installmake install的安装路径CONFIG_FEATURE_INSTALLER选中启用--install用于创建符号链接如果忘了设CONFIG_CROSS_COMPILER_PREFIX直接make会调宿主机的gcc生成x86版本拷到板子上就是“Exec format error”。这个错误我在新手期栽过不止一次后来习惯先执行file busybox看输出是不是ARM。配置保存退出后开始编译make -j8 make install执行完_install目录下会出现bin、sbin、usr这些子目录里面全是busybox的符号链接。这时候做个简单检查file _install/bin/busybox输入里带ARM、statically linked就对了。3.3 交叉编译的隐藏坑工具链版本、ASCII/UTF-8和strip策略打造基础系统时make直接就过了但有两个坑得留意。第一个坑工具链的glibc版本和开发板文件系统里的C库不一致。如果你选择动态链接busybox会依赖工具链配套的ld-linux-armhf.so.3和libc.so.6版本高了板子上没有版本低了也一样起不来。最稳妥的做法还是静态链接。第二个坑编译机和目标机的架构字节序。大端小端的问题比较罕见但一旦碰到症状是“命令能执行立刻报错”。我最早玩PowerPC平台时就遇到过arm工具链怎么编译都对换到ppc必须换工具链这个完全没法绕过只能用对架构的交叉编译器。编译完了别急着拷库先strip一下arm-linux-gnueabihf-strip _install/bin/busyboxBusyBox二进制里默认带一份符号表不strip的话几百KB的符号信息全浪费在Flash上。开发阶段可以strip一部分保留基本符号方便gdb量产镜像就完全strip。这一步对最终镜像体积影响挺明显静态链接的busybox能从1.4MB瘦身到1MB以内。4. 从BusyBox到可启动的根文件系统目录骨架、init与启动流程4.1 先搭好根文件系统的“房子架子”目录结构有讲究BusyBox的_install只负责命令本身不会替你把/etc、/dev、/proc、/sys这些目录建好。一个能启动的根文件系统目录结构得先成型。我用的初始化脚本通常是这样的mkdir -p rootfs/{bin,sbin,usr,etc/init.d,lib,dev,proc,sys,tmp,var,mnt,root,home}每个目录都有它存在的理由/bin、/sbin、/usrbusybox安装的位置以及用户程序的位置。/etc所有配置文件的归宿inittab、passwd、fstab都放这儿。/lib动态链接库。静态链接的busybox确实可以不需要但你的应用程序、dropbear这些多半还是要。/dev设备节点。用devtmpfs或mdev来动态管理。/proc、/sys内核虚拟文件系统挂载点很多工具ps、free、mdev依赖它们。/tmp临时文件通常挂tmpfs放内存里。/var日志、pid、锁文件等可变数据也可以tmpfs。目录建好之后把_install/*整个拷贝进rootfs。注意一点_install里的bin/busybox链接目标都是bin/busybox这个相对路径所以拷的时候必须保持完整结构不能只拷单个文件。4.2 动态库的搬家工程盯着ldd输出一个个找虽然我推荐busybox静态编译但根文件系统上通常还跑着别的程序比如下面要说的dropbear它们多半是动态链接的。把动态库拷进rootfs有个标准动作用工具链或板子上的ldd列出依赖然后逐个拷贝。arm-linux-gnueabihf-readelf -d /path/to/your_app | grep NEEDED会看到类似0x00000001 (NEEDED) Shared library: [libc.so.6] 0x00000001 (NEEDED) Shared library: [libgcc_s.so.1]然后从工具链的sysroot里把对应库拷到rootfs/lib。这里有个新手必踩的坑动态链接库本身通常也是符号链接比如libc.so.6指向libc-2.31.so。拷贝时要用cp -a或者是cp -d保持链接关系否则板子上找不到真实的so文件。我在NFS调试阶段经常遇到“开机到一半init起来了但某个服务起不来”一查日志全是error while loading shared libraries。所以我把这个流程做成了一个脚本先find出所有依赖库再循环拷贝最后用ls -l rootfs/lib人工确认一遍有没有断链。4.3 init进程的启动顺序与inittab内核如何找到第一个程序内核启动的最后一步会尝试按顺序执行以下程序/sbin/init/etc/init/bin/init/bin/sh这就是为什么BusyBox的init必须出现在/sbin/init或/bin/init的位置。如果这几个都没有系统就会报No init found然后panic。/bin/sh在这里其实是最后的兜底但纯sh启动没有任何配置脚本起完就等于一个裸的交互shell没法正常干活。BusyBox init启动后读取的第一个文件是/etc/inittab。它的格式是id:runlevels:action:processBusyBox里的runlevels字段可以留空id字段对应tty名字比如consoleaction是可选的几大类sysinit系统初始化时执行一次通常用来调用/etc/init.d/rcS。respawn进程退出后自动重新拉起典型用法是启动getty或者shell。askfirst在串口上询问“是否启动shell”按回车才启动开发阶段特别有用。wait等进程结束才继续下一条。once只执行一次但不等它结束。我常用的inittab骨架::sysinit:/etc/init.d/rcS console::askfirst:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r-开头表示启动这个shell时不打印“last login”这些登录信息直接进交互式shell调试时减少干扰。这个inittab写完BusyBox init的逻辑就闭环了先跑rcS完成基础初始化然后在控制台上拉起shell等用户输入。4.4 rcS脚本与mdev开机自动挂载和动态设备管理/etc/init.d/rcS是整个用户态初始化的起点busybox本身不提供这个文件全得自己写。一个能用的最小rcS大概长这样#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs devtmpfs /dev mkdir -p /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev /proc/sys/kernel/hotplug /sbin/mdev -s /bin/hostname -F /etc/hostname mkdir -p /tmp /var/tmp mount -t tmpfs tmpfs /tmp逐行解释一下mount proc和mount sysfs是给ps、mdev、内核接口访问提供基础devtmpfs让内核自动创建设备节点这是新版内核2.6.3x之后的做法老教材里用手动mknod建/dev/console和/dev/null的做法现在基本不需要了但如果你内核没开CONFIG_DEVTMPFS_MOUNT建议rcS里还是手动补两个节点mknod -m 600 /dev/console c 5 1 mknod -m 666 /dev/null c 1 3echo /sbin/mdev /proc/sys/kernel/hotplug是把内核热插拔通知交给mdevmdev是busybox里的udev替代品。-s参数表示启动时扫描一遍/sys把已存在的设备节点补齐。这样插入U盘、接上串口设备节点才能自动出现。/etc/hostname可以自己造一个文件比如内容写myboard作用是让命令行提示符不那么秃。不写也行但写了看着舒服。4.5 账号文件与其他基础配置让登录和scp不出幺蛾子虽然开发阶段用askfirst直接进shell很爽但真到要远程跑dropbear、要分权限的时候/etc/passwd和/etc/group就逃不掉了。最少可用版本长这样root:x:0:0:root:/root:/bin/shx表示密码放在/etc/shadow里shadow文件里root::0:0:99999:7:::表示root账户没有密码Dropbear登录时敲回车就能进。如果不想要裸奔可以用chpasswd或busybox的passwdapplet设置密码。还得有/etc/grouproot:x:0:另外我建议建个/etc/fstab虽然busybox init不强制读它但mount -a命令会按它来挂载所有条目proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 tmpfs /tmp tmpfs defaults 0 0写到这一步根文件系统的“最小可启动闭环”就成型了。把它打包成ext4镜像或直接丢到NFS启动目录里连上串口就能看到系统先打印内核日志然后BusyBox的init接管最后在控制台上敲出/ #提示符。那一刻的成就感算是嵌入式入门路上最值得纪念的瞬间之一。5. 开发调试的利器组合NFS根文件系统、Dropbear与网络工具5.1 NFS挂载根文件系统改代码不用反复烧录开发阶段最烦的就是每改一次脚本就要重新打包镜像、烧写、重启。NFS根文件系统方案能把这个循环彻底打掉rootfs直接放在宿主机的某个目录里开发板通过网络挂载它作为根文件系统。你在宿主机上改脚本板子上立刻生效重启也只是重新挂载一遍。宿主机上要装并启动NFS服务。配置/etc/exports把开发用的rootfs目录导出/home/workspace/rootfs *(rw,sync,no_root_squash,no_subtree_check)no_root_squash很关键否则板子上的root在NFS上会被当成nobody处理很多文件操作没权限。然后重启NFS服务sudo exportfs -rav sudo systemctl restart nfs-kernel-server开发板uboot环境下设置bootargssetenv bootargs consolettyS0,115200 root/dev/nfs nfsroot192.168.1.100:/home/workspace/rootfs ip192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off rw setenv bootcmd tftp 0x2000000 zImage; tftp 0x3000000 dtb; bootz 0x2000000 - 0x3000000 saveenvnfsroot的格式是服务器IP:路径ip参数依次是板子IP:服务器IP:网关:掩码:hostname:网卡名:off。这个参数串我当年配了整整两天才弄对最后发现是网卡名写成了eth1而实际是eth0。排查技巧内核日志里NFS相关打印会提示Looking up port of RPC 100003/2 on 192.168.1.100看到这条却没下文基本是网络不通或NFS配置问题。5.2 集成Dropbear让板子也能ssh登录BusyBox自带telnetd但telnet是明文协议代码传输和操作都被抓包看光。所以只要板子上Flash允许我一般都会集成dropbear。Dropbear是一个为嵌入式环境设计的轻量SSH服务端实现功能上能满足绝大多数看日志、拉文件、改配置的需求。交叉编译dropbear的关键步骤./configure --hostarm-linux-gnueabihf --prefix/usr --disable-zlib make PROGRAMSdropbear dropbearkey make install PROGRAMSdropbear dropbearkey--disable-zlib是为了省事dropbear对zlib的依赖不是硬性的去掉它能少折腾一条第三方库交叉编译链。如果你要更高的传输效率或兼容性再把zlib编进去但开发阶段裸的完全够用。装到rootfs之后开机脚本里得生成主机密钥mkdir -p /etc/dropbear /usr/bin/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key /usr/bin/dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key /usr/sbin/dropbear -R第一次生成密钥会比较慢尤其是没有硬件随机数熵源的板子可能会出现“挂起感”。处理办法是启动后异步生成或者预先在宿主机上生成好密钥文件一并拷进rootfs。-R参数让dropbear接受无缓冲的host key生成开发时建议加上量产再改成固定密钥。这部分我在项目里是按“host key放在数据分区、首次启动生成”的方式处理的兼顾了安全和复现性。5.3 网络工具的取舍udhcpc、静态IP与跟手程度问题既然要NFS启动、要SSH那网络初始化必然不能缺。两种方式动态获取IP和静态配置。动态获取用busybox自带的udhcpc/sbin/udhcpc -i eth0但要注意udhcpc不是想起来就能跑通它需要一个脚本去配置IP地址、路由和DNS。默认脚本路径是/etc/udhcpc/default.scriptbusybox源码的examples/udhcpc/simple.script可以拿过来直接用把它拷到rootfs的/etc/udhcpc/下并加执行权限就行。静态配置则简单粗暴ifconfig eth0 192.168.1.50 netmask 255.255.255.0 up route add default gw 192.168.1.1我开发板的习惯是保留静态配置脚本在rcS里默认执行调试网络时自己手动切udhcpc这样不会因为DHCP服务器没响应而卡住启动。另外不要太依赖busybox的ping来排障它为了塞进“小空间”把很多探活功能裁剪了比如默认只发很少的包、不带-c参数时还会无限发。真要在板子上抓包分析问题还是老老实实靠tcpdump或nmap这类独立工具虽然体积不小但排障价值很大。5.4 “U盘测速”这类小场景sync、VFS写回与掉电数据安全最近看到网上聊“嵌入式Linux U盘测速方案”其实一个小场景就暴露了VFS和sync的关系。很多人直接在开发板上跑dd if/dev/zero of/mnt/usb/test.bin bs1M count100结果速度显示异常快比如每秒300MB明显不符合U盘实际能力。原因是Linux的VFS虚拟文件系统层把写入数据先放进了page cache函数返回时数据还没真正写到U盘。dd默认的写操作遇到“设备还在缓存”时会直接返回成功。正确测法是在dd命令后面加convfdatasync强制每次写完都把数据同步到存储介质dd if/dev/zero of/mnt/usb/test.bin bs1M count100 convfdatasync或者用time dd ... sync来保证落盘时间被统计进去。这个问题不像只是“测速不准”这么简单它还牵涉到一个很实际的嵌入式隐患掉电丢数据。很多板子直接断电关机如果应用写文件后没有sync即使应用层认为“写完了”一掉电文件可能还是空的。我在做数据记录类产品时强制要求所有关键数据写入后立刻sync并且把频繁写入的日志文件放到tmpfs内存里避免Flash被频繁擦写、也避免异常掉电把文件系统搞坏。这个习惯是靠一次“明明写入了日志掉电后文件大小却是0”的事故换来的提出来希望能帮各位省一次半夜加班。6. 文件系统起不来的高频故障我实际踩过的坑和排查链路6.1 VFS: Cannot open root device从root为什么起不来推断内核和参数内核启动的最后阶段如果要挂载真实根设备失败会打印类似VFS: Cannot open root device mmcblk0p2 or unknown-block(0,0): error -2排查链路在我看来分三步。第一步确认内核有没有编对应存储介质驱动。比如你的rootfs在eMMC的第二个分区但内核的CONFIG_MMC或者eMMC驱动没编进去这个设备节点根本不会出现。用ls /dev/mmcblk*或者启动日志里搜索mmc0确认。第二步看root参数是否写对了。root/dev/mmcblk0p2只是其中一个写法很多人图省事写rootPARTUUIDxxx但uboot传参会把引号吞掉导致识别失败。我建议开发阶段直接用root/dev/mmcblk0p2这种最直白的参数。第三步查rootwait。eMMC或SD卡在初始化完成前设备节点还没准备好如果内核挂载根文件系统的时机早于设备就绪就会出现偶发性的“root device not found”。uboot传参里加rootwait内核会一直等到设备可用再挂载解决大部分“时好时坏”的root挂载问题。这类问题有个调试技巧在内核cmdline里加rdinit/bin/sh或直接看完整日志。如果日志里连mmc卡的枚举都只有半截重点查硬件连接和驱动如果mmc枚举正常但挂载失败重点查分区表和文件系统类型。6.2 No init found应找的init不在它该在的位置系统提示Kernel panic - not syncing: No init found. Try passing init option to kernel.多数情况就三个原因一是/sbin/init确实没放进去我早期把busybox拷进了/usr却忘记建/sbin目录的链接二是init文件没有执行权限三是init这个文件本身动态链接了rootfs里没有的库内核执行它时报Permission denied或Exec format error最终判成“No init”。排查技巧先把rootfs挂到宿主机上检查然后用readelf -l /sbin/init | grep INTERP看它依赖的动态链接器路径是不是/lib/ld-linux-armhf.so.3再检查/lib下有没有这个链接器。如果linker缺失即使/sbin/init路径和权限都对也会被内核拒之门外。这里有个很容易被忽略的复制顺序问题很多教程让你先make install到_install再整体拷到rootfs但如果你中途改了CONFIG_PREFIXmake install会覆盖旧链接。每次重新install之前我习惯先把_install整个删掉避免残留的旧软链接干扰排查。6.3 cant access tty; job control turned off控制台配置的经典报错进入shell后看到这行提示大概率是inittab里指定的tty不对或者/dev/console//dev/ttyS0没有正确创建。开发板上通常用串口控制台inittab里一般写console::askfirst:-/bin/sh。但如果内核命令行里的console参数指定的是ttyS0而inittab里的id写的是ttyS0有的busybox版本还是只识别console这个名字得看具体平台的映射。实测下来绝大多数arm平台用console::askfirst:-/bin/sh都不会出错。如果用了devtmpfs但设备节点没生成多半是内核没开CONFIG_DEVTMPFS或者rootfs挂载时/dev还是空目录。这种情况在rcS里手动补mknod能救急但根治还是要检查/proc/sys/kernel/hotplug和mdev配置。6.4 文件在命令却not foundPATH、解释器和链接器的三重排查这个现象非常诡异用ls /bin/foo能看到文件存在但敲foo就是not found。我遇到过的根因有三类排查时按下面顺序过一遍第一PATH没包含那个目录。很多精简系统的PATH可能只有/bin和sbin你如果自定义装了程序到/usr/local/bin自然找不到。rcS里export PATH或在/etc/profile里写全路径可以解决。第二文件是可执行脚本但脚本第一行的解释器路径不对。比如脚本开头写#!/usr/bin/python3而你的板子上python3在/usr/bin/python执行时就会报“not found”因为内核解析器的路径不存在。这个坑在跨平台拷贝脚本时特别常见。第三动态链接器路径不对。用file看这个文件会显示dynamically linked, interpreter /lib/ld-linux-armhf.so.3如果/lib下没有这个文件结果就是“文件存在但无法执行”。检查方法很简单/lib/ld-linux-armhf.so.3 --list /bin/your_program把缺的库拷贝补齐即可。6.5 细节里的魔鬼权限、目录属性和“看似多余”的空目录最后闲聊一个最容易“被跳过”的问题权限。busybox的applet链接好之后bin、sbin目录通常默认755就行但有两个例外/etc/shadow、/etc/dropbear这种包含密钥的目录权限必须严格控制否则dropbear会拒绝启动。dropbear的host key如果权限是world-readable它自己都会警告并拒绝我见过有人被这个卡了整个下午后来chmod 600解决。另外rootfs里有些“看起来没用”的目录比如/var/run、/var/lock很多老牌服务会往这些路径写pid和锁文件目录不存在它们就起不来。buildroot生成的rootfs里总会把这些目录预先建好不是没有道理的。我在做最小系统时曾为了省几个字节删掉了/var/run结果装了个脚本需要拉pidfile服务一直报错找了一圈才反应过来是目录缺失。从此以后目录结构核对我都放进了镜像打包脚本里少一次这种无意义的debug就多一小时正经开发时间。7. 从能启动到用得顺最后的一点经验总结从拿到一个空板子到看到/ #提示符能敲命令、能ssh进去、能远程挂NFS改代码这个过程是嵌入式Linux从“纸上谈兵”到“真正落地”的分水岭。我见过不少同事在“能启动内核”之后就觉得大功告成结果产品化阶段被根文件系统反复坑脚本起不来、设备节点不稳定、网络不通、掉电丢数据最后全要回头补基础课。如果你正在走这条路我的建议是别只对着教程复制粘贴把每一步的原理都反问一遍“为什么”。为什么用静态链接、为什么inittab要写sysinit和askfirst、为什么NFS挂载要no_root_squash、为什么这里要sync——想清楚这些为什么你从“会搭板子”到“能设计一套可靠启动方案”的进化会快得多。最后分享一个小技巧开发调试阶段可以在rcS里临时加入busybox --list的输出到日志文件这样开机后能一眼确认你的busybox编译裁掉了哪些命令免得辛辛苦苦把应用跑起来结果发现rsync、wc这种不起眼的小命令没编进去又要重新编译整个镜像。类似的经验还有很多但最核心的还是那一句一个能自己控制根文件系统的开发者才算真正摸到了嵌入式Linux的门把手。这套流程我前前后后在好几个平台A7、A9、Cortex-M架构的MPU上重复了不下十遍越到后面越觉得BusyBox不是一个个命令的堆砌而是嵌入式系统里“节俭与可靠”设计哲学的一个缩影。
返回列表