news 2026/9/6 10:42:31

嵌入式Linux根文件系统实战:BusyBox交叉编译与启动排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Linux根文件系统实战:BusyBox交叉编译与启动排查

做嵌入式Linux绕来绕去,最终还是绕不过两样东西:根文件系统,以及那个被称为"瑞士军刀"的BusyBox。我见过不少刚入行的朋友,一提到BusyBox就以为只是个"瘦身版Linux命令行",拿着它当普通工具包用,遇到启动失败时又毫无头绪。这次我就从原理拆到落地,完整过一遍BusyBox在嵌入式Linux里的角色、交叉编译方法,以及怎么用它在实际项目中组装出一套能跑起来的根文件系统。这篇文章适合刚好在搭开发板环境、被NFS挂载或init启动卡住的读者,也适合想系统整理一遍相关知识栈的工程师。

1. 为什么嵌入式Linux离不开BusyBox这样的"百宝箱"

1.1 存储和内存约束下的"一个顶一百个"

嵌入式环境里,Flash和RAM的价格虽然一直在降,但量产产品对BOM成本异常敏感,留给软件的空间就是那么点。你很难在4MB的Flash里装一套标准Linux用户态工具,但你可以装一个BusyBox。它最核心的设计目标很朴素:把几百个常用命令合并进一个可执行文件里,共享同一个主程序框架和基础函数,从而把体积压到极致。

我举个直观的对比。一个标准发行版里,coreutils这一组工具(ls、cp、mv、cat等等)的二进制文件加起来,往往几十MB起步;如果你再把shell、mount、vi这些算进去,体积更大。BusyBox常规编译出来,动态链接版本经常几百KB,静态版本也就1MB上下。差距不是一个数量级,是好几个数量级。

当然,光看体积还不够。嵌入式设备上每跑一个进程,内核都要维护对应的task_struct、页表、代码缓存这些资源。BusyBox把所有applet合并进一个可执行文件里,意味着同一份代码段能在内存里被所有applet共享,启动命令时也别再逐个从Flash里读取不同的二进制。这个特性在RAM很紧张的老平台上尤其重要,很多路由器、IoT模组到现在依然靠这一点活得很好。

1.2 applet机制:软链接、符号链接与busybox命令分发

很多人第一次用BusyBox时,都会疑惑:为什么/bin/ls是指向/bin/busybox的软链接?难道一个程序能同时变成上百个命令?答案是能,秘密就在applet机制。

BusyBox在编译时会生成一个applet名字表。运行时,它检查被调用的名称,也就是argv[0],在名字表里找到对应的入口,然后执行那个命令的逻辑。如果你直接敲busybox ls,它同样会取出argv[0]后面的第一个参数当作命令名来分发。从使用者的角度看,就是一条软链接唤醒一个命令,完全无感。

这样做的好处很明显:实现一个命令的开销只有一份表项和一段函数代码,命令之间还能复用大量基础工具函数。比如ls、cp、mv、rm都要解析路径,BusyBox内部的统一代码就能避免重复造轮子。构建时用make install会在指定前缀目录里创建全部软链接;如果目标文件系统里不方便放大量链接,也可以只创建实际用到的那些,甚至什么都不建,统一通过busybox命令加参数来调用。

1.3 从内核启动到init进程:BusyBox的"接棒"位置

根文件系统要真正"用起来",内核只负责把关,真正接管用户态的是init进程。内核把根文件系统挂载好之后,会去执行init=参数指定的程序,默认是/sbin/init。在BusyBox构建的根文件系统里,/sbin/init通常就是指向/bin/busybox的软链接。

于是事情变得很有意思:整个系统的第一个用户态程序是BusyBox,后面的shell、命令、守护进程也基本来自BusyBox。它不只是"瑞士军刀",更像系统从内核态交接给用户态后的第一个接力棒。

