
1. 为什么嵌入式Linux绕不开BusyBox1.1 从一块“最小可用系统”的尴尬说起前几年我做一块ARM开发板的系统移植内核编译完、DTS改完、u-boot也跑起来了结果卡在一个非常尴尬的环节内核起来之后用户空间什么都没有。没有/bin/ls没有/bin/sh没有mount连cat /proc/cmdline都敲不出来。内核宏亮的启动日志刷完之后控制台就死寂一片。你想用init/bin/bash进入一个可操作的shell却发现连bash这个文件都不存在。那一刻你会真正理解一件事Linux内核只是一台没有方向盘的引擎用户空间才是让这台机器真正跑起来的驾驶舱。而解决这个尴尬的标准答案就是BusyBox。这个体积只有几百KB的“小东西”把ls、cp、sh、mount、ifconfig、vi、udhcpc等两百多个常用命令全部塞进了一个静态链接的二进制文件里。嵌入式Linux开发圈子里管它叫“瑞士军刀”一点都不夸张——你很难找到第二个工具能用一个文件撑起一套可用的Linux用户环境。这篇文章我希望把BusyBox这件事讲透。不只是教你怎么make menuconfig然后编译而是把它为什么能这么小、它和根文件系统之间到底是什么关系、从零到一搭出能跑并且方便开发调试的根文件系统整个过程你会踩到哪些坑都尽量说清楚。适合正在学嵌入式Linux、准备自己动手做系统移植的读者也适合做了几年应用开发但没从头搭过系统的朋友。1.2 BusyBox设计哲学一个二进制全部功能先理解一个核心问题BusyBox为什么能这么小普通桌面Linux发行版里ls是一个独立的可执行文件grep是另一个独立文件tar又是一个。每个文件都各自链接了C库、各自有完整的初始化逻辑。体积大是一方面关键是在Flash容量以MB甚至KB计量的嵌入式设备上这种“一个命令一个文件”的铺张浪费根本不现实。BusyBox解决思路非常朴素把二百多个命令的main函数全部收编进一个源码树统一编译、统一链接最后生成一个busybox可执行文件。运行时通过命令名分发到对应的函数入口这就是它所谓的applet机制。你可以把它理解成一个小区物业——一个前台接待员干了几十种活你来报修他就处理报修流程你来交水电费他就切换成收费模式人来人往前台永远只有一个人。裁剪方面它更是做到了极致。每个命令都支持精细的功能开关比如ls要不要支持颜色、tar要不要支持压缩、vi要不要支持语法高亮全部可以在menuconfig里勾掉。我见过一个最小配置只保留sh、init、mount、ls、cat五个命令busybox二进制体积不到200KB。这点体积在如今动辄几十MB的APP面前不值一提但在嵌入式Flash上200KB可能就是一块芯片选型的转折点。2. BusyBox的命令分发内核applet机制与链接方式2.1 从argv[0]到函数跳转的秘密很多人第一次接触BusyBox都会有个疑问为什么执行ls实际跑的是busybox为什么把busybox改名成cp再执行它就变成了cp答案藏在C语言的main(int argc, char *argv[])里。当你在shell里敲一个命令时内核通过execve执行对应文件argv[0]就是你在命令行里输入的那个名字。BusyBox在启动时第一件事就是读取argv[0]提取出basename然后去一张applet查找表里匹配这个名字匹配到了就跳转到对应的applet_main函数。这就是为什么BusyBox的安装过程里有一个关键步骤在/bin、/sbin下创建大量指向/bin/busybox的符号链接。/bin/ls只是个符号链接真正执行的是busybox这个文件但argv[0]是lsbusybox就知道自己该扮演ls。这里有个实操中容易踩的细节如果你直接把busybox复制成/bin/ls硬复制而非链接那你在命令行敲ls确实能用但如果你手动/bin/busybox ls这样调用你会发现它不工作——因为argv[0]变成了/bin/busyboxbusybox找不到名为busybox的applet匹配规则默认会打印usage然后退出。对于这种调用方式busybox其实是有处理的它会进入通用命令行解析即busybox ls这种形式是可以用的。但反过来如果你想让它无条件以ls身份运行更稳妥的做法还是用符号链接让argv[0]的basename准确命中。2.2 功能裁剪的关键menuconfig里的那些勾选项BusyBox的菜单配置和Linux内核用的是同一套Kconfig机制所以如果你编译过内核对这个界面一定不陌生。make menuconfig进入配置界面后你能看到一长串按功能分类的applet列表从coreutils核心工具、shellsshell、networking网络工具到init、login、miscutils等等。每一个applet下还有二级选项控制它更细的行为。我的习惯是第一遍先把整个菜单过一遍了解都有什么命令。然后回到默认配置make defconfig在此基础上按项目需求裁剪。裁剪优先级是这样的产品功能真正用到的命令必须保留这是底线。系统启动和调试必需的命令要保留比如mount、sh、cat、ls、ps、ifconfig、ping、udhcpc。调试阶段少了这些你会非常难受。可有可无的大块头尽量关掉。比如vi完整版能占几百KB、awk完整版体积不小、find里的高级特性。开发板调试阶段可以开着产品化时能关就关。安全相关的选项要谨慎比如telnetd这种明文登录的服务调试可以用上线前必须关。还有一个很多人忽略的选项Settings - Build Options - Cross Compiler prefix。这里填你的交叉编译工具链前缀比如arm-linux-gnueabihf-。如果填错了或者不填make的时候会用gcc去编本机版本编出来的东西拷到板子上直接报Exec format error这是新手非常常见的翻车现场。2.3 静态链接还是动态链接一个影响全盘的决策这是BusyBox构建时最需要慎重决定的选项。Settings - Build Options - Build BusyBox as a static binary勾选就是静态链接不勾就是动态链接。静态链接意味着glibc或者musl的代码直接被塞进了busybox可执行文件里。好处是运行时不再依赖任何.so文件拷一个busybox到任意目录都能直接执行坏处是体积会膨胀我记得用glibc静态链接的busybox比动态链接的大个1MB左右是正常的。动态链接则相反busybox本身很小但对目标板上的C库有要求。如果板子上的libc版本和编译时用的不一致运行时会报version GLIBC_X not found之类的错。所以动态链接方案要求你对根文件系统里的C库版本有严格管理一般是把整个工具链的sysroot里的lib目录原样拷贝进根文件系统。对于刚入门的朋友我的建议分两种场景如果你是做产品、有稳定的工具链和libc版本管理方案动态链接能省不少Flash空间值得用。如果你是学习、做实验、或者工具链还不是特别确定直接用静态链接。省去一堆依赖问题先把系统跑起来才是最关键的。我自己做实验板的时候默认静态链接只有做产品优化时才会切到动态链接去抠那1MB的空间。这个取舍没有绝对的对错关键是理解背后的代价。3. 手把手构建最小根文件系统从编译BusyBox到init跑起来3.1 目录骨架根文件系统的“户型图”在动手编译之前先搭目录骨架。传统Linux根文件系统的目录结构是有讲究的BusyBox初始化时很多路径都是写死的。创建一个rootfs目录按下面的结构建mkdir -p rootfs/{bin,sbin,usr/bin,usr/sbin,etc/init.d,lib,proc,sys,tmp,dev,var,mnt}每个目录干什么用我说几个关键的/bin和/sbin放busybox的符号链接和运行所必需的可执行文件。区分在于/sbin通常放系统管理类命令但BusyBox并不强制安装时会自动放。/etc配置文件目录inittab、fstab、passwd都在这里。/proc和/sys挂载procfs和sysfs的挂载点内核与用户空间交换信息的虚拟文件系统。/dev设备节点目录可以静态创建也可以用devtmpfs或mdev动态管理。/tmp临时文件目录一般挂tmpfs。注意BusyBox不会帮你创建这些目录它默认假设它们存在。如果你漏了/proc这个挂载点后面mount procfs时会失败很多工具比如ps会表现异常。3.2 编译BusyBox并安装到rootfs假设你已经下载了busybox源码我建议用最新稳定版老版本比如1.22.1在一些新内核上会有兼容问题进入源码目录export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- make menuconfig配置里把Settings - Build Options - Cross Compiler prefix填好把Build BusyBox as a static binary勾上。然后make -j8 make install CONFIG_PREFIX/path/to/rootfsCONFIG_PREFIX就是刚才建的rootfs目录。make install执行后busybox会被复制到rootfs/bin/busybox并且自动在bin和sbin下创建所有已启用applet的符号链接。这一步做完敲rootfs/bin/busybox ls应该能看到一堆内容。用file rootfs/bin/busybox确认架构$ file rootfs/bin/busybox rootfs/bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked看到ARM和statically linked就对了。如果看到x86-64回炉重查交叉编译配置。3.3 设备节点、fstab与mdev内核和用户空间的握手根文件系统里必须有几个基础的设备节点否则内核挂载根文件系统后会因为找不到/dev/console而panic。最简单的办法是用devtmpfs。现代内核都支持配置CONFIG_DEVTMPFS_MOUNTy后内核会在挂载根文件系统时自动挂载devtmpfs到/dev并且自动创建设备节点。这种情况下你甚至不需要手动创建/dev/console。但如果你的内核没开这个选项就得老老实实静态创建几个必须节点cd rootfs sudo mknod dev/console c 5 1 sudo mknod dev/null c 1 3 sudo chown root:root dev/*至于/etc/fstab我的最小配置长这样/dev/root / ext4 defaults 1 1 proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 tmpfs /tmp tmpfs defaults 0 0 devtmpfs /dev devtmpfs defaults 0 0fstab里不写这些条目其实也能跑但写上后可以让你随时用mount -a一键挂载所有文件系统调试方便不少。嵌入式环境里很多脚本都会在启动早期调mount -a所以这个文件值得维护好。设备动态管理方面BusyBox的mdev是个轻量级方案配合内核的uevent机制能在设备插入时自动创建/删除节点。在/etc/init.d/rcS里加一行echo /sbin/mdev /proc/sys/kernel/hotplug mdev -smdev -s会扫描/sys为已存在的设备补充节点。这个方案比起桌面Linux的udev来说功能弱很多但胜在体积小、依赖少嵌入式场景完全够用。3.4 init与inittab用户空间的第一个进程内核启动最后阶段会尝试执行根文件系统里的/sbin/init找不到就找/bin/init再找不到依次尝试/bin/sh。BusyBox的init就是通过符号链接/sbin/init - /bin/busybox来提供的。BusyBox init的行为由/etc/inittab控制。我的最小inittab长这样::sysinit:/etc/init.d/rcS ::respawn:/sbin/getty -L ttyS0 115200 vt100 ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r解释一下sysinit行指定系统初始化脚本路径通常就是rcS。respawn行是重中之重。ttyS0对应你的串口设备节点如果没有这一行串口上根本不会出现登录提示符你会以为系统没起来。波特率要和你终端软件一致我这里用的115200。ctrlaltdel处理CtrlAltDel组合键嵌入式设备上一般映射到重启。shutdown行会在系统关机时自动执行umount。这里的getty也是BusyBox提供的applet它负责打开串口、显示登录提示、启动登录程序。如果你不想登录只想直接进shell可以把respawn行改成::respawn:-/bin/sh前面的-表示让sh作为登录shell会读取/etc/profile。调试阶段我更喜欢直接进shell不做登录校验省得每次敲用户名密码。3.5 rcS脚本的写法与易错点/etc/init.d/rcS是一个普通shell脚本但它扮演的角色很重要——它是系统在内核态和用户态之间切换后的第一个“大总管”。#!/bin/sh mount -a mdev -s echo System boot completed.第一行#!/bin/sh必须有否则内核或init执行脚本时会找不到解释器。注意这里/bin/sh是busybox的符号链接已经由make install创建好了。这段脚本里有个隐蔽的坑如果你fstab里的条目有误比如把proc写成了实际不存在的设备路径mount -a会报错但脚本并不会因此中断——mount -a默认失败后继续处理下一条。所以fstab写错不会导致启动完全失败但会留下一堆诡异的副作用比如ps命令显示不出来进程列表。排查时留意下/proc有没有挂上——敲个mount看看输出就知道了。脚本权限也要注意chmod x /etc/init.d/rcS别忘了。少这一步的话init会报cant execute /etc/init.d/rcS系统看起来“死了”实际上只是脚本没跑起来。4. 从内核态到用户态init、mount、sync与VFS的协作逻辑4.1 内核启动后到底发生了什么很多初学者对“启动”的理解停留在“内核跑起来就完事了”。实际上内核自我初始化结束后会做一件承上启下的事挂载根文件系统然后执行根文件系统里的init程序。这里涉及VFS虚拟文件系统的抽象机制——根文件系统既可以是ext4分区也可以是NFS远程目录还可以是initramfs内存盘但对内核和用户进程来说它们都是“一个文件系统树”感知不到底层差异。挂载根文件系统的方式通过内核命令行参数root指定root/dev/mmcblk0p2从eMMC或SD卡第二分区挂载root/dev/nfsNFS挂载后面细说root/dev/ram0从内存盘挂载内核先通过initramfs机制解开一个微小的临时用户空间如果你的内核配置了在这里加载真正的根文件系统驱动然后再真正挂载根。这一步对新手不太可见但理解它有助于排查“根文件系统挂载失败”一类的问题。执行/sbin/init后控制权完全从内核移交到用户空间。后续的一切——起服务、挂载文件系统、启动shell——都是用户空间的应用程序在做内核只是提供syscall接口。理解这个分界线很重要不是内核出问题才叫系统启动失败很多时候是init脚本或者busybox配置出了问题但表现看起来跟内核panic一样吓人。4.2 mount与VFS为什么“一切皆文件”不只是口号BusyBox里mount这个命令大家天天用但它的底层机制值得多说几句。Linux的文件系统之所以能支持几十种格式靠的就是VFS这层抽象。进程读写文件时内核通过VFS把读写请求转发到具体文件系统的实现函数对调用者来说完全透明——你只管用open/read/write根本不用关心底下是ext4还是NFS。mount的作用就是把一个具体存储设备或远程资源关联到目录树的一个挂载点上。关联之后挂载点目录下原来的内容会被隐藏。这个特性在嵌入式里有个非常经典的用法把根文件系统做成只读然后把可写目录如/var、/tmp单独挂tmpfs既保护系统数据不被意外篡改又给运行时数据留了可写空间。BusyBox默认编译时把mount相关功能精简过但它支持的参数对于嵌入式场景足够用了。需要注意的一个坑用mount -t ext4 /dev/mmcblk0p1 /mnt时如果目标板内核没有把ext4驱动编进内核而是编成模块且模块又没加载mount会报unknown filesystem type ext4。所以嵌入式系统里我强烈建议把根文件系统要用的文件系统驱动直接编进内核built-in不要依赖模块。4.3 sync数据落盘的最后一道保险sync命令看起来没什么技术含量就是调用sync系统调用把脏页刷到磁盘。但嵌入式场景里它往往决定你掉电后是数据完好还是文件系统损坏。为什么需要sync因为Linux的页缓存机制。你可能以为fwrite返回成功数据就写进磁盘了实际上写到了内存的page cache里真正落盘的时机由内核的writeback机制决定默认30秒左右。如果在这期间突然掉电缓存里的数据就丢了。严重的情况下元数据没写完整整个文件系统都打不开。BusyBox的sync命令只是触发一次刷新。更系统的做法是在关键写操作后调sync或者用mount -o sync挂载数据完整性要求高的分区。我见过不少产品在关机脚本里执行sync sleep 1 sync第一次sync触发写回等一秒让它完成第二次sync确保所有队列清空。这个双重sync的技巧看着土但在老内核、慢Flash组合的设备上确实有用。4.4 根文件系统的只读策略与OverlayFS说句题外话嵌入式设备上我特别推荐根文件系统只读挂载。原因很简单掉电不怕、病毒难写、误操作少了。代价是什么有些程序需要写/etc、写/var。解法是OverlayFS。上层的可写层是tmpfs内存下层的只读层是根文件系统。写入操作落在内存里掉电即失系统主体永远干净。BusyBox里对应命令是mount -t overlay。这种方案足够大多数设备用了还能把Flash磨损降到最低。5. 开发阶段的效率神器NFS挂载根文件系统5.1 为什么开发期别急着烧Flash嵌入式开发有个高频需求频繁修改根文件系统里的脚本、可执行文件、库。如果用传统方案每次修改都得重新打包镜像、烧写Flash、再启动设备一轮下来几分钟就没了一天改十几轮时间全浪费在等待上。NFS挂载直接改变这个节奏目标板的内核起来后通过网口挂载开发机上的一个NFS共享目录作为根文件系统。你在开发机上直接修改文件目标板重启后用的就是新内容连重新打包都省了。这个方案的局限是目标板必须有网口、开发机必须开NFS服务以及内核必须编译了NFS客户端和root over NFS的支持CONFIG_ROOT_NFSy。生产环境用不上但开发阶段它几乎能改变你的工作效率。5.2 开发机配置与内核命令行参数开发机Ubuntu为例上用nfs-kernel-server把/opt/rootfs这个目录共享出来/opt/rootfs *(rw,sync,no_root_squash,no_subtree_check)三个关键参数rw读写权限。sync同步写避免NFS写入时掉电导致服务端缓存不一致。no_root_squash允许目标板的root用户保留root权限。如果没有这个选项目标板上root创建的文件的属主会被映射成nobody很多操作会莫名失败。然后配置目标板的内核启动命令行root/dev/nfs nfsroot192.168.1.100:/opt/rootfs,v3,tcp ip192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off第一个IP是开发机地址第二个是目标板IP第三个是网关。参数虽然长但含义不难理解我把根文件系统的位置、网络配置全部通过命令行传给了内核。u-boot环境下可以用setenv bootargs预设这一串。开发板没有IP没关系先用串口线连接在u-boot里手动设置或者在bootargs里让目标板用DHCP自动获取IP。5.3 NFS根挂载失败的排查链路NFS挂载失败是嵌入式开发里最常遇到的问题之一常见原因按出现频率排序网络不通先ping一下开发机。目标板上不了网什么都白搭。这个问题听着蠢但很多时候是网线没插好或IP配置冲突。内核少了NFS驱动检查内核配置确认CONFIG_NFS_FS和CONFIG_ROOT_NFS都编进去了。开发机防火墙拦截NFS端口NFS依赖rpcbind111端口和mountd动态端口防火墙一拦就连不上。no_root_squash没设置挂载能成功但写入时报权限错误。NFS版本不匹配内核的NFS客户端默认用v4服务器只支持v3时会失败。在nfsroot参数里显式指定v3能解决大部分兼容问题。排查时建议先在内核命令行里加nfsrootdebug内核会打印NFS挂载的详细调试信息定位更快。这套链路走通过一次后面开发就如同本地操作一样顺畅。6. 给开发板加把“远程钥匙”与dropbear集成6.1 开发期SSH到底有多重要串口调试虽然可靠但有个硬伤串口线长度有限调试时你人必须在板子旁边。而一旦板子放到现场或者机柜里串口访问就变得不现实了。SSH远程登录对嵌入式开发来说不是锦上添花是刚需。OpenSSH功能全面但体积偏大依赖PAM、openssl等一长串库编译进根文件系统很占空间。这时候dropbear就是更好的选择。dropbear是一套专为嵌入式环境设计的轻量级SSH服务端/客户端实现支持SSH v2协议编译后二进制几百KB依赖少在BusyBox社区里非常流行。把dropbear集成进根文件系统目标板就能被远程登录配合scp直接传文件开发效率提升非常明显。6.2 dropbear的编译与集成步骤假设你已经有了交叉编译环境。下载dropbear源码编译安装到rootfs./configure --hostarm-linux-gnueabihf --disable-zlib \ --prefix/usr CCarm-linux-gnueabihf-gcc make -j8 make install DESTDIR/path/to/rootfs--disable-zlib是为了减少对zlib的依赖如果目标板有zlib库保留也行。编译出来的dropbear、dropbearkey会装到/usr/sbin和/usr/bin下。然后在目标板的/etc/init.d/rcS里加两行mkdir -p /etc/dropbear dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key dropbear -p 22第一行创建密钥目录。第二行生成SSH通信所需的RSA主机密钥。第三行启动服务监听22端口。需要注意dropbearkey生成密钥只需要执行一次生成的dropbear_rsa_host_key是持久化保存的。如果把SSH服务做成每次开机都重新生成密钥客户端会抱怨host key冲突甚至拒绝连接这是个典型的坑。正确做法是把密钥文件和根文件系统一起打包烧录或者挂载一个可写的分区专门存放。6.3 SSH用户与安全策略dropbear默认允许root登录但需要密码。BusyBox提供的passwd命令可以设置root密码passwd root如果你不想每次登录输密码可以配公私钥认证。把开发机的公钥放到目标板的/root/.ssh/authorized_keys里dropbear启动后会自动读取。这个文件需要手动创建mkdir -p /root/.ssh echo ssh-rsa AAAA... /root/.ssh/authorized_keys chmod 644 /root/.ssh/authorized_keys权限设置是这里最隐蔽的坑authorized_keys权限太开比如chmod 644变成普通组可读时较新版本的dropbear会拒绝读取它以保证安全。一般要求文件属主是root权限为600或644某些版本宽松些目录权限建议700。我遇到过几次“密钥明明比着教程写了还是连不上”的情况都是权限问题。7. 从rootfs到可烧写镜像打包与存储方案7.1 制作ext4根文件系统镜像开发调试完成后最终要把rootfs目录打包成一个可烧写的镜像。以ext4为例dd if/dev/zero ofrootfs.ext4 bs1M count64 mkfs.ext4 -d /path/to/rootfs rootfs.ext4mkfs.ext4 -d参数直接把指定目录内容写进镜像比先挂载再拷贝方便得多。count64是镜像大小按你实际rootfs的体积调整我用64MB举例。如果是eMMC或SD卡启动还需要用fdisk或parted给镜像做分区表把rootfs.ext4当作其中一个分区烧进去。具体的烧写命令取决于你的Flash类型和烧录工具这里不展开。7.2 squashfs只读方案与OverlayFS组合产品级设备我更推荐squashfs。这是一种高压缩比的只读文件系统特别适合Flash这种介质读取快、抗掉电。缺点是不可写所以需要配合OverlayFS。整体方案是根文件系统用squashfs镜像只读挂载。/var、/tmp、/etc等需要写入的目录用tmpfs或单独的读写分区挂载覆盖。如果产品需要保存配置划分一个小分区或文件作为持久化存储在rcS里挂载后手动创建符号链接。这样做的好处很明显根文件系统永远不会被写坏突然掉电也不会损坏系统。代价是设计复杂度上升分配的空间要规划好。squashfs镜像制作mksquashfs /path/to/rootfs rootfs.squashfs -comp xz内核必须编译CONFIG_SQUASHFSy才能挂载。这个方案是很多商业路由器、门禁设备的标配值得熟悉一下。7.3 根文件系统瘦身的几条经验体积直接关系到Flash选型成本做产品时抠空间是有价值的。用strip去掉调试符号。交叉编译时加-s参数或者arm-linux-strip处理bin/so文件体积直接小一到两倍。删掉不必要的busybox applet。如果产品只用十个命令其他全部关掉。精简库文件。动态链接方案下用arm-linux-readelf -d查看依赖只拷贝用到的.so能省不少空间。但要注意依赖递归别删了A却发现A依赖的B也被删了。日志别落盘。嵌入式设备上日志输出到串口或logd尽量别写文件既省空间又减Flash磨损。8. 这几年我在BusyBox上踩过的坑8.1 命令“消失”的真相有段时间我在目标板敲svc命令提示not found。查/bin/svc符号链接明明存在指向/bin/busybox。反复检查最后发现是menuconfig里这个applet根本没有启用make install时自然就没创建链接。busybox的符号链接不是全部安装的只装启用的。所以看到“命令不存在”时先别急着怀疑文件系统去menuconfig里看一眼这个applet是否选中了。8.2 动态链接的glibc依赖地狱有次把动态链接的busybox拷到目标板启动时报/lib/ld-linux-armhf.so.3: No such file or directory。原因是编译用的工具链glibc版本和板子上的libc不一致或者lib目录没拷全。解决方法是编译时从工具链sysroot里把lib和usr/lib完整拷贝别嫌大少一个符号都可能启动失败。学会用arm-linux-readelf -d查看ELF的依赖很多这种问题都能提前发现。8.3 串口登录卡死与getty的交互问题调试中最诡异的问题是系统起来了串口有输出但键盘输入没反应。后来发现原因在inittab里getty的-L参数用错了导致没有正确配置line discipline。另外还有一个常见坑getty的命令行参数里指定了ttyS0但设备的实际串口名是ttyAMA0树莓派或ttymxc0NXP i.MX。不同的SoC串口设备名不一样一定先确认内核打印里用的是哪个。8.4 时间不同步导致的一连串奇怪问题有次集成dropbear后ssh登录报Host key verification failed反复删known_hosts也没用。折腾半天发现是目标板RTC时间不对导致密钥文件的mtime异常客户端以为是新的host key。嵌入式设备普遍没有RTC电池时间不准是常态。解决方法是在rcS里用rdate或ntpclient从局域网时间服务器同步时间或者干脆让SSH客户端忽略时间戳变化。这个问题让我印象很深也提醒我嵌入式里很多神秘问题最后都指向一些不起眼的基础设施。9. 最后分享两个提高效率的小习惯BusyBox这套东西你现在可能觉得内容不少但真正用过一轮以后它就会变成一个非常顺手的基础工具。这里分享两个我一直在用的小习惯。第一个是创建一个Makefile来管理整个构建流程把下载源码、配置、编译、安装、打包镜像这几步串起来。我早期都是手动敲命令经常忘记某一步后来把命令写进Makefile一条make rootfs就把所有事做完了干净利落。你完全可以照这个思路建一套自己的构建脚本。第二个是每次都把内核反编译出来的System.map和busybox的.config归档保存。出问题时能快速确认内核开了哪些功能、busybox启用了哪些applet排查问题会快很多。这俩文件加起来也没几KB看着不起眼关键时刻能救命。