当init被内核作为PID 1启动后,它要负责持续运行、回收孤儿进程、响应shutdown信号。BusyBox的init实现虽然精简,但该有的机制都有,并且会读取/etc/inittab来决定启动哪些服务、打开哪些控制台。这一步是整个根文件系统能否正常"活"起来的关键。如果你见过某个板卡停在内核启动成功之后,串口没有shell提示符,十有八九就是init或inittab出了问题——这个后面我专门讲。

2. 从源码到可执行文件:一次完整的BusyBox交叉编译

2.1 交叉工具链怎么选:发行版安装包还是厂商SDK

BusyBox不是Docker容器,不能在目标板上现场编译,编译它需要一个能生成目标平台代码的交叉编译器。选型上有个基本原则:工具链的GCC主版本不要和目标平台的内核、libc版本差太多,否则容易编译出运行时行为古怪的程序。

比较省事的方式是直接用Ubuntu发行版里的交叉工具链。比如目标平台是32位ARM Cortex-A,装这个:

sudo apt-get install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross

目标平台是AArch64,就换成gcc-aarch64-linux-gnu。这套工具链适合跑Linux的ARM平台,glibc版本也比较新,编译BusyBox和Dropbear这类用户态程序绰绰有余。

如果板子厂商提供了配套SDK,比如Buildroot或Yocto生成的工具链,优先用厂商的。因为它的内核头文件、libc和板子自带的库更匹配,后面做动态链接时不容易出现版本不兼容。没有SDK就用发行版工具链,大多数项目都能跑通。关键是在一套项目里从编译内核到编译用户态都用同一个工具链,不要混用,否则架构相同但ABI不匹配的坑能把人磨疯。

2.2 静态编译还是动态编译,这是个需要提前拍板的问题

在配置BusyBox之前,得先想明白一件事:根文件系统里的BusyBox打算静态编译还是动态编译。这两个选择的后续影响完全不同。

静态编译的BusyBox不依赖任何外部共享库,拷到目标板就能跑。制作initramfs、救援系统、以及极简根文件系统时,静态编译是首选。这么做的好处是省心,彻底避开"库依赖"问题,坏处是生成的二进制体积大几十到一两百KB,不过对嵌入式来说这点体积完全可控。

动态编译的BusyBox依赖目标板上的libc和动态链接器,体积更小,也可以跟着系统库升级获得bug修复,但代价是:拷到rootfs里的库必须一个不少,否则启动时直接报"line 1: /bin/sh: not found"这类诡异错误。开发初期用NFS挂载rootfs时,动态编译能省点Flash空间,但排查库依赖会比较麻烦。

我的习惯是:做量产镜像倾向静态编译,少一个变量;做快速调试环境则无所谓,怎么顺手怎么来。配置方式是在menuconfig里找到Settings -> Build static binary,对应CONFIG_STATIC,打开后重新编译。

2.3 配置裁剪:项目里到底要留哪些命令

执行下面的命令,会得到一个比较全的功能集合:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

进入图形界面后按项目需求裁剪。我的裁剪思路是"按功能域分块":shell类必须有ash;文件操作类留ls、cp、mv、rm、mkdir、tar;挂载和网络类留mount、umount、ifconfig、ip、ping、udhcpc;进程和系统类留ps、top、kill、dmesg、sysctl;编辑和文本处理类留vi、grep、sed、awk;设备管理留mdev。打印机、模块加载、各种用不到的杂项全部砍掉。

裁剪不是单纯追求小。每少一个applet,就少一分被攻击面。尤其是设备要暴露到公网的场景,BusyBox里用不到的网络服务applet应尽量关掉。编译完成后:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install CONFIG_PREFIX=$PWD/_install

产物放在_install目录,后面组装根文件系统要用的就是它。

2.4 交叉编译中的常见报错与我的处置习惯

第一次用新版本源码编译时,最容易遇到的报错是缺少bison、flex、make等构建工具。Ubuntu上一条命令全解决:

sudo apt-get install bison flex make gcc

还有一类报错是编译到某个applet时提示头文件里的函数不存在,这多半是工具链太老或太新导致的kernel API漂移。遇到这种问题我一般直接降级到较稳定的BusyBox版本,或者看一下release note里对应的编译要求。

如果你拿的还是老项目里锁定的BusyBox源码,比如某个产品用了v1.22.1这样的老版本,记得同时关注工具链版本。老的BusyBox碰到新版GCC可能在某些文件上警告特别多,但只要make没挂掉,基本不影响最终产物。真正要小心的是不同版本之间配置结构差异不小,不要拿新版本的defconfig直接套老代码。

编译全部完成后检查一下输出目录里有没有busybox文件,以及file命令显示的架构是否正确。这一步只要走神一次,后面整个rootfs都会白忙一场。我见过有人拿着x86的busybox往ARM rootfs里塞,启动时卡在init执行,排查了几个小时才反应过来,非常浪费时间。

3. 组装根文件系统的实战顺序与关键命令

3.1 一个最小rootfs的目录清单

拿到_install目录之后,第一步不是急着拷贝,而是先规划rootfs的目录骨架。哪怕是最小的根文件系统,也至少需要下面这些目录:

目录用途
bin系统命令
sbin管理员命令
etc配置文件
lib动态库与内核模块
dev设备节点
proc进程信息虚拟文件系统挂载点
sys设备与驱动信息虚拟文件系统挂载点
tmp临时文件
var运行时数据
home用户目录
rootroot用户目录

这些目录不是拍脑袋建的。比如/proc和/sys是内核虚拟文件系统的挂载点,没有它们,ps、mdev这类命令拿不到内核数据;没有/dev,设备节点无处安放;没有/tmp,很多临时文件程序直接崩。目录建好后,把_install下的内容复制过去:

mkdir -p rootfs/{bin,sbin,etc,lib,dev,proc,sys,tmp,var,home,root} cp -a _install/* rootfs/

这一步会把busybox及其软链接全部拷入rootfs。

3.2 拷动态库:最容易被忽略的"下一步"

如果你的BusyBox是动态编译的,现在就要处理动态库。先看它依赖哪些库:

arm-linux-gnueabihf-readelf -d rootfs/bin/busybox | grep NEEDED

输出里一般有libc.so.6,还有动态链接器lib/ld-linux-armhf.so.3。从交叉工具链的sysroot把这些库拷到rootfs对应目录。sysroot位置可以用工具链命令所在的路径推出来,比如:

arm-linux-gnueabihf-gcc -print-sysroot

然后把libc.so.6、ld-linux-armhf.so.3等拷到rootfs/lib。使用动态BusyBox时,很多人就在这一步翻车:文件明明在,但系统报告找不到。原因往往是动态链接器本身不在标准路径,或者库版本不对。用file和readelf逐项核对最保险。

有一回我排查一个"init: No such file or directory",最后发现是interpreter路径写的/lib/ld-linux-armhf.so.3,但我的rootfs里只有/lib/ld-linux-armhf.so.3.5,文件名不完全一致,内核exec时无法加载就报错。这个坑非常隐蔽,建议拷完库后先ls核对文件名。

3.3 设备节点:/dev/console与/dev/null的玄机

经过上面的处理,rootfs里还没有/dev目录的实质内容。这里有两种方案。

方案一,手动创建最基础的静态节点。哪怕是最小的系统,也必须有两个节点:/dev/console和/dev/null。原因很实际:内核向init进程传递的是console这个设备;而很多命令要求标准输入输出存在。创建命令如下:

sudo mkdir -p rootfs/dev sudo mknod -m 600 rootfs/dev/console c 5 1 sudo mknod -m 666 rootfs/dev/null c 1 3

方案二,依赖内核的devtmpfs和BusyBox的mdev。devtmpfs能让内核在启动时自动创建设备节点,mdev再基于/sys里的uevent事件动态维护。生产环境我基本都用这套,rcS脚本里写mount -t devtmpfs devtmpfs /dev;mount -t sysfs sysfs /sys;mdev -s。这样就不用手工维护一堆节点文件。

静态节点方案适合早期调试,优点是环境完全可控。但你得记得手动创建ttyS0、mmcblk0这类调试需要用到的节点,否则后面访问串口和磁盘都会找不到设备。

3.4 必备配置文件:inittab、rcS、fstab、passwd

有了busybox和库,系统还是没法自己"醒来",因为还缺配置脚本。最基础的四份文件:

/etc/inittab决定init启动哪些程序,初期可以先写极简版:

::sysinit:/etc/init.d/rcS ::askfirst:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r

/etc/init.d/rcS负责初始化脚本,最基础的内容:

#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev mdev -s

/etc/fstab用于mount -a时自动挂载,没有可以暂时留空,但建议写上tmpfs挂到/tmp和/var。

/etc/passwd和/etc/group可以先放一行root用户信息,不然login时会提示无法识别用户。权限方面四个坑经常踩:rcS必须可执行;inittab文件权限普通644即可;所有脚本换行符必须是LF而不是CRLF;busybox最好加上setuid位。做法是:

chmod 755 rootfs/etc/init.d/rcS chmod 4755 rootfs/bin/busybox

4. init进程、设备节点与挂载流程:让系统真正跑起来

4.1 内核是怎么找到并挂载根文件系统的

内核启动过程中,start_kernel会最终走到挂载根文件系统这一步。根文件系统在哪,由cmdline里的root参数决定。比如root=/dev/mmcblk0p2表示从MMC设备的第二分区启动;root=/dev/nfs表示通过网络启动;rootfstype=ext4可以指定文件系统类型,防止内核逐个探测。

这里要理解一点:对VFS虚拟文件系统层来说,所有文件系统最终都挂载到同一个根目录树上。内核在启动初期先用一个简易ramfs作为临时根,随后根据root参数把真正的文件系统挂载到根目录,并切换过去。很多底层问题就出在"找到设备-识别文件系统-挂载"这条链路上:设备驱动没编进内核,或文件系统模块没编进内核,都会导致无法挂载。

等到挂载成功后,内核会尝试执行/sbin/init。若成功,PID 1从此接管;若失败,就会看到经典的kernel panic或者"Attempted to kill init!"。

4.2 /etc/inittab 配置项逐段拆解

BusyBox的init读取的/etc/inittab格式比SysV init简单得多,四段用冒号分开:id:runlevels:action:process。BusyBox并不严格使用runlevels,习惯上这部分留空。

action字段决定进程行为,最常用到以下几种:

  • sysinit:开机最先执行一次,通常拉起rcS;
  • respawn:进程退出后自动重新拉起,常用于shell或getty;
  • askfirst:向控制台打印"Please press Enter to activate this console",等用户敲回车再启动一个shell;
  • ctrlaltdel:按Ctrl+Alt+Del时要执行的命令;
  • shutdown:关机时要执行的命令。

我常用的组合是sysinit拉起rcS,askfirst在串口上给出第一个shell,shutdown时先卸载文件系统。这段看起来小,但它直接决定你的串口会不会出现可交互的登录提示。如果inittab写错,比如路径写错,init会发现命令唤醒失败,但通常不会panic,只是反复打log,系统看起来"没反应",其实内核还活着。

4.3 rcS 里mount proc/sysfs/devpts到底有什么用

rcS作为系统初始化脚本,行使的是原来SysV init里一大堆rc脚本的职责。它至少要完成三件事:

第一,挂载proc和sysfs。ps、mdev、lsmod这些命令都依赖它们。没有/proc,系统里很多状态信息就是空的。第二,挂载devpts。这是为PTY准备的,SSH、telnet登录、串口getty都会用到。第三,启动mdev或配置网络。mdev正常工作的前提是sysfs已挂载,所以顺序必须是proc -> sysfs -> dev -> mdev。

一个常见版本如下:

#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev mkdir -p /dev/pts mount -t devpts devpts /dev/pts mdev -s hostname myboard

注意mdev -s必须放在/dev挂载完成之后执行,否则/dev节点是空的。即使内核用了devtmpfs,很多测量工具、USB设备接入的动态节点还是靠mdev补全。rcS里每个命令执行失败也要留意,比如mkdir -p已经存在不会报错,可如果前一步mount失败,后面接mdev只会拿到一个不完整的设备列表。

4.4 站在VFS与sync的角度看根文件系统

把根文件系统和VFS放一起看,很多问题突然清晰。VFS是内核的一个抽象层,它以树形结构组织所有文件系统。根文件系统是这棵树的最原始挂载点,而每个分区、每个U盘、每个tmpfs都是树上的一个子节点。

嵌入式调试时经常遇到sync相关的问题。比如你往文件系统里写了一个文件,断电重启后文件不见了。原因可能是数据还在页缓存里没刷回Flash。sync命令就是强制把脏页刷回底层设备。在关机流程里,inittab的shutdown动作最好也安排sync再umount,可以有效减少文件系统损坏的概率。

另一个常见场景是启动早期要修改只读文件系统。嵌入式产品里根文件系统可能是只读的ext4或squashfs,调试时可以用:

mount -o remount,rw /

把根重新挂载成可写。这个动作背后就是VFS的mount系统调用在重新处理挂载标志。理解这一层,再去排查"为什么文件写不进去""为什么umount提示Device or resource busy"就顺手多了。

5. 集成Dropbear与NFS调试:让根文件系统变得好用

5.1 为什么选择Dropbear而不是OpenSSH或telnetd

到了系统能开机、能进shell,下一步就是怎么高效调试。很多新手第一反应是开telnetd,顺手是顺手,但明文传输在现在的网络安全背景下基本是裸奔。完整OpenSSH又重,依赖一堆库。Dropbear正好卡中间:体积小、功能足够、支持SCP,还方便静态编译。嵌入式Linux里说"busybox集成dropbear",指的就是在BusyBox根文件系统基础上,把Dropbear作为SSH服务端装进去。

选择Dropbear还有个好处:它和BusyBox在技术路线上很合拍,都是为嵌入式场景设计的精简实现。Dropbear提供dropbear主服务、dropbearkey密钥生成工具、dropbearconvert转换工具。整套体系很小,可以静态链接成一个独立的sshd替代品。

5.2 交叉编译Dropbear并在rcS中拉起

集成Dropbear大致分三步:交叉编译、生成主机密钥、启动服务。

交叉编译的命令大概是:

./configure --host=arm-linux-gnueabihf --disable-zlib make PROGRAMS="dropbear dropbearkey dropbearconvert" make install

如果遇到头文件缺失,可以先跑autoreconf。想静态编译就加CFLAGS=-static,但要注意库依赖。

生成密钥在目标板上更稳妥,因为Dropbear会按配置路径读取。可以在rcS里放一行:

/usr/bin/dropbearkey -t ecdsa -f /etc/dropbear/dropbear_ecdsa_host_key /usr/sbin/dropbear -R

-R参数让它自动生成缺失的host key,方便调试;量产固件则建议提前固化密钥,否则每次启动都不一样,客户端连接时要重新确认指纹。启动脚本里别忘了把PATH和LD_LIBRARY_PATH写清楚。动态编译的Dropbear若提示找不到库,多半是rootfs里没有libnss或libutil,用readelf查一下依赖再补齐就行。

5.3 用NFS把板子的根文件系统放在开发机上

嵌入式开发初期最爽的方式,是让目标板子的根文件系统直接放在开发机上,通过网络NFS挂载。这样在开发机上改代码、改配置,板子重启就能看到,省去烧写Flash的时间和消耗。

先在开发机上安装并配置NFS Server。Ubuntu上:

sudo apt-get install nfs-kernel-server

然后在/etc/exports里加一行:

/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)

注意no_root_squash很关键。目标板的root账号在NFS访问时一般会被映射成nobody,如果不开no_root_squash,rootfs里很多文件会没有权限写。开发调试时可以开,量产环境还是建议规规矩矩按最小权限来。

板端一侧,需要内核支持NFS客户端和Root over NFS,对应配置项CONFIG_NFS_FS、CONFIG_ROOT_NFS。U-Boot的bootargs里改成:

root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,v3,tcp rw ip=dhcp

ip=dhcp让板子先拿到IP,NFS客户端才知道去哪找服务端。

5.4 从烧写到共享文件系统的调试流

NFS根文件系统调试流里,我最常用的是三层结构:U-Boot用tftp加载内核和设备树,根文件系统用NFS挂远程目录。这样内核换新、rootfs换版本都不用重烧Flash,只要重启板子。

第一次接触NFS启动,很多人会卡在挂载超时。建议先确认板子能不能ping通开发机,然后在bootargs里手动指定ip:

ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off

等挂载成功后,在开发机上直接往rootfs目录里丢文件,板子上立刻可见。需要改动rcS或inittab时,改完开发机侧同步一下即可,板上重启生效。这套流程能极大缩短"改配置-重新打包-烧写"的循环。

NFS调试也有一些边界:不要在rootfs里写太多日志,否则大量IO走网络会拖慢系统;不要在no_root_squash的rootfs上跑生产服务,权限太开放了;如果NFS挂载正常但系统启动后卡住,往往不是NFS的问题,而是rcS脚本或服务依赖问题,需要回到串口log去看。

6. 根文件系统"起不来"时的高频坑位与排查思路

6.1 "VFS: Unable to mount root fs"不一定是文件系统损坏

内核日志里出现"VFS: Unable to mount root fs on unknown-block"这类信息时,很多人第一反应是文件系统坏了。实际上一大半情况不是分区坏了,而是内核根本找不到root参数指定的设备。

排查顺序我建议这样:先看启动日志里有没有注册出对应的块设备。比如root=/dev/mmcblk0p2,那日志里有没有mmc0和mmcblk0?如果没有,说明mmc驱动没编进内核,或者设备树没配置好。如果有设备但mount不上,再查rootfstype是不是写错,或者这个分区上是不是空的。手边有串口就全程盯着,一句"unknown-block(179,2)"能给你省很多事。

还要留意一个常见差异:SPI Nor Flash上很多用的是mtdblock设备,root=/dev/mtdblock3,而NAND/MMC用mmcblk,两者不能混写。网上很多例程只按自己的平台来,照抄容易翻车。

6.2 init进程"找不到"的三种死法

系统能挂载根文件系统,但在init这里挂掉,通常有三种表现:

第一种,日志是"No init found. Try passing init= option to kernel",说明内核没在默认位置找到/sbin/init。这时先ls一下rootfs的/sbin/init是否存在,有没有被软链接指错地方。

第二种,日志显示文件存在但exec失败,比如"Failed to execute /init (error -2)"。-2是ENOENT,常见原因不是文件不存在,而是动态链接器或共享库缺失。用readelf看interpreter,到rootfs里确认动态链接器对应文件是否在标准路径。上面说的ld-linux-armhf.so.3那个坑就是这一类的经典案例。-8则是EXEC格式错误,八成是架构不对,拷成了x86版busybox。

第三种,init起来了,但inittab里配置的shell或脚本全部失败,表现为串口无提示符或反复打印"can't access tty"或"Job control turned off"。这种要回到/etc/inittab和/etc/init.d/rcS逐行排查,尤其注意换行符和权限。

6.3 CRLF、文件权限和setuid这类"隐形杀手"

嵌入式rootfs的脚本文件经常是在Windows下编辑后直接放进来的,CRLF换行会在Linux下引发各种灵异事件。最典型的是rcS第一行#!/bin/sh\r,内核挑中了,但/bin/sh把\r当成命令的一部分,执行时直接"not found"。

排查这类问题很简单:在开发机上用file或cat -A查看rootfs里的脚本,如果有^M$,说明混入了CRLF。一次性转换可以用:

sed -i 's/\r$//' rootfs/etc/init.d/rcS

另外脚本权限和busybox的setuid位也容易被忽略。BusyBox如果需要提供login、su这类能力,/bin/busybox必须带setuid位,否则很多操作会静默拒绝。还有两类文件权限问题:一是/etc/passwd如果写错会导致login起不来,二是rootfs里目录权限如果全是777,生产环境看着都头疼。一个比较稳妥的习惯是:组装完rootfs后,把所有脚本和可执行文件过一遍权限,尤其是setuid位,不要全盘一把梭。

6.4 我的排查路线:先用最小ramdisk把系统救活

遇到根文件系统一直起不来的棘手情况,我的固定打法是用一个最小静态BusyBox ramdisk先启动系统,再做二次排查。这个ramdisk不用大,包含busybox和busybox --install,再加一个最简单的init脚本把shell拉起来就行。通过initramfs进入后,系统已经有一个可用的shell环境,再去mount真正的rootfs分区,逐项检查文件、权限、库依赖。

具体做法是:编译一个静态BusyBox,做成cpio格式initramfs,在U-Boot或QEMU里指定initrd加载。起来的系统里什么高级功能都没有,但足够你观察硬件寄存器、块设备节点、分区表,也可以把挂不上的rootfs挂到/mnt下chroot进去,慢慢定位。曾经有个板子顽固地报"No working init found",我用最小ramdisk进去后发现它真正的rootfs分区根本没被格式化,工厂镜像里放的是一个空文件系统,这个问题光看线上日志很难想到。

这套思路,也适用于BusyBox和Dropbear集成后突然出现的新问题。先用最小系统验证基础链路,再逐步引入业务组件,定位面立刻缩小一大半。如果你现在也正被某个rootfs启动问题卡住,不妨先别盯着日志焦虑,把问题想象成一条链路:内核加载rootfs、exec init、inittab拉服务、rcS做初始化,顺着这个链路一层层验证,大多数问题都能在半小时内找到根因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 10:40:09

搞懂JIS公差配合与基孔制选择,机械设计才能少返工

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 10:32:44

树莓派Pico ADC采集实战:定时温度记录与MicroPython避坑指南

手头一个开源硬件项目需要做环境温度记录,每隔十几秒采一次温度,存成日志。我翻了一圈手边的板子,最后选了树莓派 Pico。原因很直接:便宜、功耗低、MicroPython 生态成熟,而且 RP2040 片内带了一颗 12 位 ADC 和一颗内…

作者头像 李华
网站建设 2026/9/6 10:30:09

RK3566实战:强化学习四足机器人从训练到实机部署全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 10:27:12

RISC-V启动流程详解:从复位向量到内核跳转的完整路径

做嵌入式这行越久,越发现 RISC-V 的启动流程是个绕不开的坎。很多朋友拿到开发板,串口刚连上,一按复位,看到 Bootloader 正常打印、内核顺顺当当加载起来,就觉得一切理所当然;可真到出问题时,从…

作者头像 李华
网站建设 2026/9/6 10:26:22

边缘AI语音唤醒的MCU实现:ML-KWS-for-MCU源码静态评测与工程架构深析

这个项目我在看边缘AI部署方案时就盯上了。当时要评估在Cortex-M级别芯片上做语音唤醒的可行性,找了一圈开源方案,最后把目光落在ARM官方维护的ML-KWS-for-MCU上。它在ARM边缘AI开源生态里是少有的“麻雀虽小五脏俱全”的范本:训练脚本、模型…

作者头像 李华
网站建设 2026/9/6 10:25:26

出租房全自动波轮洗衣机:从成本拆解到安装维护的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华