折腾完内核和设备树之后,我踩到了教科书上不会告诉你的一步:内核起来只是“心脏开始跳”,想让整个系统站住,还得有一副完整的“骨架”——也就是根文件系统 Rootfs。最近在给一块国产 Cortex-A 系列开发板做系统移植,前面几篇分别解决了交叉编译环境、bootloader 和内核启动,到了第四篇,内核已经能正常解压并跑到挂载根文件系统的阶段,结果因为目录结构完全不存在,直接停在经典的 “No init found”。这篇就把 Rootfs 目录结构创建的完整思路、操作命令和踩坑记录一次说清楚。
开篇先把结论放这:Rootfs 目录创建不是“多敲几个 mkdir 的事”,目录缺哪个、权限给多少、特殊文件建不建,直接决定你的 sbin/init 能不能跑起来、设备节点能不能出现、最终系统能不能登录。
1. 为什么必须先建目录?Rootfs 的本质思考
1.1 从内核启动流程看目录的意义
很多人以为内核启动完成后,系统就“自动”有了文件系统,其实这是最大的误解。Linux 内核本身是个光杆司令:设备驱动、硬件初始化、内存管理都由内核负责,但用户态的进程、shell、配置统统来自根文件系统。内核启动流程走到最后一步,会去尝试挂载 rootfs,然后查找并执行第一个用户态程序。
这个程序默认路径有好几个候选,最常见的是 /sbin/init,如果指定了 init= 参数也可以换成 /bin/sh。无论哪个,内核都会用一个绝对路径去找这个文件。问题是:如果没有根文件系统,或者目录结构不完整,内核就会在挂载点看不到这些路径,然后打印 “Kernel panic - not syncing: No init found. Try passing init= option to kernel.”。
这说明了一个很底层的事实:Linux 的根文件系统不是一个普通数据仓库,它本身是内核启动链路中的一环。目录结构就是这张链路的骨架:/sbin 下必须有 init 或者指向 busybox 的链接,/lib 下必须能找到动态链接器和共享库,/dev 下必须有 console 和 null 这两个基础设备节点,/etc 下要有 inittab 或 rcS 之类的配置脚本。少了任何一环,系统都站不起来。
我在第一次试验时,光顾着把 busybox 编出来丢进 rootfs,却漏了 /dev/console 设备节点,启动后串口完全没有输出,屏幕黑压压一片。查了很久才发现,内核在启动阶段要打开 /dev/console 作为标准输入输出,这个节点不存在,console 上的所有打印就直接丢了。这种问题不是程序逻辑错误,而是文件系统目录“骨架”缺失导致的底层故障。
1.2 FHS 标准与嵌入式精简的平衡
Linux 文件系统有一套公开的目录规范,叫 FHS(Filesystem Hierarchy Standard),也就是文件系统层次标准。它规定了 /bin、/usr/bin、/etc、/var、/tmp、/home 这些顶层目录的用途。桌面发行版几乎完全遵循这套规范,目录非常多,光 /usr 下面就有十多个子目录,连 /usr/games 都有。
但嵌入式 Rootfs 不能照搬这套全量目录,原因很简单:空间和复杂度都受不了。一块成熟的嵌入式方案,整个 Rootfs 往往只有几十 MB,甚至几 MB。在这个体积下,把桌面发行版的完整目录树搬过来是灾难。轻则占用大量存储,重则各种无关配置和启动脚本互相干扰,导致系统行为诡异。
不过“精简”不等于“乱删”。我见过有些移植资料把目录砍到只剩 bin、sbin、etc、dev、lib、proc、sys、tmp、usr,连 /home、/root、/var 都不要。短期内这样确实能启动,但后患无穷:你装第一个需要写日志的应用程序时就傻眼了,因为没有 /var/log;你想用 root 用户正常登录,shell 会要 /root 目录作为 HOME,缺了就报各种配置错误。
嵌入式 Rootfs 的目录精简应该把握一个原则:在满足 FHS 兼容性的前提下,去掉明显用不到的目录,保留所有基础用户态程序运行所依赖的目录。我实际开发中推荐的底线是 /bin、/sbin、/lib、/etc、/usr、/var、/home、/root、/tmp、/proc、/sys、/dev、/mnt,后面需要 /opt 或 /media 再加也不迟。
1.3 目录结构决定后续移植的“影响半径”
目录结构创建这一步的“影响范围”比表面看起来大得多。它直接决定后面所有组件的落地位置和运行方式,我结合自己的移植经验梳理了几条:
- 影响第一个用户态程序的查找。内核要执行 /sbin/init,你的 init 或 busybox 必须出现在这个路径上,否则系统根本起不来。
- 影响动态库的搜索路径。交叉编译成的动态程序默认会去 /lib 找加载器和 so 文件,目录没有或者 so 放错位置,程序会报 “No such file or directory” 或 “cannot open shared object file”。
- 影响挂载点的可用性。/proc、/sys、/dev 是内核伪文件系统的挂载点,目录不存在,就算内核支持这些文件系统也无法挂载。
- 影响用户管理。passwd 文件里每个用户都有家目录路径,如 /home/xxx 或 /root,目录不存在,登录成功也会被踢回默认路径,或者出现各种权限异常。
- 影响程序运行期数据。日志写到 /var/log、锁文件放 /var/run、临时文件放 /tmp,这些目录缺失或者权限不对,应用会在运行时报错,而且报错信息经常很迷惑。
- 影响后续扩展。Builder 系统、软件包管理器、应用程序安装脚本都假定标准目录存在。如果你手工创建的目录不全,后面每装一个软件都要为“没有标准目录”买单。
所以,别把 Rootfs 目录创建看成一步“走个过场”的命令执行,它其实是整个移植方案的地基。地基歪一寸,上层楼就歪一丈。
2. 动手前必须明白:核心目录各自的角色
2.1 必建目录与功能对照
如果你用的目标平台是 ARM 32 位还是 ARM64,目录结构创建上没有任何区别,顶层目录名字都一样,区别只在于交叉编译工具链和库文件架构。我这边用 arm-linux-gnueabihf- 作为工具链前缀,先列出我最终创建的必建目录与对应作用:
| 目录 | 用途 | 嵌入式开发中的必要性 |
|---|---|---|
| /bin | 用户基本命令(sh、ls、cat、mv 等) | 必须 |
| /sbin | 系统管理命令(init、mount、ifconfig 等) | 必须 |
| /usr/bin | 额外的用户程序 | 建议保留 |
| /usr/sbin | 额外的系统管理程序 | 建议保留 |
| /lib | 动态链接器、共享库、内核模块 | 必须 |
| /etc | 系统配置(inittab、fstab、passwd、rcS) | 必须 |
| /home | 普通用户家目录 | 建议保留 |
| /root | 管理员家目录 | 必须 |
| /var | 可变数据(日志、锁、临时运行数据) | 必须 |
| /tmp | 临时文件 | 必须 |
| /proc | procfs 挂载点,进程/内核信息 | 必须 |
| /sys | sysfs 挂载点,设备与驱动信息 | 必须 |
| /dev | devtmpfs 挂载点或设备节点目录 | 必须 |
| /mnt | 临时挂载点 | 建议保留 |
| /opt | 第三方软件的安装目录 | 可选 |
这里有个容易忽视的点:/var 下面至少要拆出 /var/log、/var/run、/var/tmp 三个子目录。很多 init 脚本和应用程序默认往这三个路径写东西,不建的话启动脚本第一步就可能报 “Unable to open /var/log/messages”。
还有一个细节是 /usr。busybox 安装时默认会生成 /usr/bin、/usr/sbin、/usr/lib 这些链接,所以即使你觉得暂时用不到,预留 /usr 及其子目录会更省事,避免后面手动补目录时权限五花八门。
2.2 proc、sys、dev:三座特殊的“桥”
/bin、/etc 这些目录是真实存储在存储介质上的普通目录,而 /proc、/sys、/dev 这三个目录比较特殊:它们本质上是“挂载点”,对应的是内核提供的伪文件系统,数据并不存在磁盘里。创建目录只是准备工作,真正让它们工作起来要等到系统启动后执行 mount。
- /proc:procfs,进程和内核运行状态都在这里,ps、top、free 这些命令要正常工作,前提就是 /proc 被挂载。挂载命令是 mount -t proc proc /proc。
- /sys:sysfs,设备和驱动模型、总线、块设备拓扑等都在这里。mdev 或 udev 这类设备管理程序依赖 /sys 扫描设备并创建设备节点。
- /dev:设备节点目录。最理想的情况是用 devtmpfs 自动管理,内核启动时自动填充设备节点。挂载命令通常是 mount -t devtmpfs devtmpfs /dev。如果内核没开 devtmpfs,你就得靠 mdev/udev 或手工 mknod。
这三个目录如果不挂载,最明显的现象是:ps 命令报错,找不到 /proc/stat;插入 USB 设备没反应,mdev 收不到热插拔事件;/dev 下只有几个手动创建的节点,其他设备一律不出现。
我在第一次跑系统时,把 /proc 和 /sys 的空目录建好后就没管了,结果进入 shell 后敲 ps,内核直接打印 “can't open /proc/stat” 之类的报错。当时还以为是 busybox 编译问题,后来才发现纯粹是没挂载伪文件系统。
2.3 权限和属主:骨架的“关节强度”
目录结构建好不等于骨架耐用,权限和属主决定了这套骨架能不能被正常使用。Linux 系统对目录权限的判定和文件略有不同:目录必须有 x 权限才能被进入,必须有 r 权限才能列出内容,必须有 w 权限才能在里面创建/删除文件。
基础建议是所有顶层目录统一属主 root:root,目录权限保持 755(即 rwxr-xr-x),这能保证 root 和普通用户都能读、能进入,但只有 root 能创建文件。有几个目录要单独调整:
- /root 权限设 700。管理员家目录通常不允许其他用户查看,设成 700 避免普通用户窥探 root 的配置文件和私密数据。
- /tmp 权限设 1777。1 表示 sticky bit,也就是粘滞位。这个权限的作用是:所有用户都能在 /tmp 里创建文件,但只能删除自己创建的文件,避免互相误删。
- /dev 下的设备节点权限视情况而定。比如 /dev/null 一般是 666,方便所有用户丢弃输出;/dev/console 一般 600,只有 root 能访问控制台。
权限出问题时的表现很“诡异”。我遇到过某个工作目录缺 x 权限,结果 mount 命令执行时报 “Permission denied”,但 mount 本身是有权限的;还有一次 /tmp 权限设成了 755,一个普通用户程序写临时文件时一直报错,排查了很久才发现是目录权限过严。所以在创建目录后,我建议立刻用 ls -ld 把关键目录的权限列一遍,而不是等到系统起不来再怀疑。
3. 实操:把 Rootfs 骨架从零搭起来
3.1 准备工作区:环境变量与宿主机注意事项
在宿主机上创建 Rootfs 第一步不是 mkdir,而是先在习惯的工作目录下建一个统一的项目根目录。我一般在 Linux 开发机上用一个非 root 用户的目录,比如 /home/dev/work/,然后在这个目录下新建 rootfs 作为 Rootfs 的顶层目录。
这里有个重要经验:不要在 root 用户的主目录下直接开建,也不要用已经用过的发行版 rootfs 来改。发行版 rootfs 里有大量与目标平台无关的服务、systemd 目录、启动脚本,混在一起后期排查起来痛苦不堪。从零手工创建反而更可控,一切都是你自己加进去的。
建议先定义环境变量,省得后面每次敲全路径:
export ROOTFS=/home/dev/work/rootfs后面所有命令都可以用 $ROOTFS 指代 Rootfs 顶层目录。这个变量在当前 shell 里有效,如果你在其他终端操作,记得重新 export 一下,或者直接写一个环境变量文件 source。
3.2 创建目录的完整命令与逐条解读
创建目录我用的是 mkdir -p,配合 shell 的花括号展开一次完成。直接贴我当时用的完整命令:
sudo mkdir -p $ROOTFS/{bin,sbin,lib,etc,root,home,var,var/log,var/run,var/tmp,tmp,proc,sys,dev,dev/pts,mnt,opt,usr,usr/bin,usr/sbin,usr/lib,usr/local}逐段理解一下:
- {bin,sbin,...} 是 shell 的花括号展开,等价于逐个 mkdir。
- dev/pts 被创建出来是为了后面挂载 devpts 伪终端文件系统。master/slave 伪终端节点都在 /dev/pts 下,没有挂载点则 SSH 或 telnet 登录这类需要终端设备的服务可能异常。
- var/log、var/run、var/tmp 是新增的三个子目录,它们都用于可变数据,后续日志、锁文件、临时文件都有家可归。
- usr 和 usr/bin、usr/sbin、usr/lib 是给标准用户态程序使用的,busybox 安装脚本会默认往这些路径放内容。
创建完目录,接着设置基础权限和属主。建议先统一把顶层目录设为 root:root,再对特殊目录单独调整:
sudo chown -R root:root $ROOTFS sudo find $ROOTFS -type d -exec chmod 755 {} \; sudo chmod 1777 $ROOTFS/tmp sudo chmod 700 $ROOTFS/root这里 chown -R root:root 的作用是把 Rootfs 里所有文件和目录的属主统一为 root,这样后续从 NFS 启动时不会因为属主不一致出现权限异常。find 的 -exec chmod 755 是批量把目录都设置成标准权限。随后 /tmp 覆盖为 1777,/root 覆盖为 700。
还有一个容易被遗漏的操作:创建设备节点。如果你打算依赖 devtmpfs 或 mdev 来管理 /dev,可以跳过这一步;但为了稳妥,我建议一开始就把基础节点建好,防止 devtmpfs 没有配置或 mdev 还没运行时,系统在启动早期就缺少 /dev/console:
sudo mknod -m 600 $ROOTFS/dev/console c 5 1 sudo mknod -m 666 $ROOTFS/dev/null c 1 3mknod 命令中的 c 表示字符设备,后面的 5 1 是主设备号和次设备号。console 的主设备号是 5,次设备号是 1;null 的主设备号是 1,次设备号是 3。这是内核约定好的标准分配,不能写错。写错之后设备文件就算存在,内核打开它时也会报错或者直接当普通文件处理。
3.3 校验骨架:树形结构、权限与特殊文件
目录建完别急着往里面放文件。先把“骨架”校验一遍,确认没有遗漏。常用命令有 tree 和 find,如果宿主机没装 tree,直接用 find 足够:
tree -L 2 $ROOTFS或者:
find $ROOTFS -maxdepth 2 -type d | sort还要看一下关键目录和设备的权限:
ls -ld $ROOTFS/tmp $ROOTFS/root $ROOTFS/var/log ls -l $ROOTFS/dev/console $ROOTFS/dev/null我当时注意到一个细节:用 mknod 创建的 /dev/console 和 /dev/null 在ls -l里显示类型是 c,而不是普通文件的-。看到 c 开头的权限位就说明特殊设备文件建对了。
如果后面要从 NFS 启动验证挂载点,可以先把宿主机的 /proc、/sys、/dev 用 mount --bind 临时绑到 Rootfs 对应目录,这样在宿主机里就可以直接观察 Rootfs 启动后的完整文件系统视图。这个操作只是宿主机侧验证,不是最终目标系统必需的启动流程:
sudo mount --bind /proc $ROOTFS/proc sudo mount --bind /sys $ROOTFS/sys sudo mount --bind /dev $ROOTFS/dev这种临时挂载的好处是,你可以直接在宿主机上 chroot 进入 Rootfs,做快速验证。chroot 进去之前,我建议先检查 /bin/sh 或 /sbin/init 是否存在,如果还没有,那就先进入下一步:把 BusyBox 装进去。
4. 骨架验证:放上 BusyBox 并启动最小系统
4.1 BusyBox 的编译与安装要点
目录结构只是一个空壳,真正让系统“能跑”的第一个程序通常是 BusyBox。BusyBox 是一个把常用 Unix 命令打包成单个二进制的程序,通过符号链接或参数模拟 ls、sh、mount、init 等命令。嵌入式系统里它几乎是事实标准。
编译 BusyBox 时,我特别强调一个关键选项:静态编译。很多人第一次交叉编译 busybox,默认配置是动态编译,装到 Rootfs 后一运行就报 “/bin/sh: not found”。其实 busybox 文件明明在那里,问题在于动态链接器 /lib/ld-linux-armhf.so.3 或系统需要的基础库文件还没放进去。如果你还没有搭好动态库环境,最省事的方法是选静态编译。
配置命令大致如下:
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make defconfig make menuconfig进入 menuconfig 后,在 BusyBox Settings -> Build Options 里找到 “Build static binary (no shared libs)” 并选中。然后编译安装:
make -j$(nproc) make install CONFIG_PREFIX=$ROOTFSCONFIG_PREFIX 指向你的 Rootfs 目录,BusyBox 会把 busybox 二进制本身放入 /bin 或 /usr/bin(取决于配置),同时创建 /sbin/init、/bin/sh、/bin/ls 等大量符号链接,都指向 /bin/busybox。这一步就是在给骨架填充第一块“肌肉”。
安装完成后,验证一下骨架是否具备启动最小系统的条件:
ls -l $ROOTFS/sbin/init $ROOTFS/bin/sh $ROOTFS/bin/busybox如果看到类似 “/sbin/init -> ../bin/busybox” 的符号链接,就说明基础命令已经就位了。
4.2 通过 NFS 挂载验证 Rootfs
Rootfs 创建好后,最常用的快速验证方式是通过 NFS 让目标板直接挂载宿主机上的 Rootfs。这样你不用反复把文件系统烧写到 SD 卡,直接从网络启动,改代码、调页面都非常方便,开发效率会高很多。
宿主机侧导出 Rootfs:
sudo apt install nfs-kernel-server # Debian/Ubuntu 系安装 NFS 服务 echo "$ROOTFS *(rw,sync,no_root_squash,no_subtree_check)" | sudo tee -a /etc/exports sudo exportfs -ra内核启动参数我通常这样设置:
root=/dev/nfs nfsroot=192.168.1.100:/home/dev/work/rootfs,vers=3,tcp rw ip=dhcp这里 192.168.1.100 是宿主机的 IP,后面是 Rootfs 的路径。vers=3 表示使用 NFSv3,tcp 是传输协议,rw 表示可读写。ip=dhcp 表示目标板通过 DHCP 获取 IP。
如果你只是想快速看目录结构能不能正常工作,还可以用更粗暴的方式:在 U-Boot 里临时给内核传 init=/bin/sh,跳过完整 init 流程,直接进入 shell。进入后手动挂载伪文件系统:
mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev然后查看 /proc/cpuinfo、/sys/class 的内容是否存在,再尝试向 /tmp 写入文件。如果这些操作全部成功,说明 Rootfs 的目录骨架在目标架构上已经基本可用,接下来要做的就是往里填配置文件和应用了。
关于挂载参数还有个细节:NFS 挂载时最好在 bootargs 里加上 rootwait。这个参数让内核在检测网络设备时多等待一段时间,避免因为网络链路还没准备好,根文件系统挂载失败。我试过不止一次,不加 rootwait 时系统随机卡在挂载 NFS 阶段,加了之后几乎百发百中。
4.3 目录不全时最容易见到的启动报错
验证过程中,我遇到过的与目录直接相关的启动报错可以总结成几类,每一类都指向目录结构里某个具体的缺失:
缺 /sbin/init:内核启动最后阶段打印 “No init found. Try passing init= option to kernel.”。这个报错最常见的原因是 /sbin/init 不存在或是指向的 busybox 无法执行。解决方法是确认 /sbin/init 符号链接是否存在,以及 /bin/sh 是否真的能跑起来。
缺 /dev/console:启动过程中登录进程或 init 脚本报 “can't open /dev/console”。这个现象很容易被误判为串口配置问题,但实际往往是 /dev/console 设备节点缺失或主次设备号不对。
缺 /proc 或 /proc 未挂载:进入 shell 后敲 ps、top、free 这类命令,输出内容为空或直接报错。因为这个信息来自 /proc 文件系统,目录存在但没挂载就等于没有数据源。
缺 /tmp 或权限不当:启动脚本执行到一半报错,说无法创建临时文件。解决方法是确保 /tmp 存在并且权限包含写入和粘滞位。
这些报错都不难修,但要修得快,前提是你已经清楚目录结构里哪个缺了。这也是我把校验放在实操里的原因:多花两分钟看 ls,能省下后面至少一小时的排查时间。
5. 高频问题与排查技巧实录
5.1 “No init found” 这类问题的完整排查路径
“No init found” 是我见过最容易被表象误导的报错。第一次看到这个提示时,我以为是内核配置错了或者 bootargs 没写对,后来才发现是 Rootfs 里根本没有 /sbin/init。
排查路径建议按这个顺序走:
第一步,查看 Rootfs 里是否存在 /sbin/init 或配置了 init= 参数指定的程序:
ls -l $ROOTFS/sbin/init如果文件不存在,说明 BusyBox 没安装进去,或者安装时 CONFIG_PREFIX 指向的路径不对。
第二步,检查 busybox 本身能否在目标架构上执行:
file $ROOTFS/bin/busybox输出应该是 “ELF 32-bit LSB executable, ARM, ...”。如果显示 x86 或 unknown,那就意味着交叉编译失败或安装错了工具链。
第三步,检查动态链接依赖。如果是动态编译的 busybox,用 readelf 看它依赖哪些共享库:
readelf -d $ROOTFS/bin/busybox | grep NEEDED输出里可能有一串 libc.so.6、ld-linux-armhf.so.3 之类的库名。这些库必须存在于 /lib,否则内核加载 busybox 时会报类似 “cannot open shared object file” 的错误。
第四步,检查 inittab。就算 init 找到了,busybox init 默认会读取 /etc/inittab,没有这个文件时它会走默认动作,可能会试图执行 /etc/init.d/rcS。如果 rcS 也不存在,又会出现另一类启动失败。
5.2 dev 目录“闹鬼”:设备节点不出现
Rootfs 启动后进入 shell,但 /dev 下只有空空的几个文件,USB 设备插上去也没反应,这是很多嵌入式新手会踩的坑。
根本原因通常是 devtmpfs 没有挂载或者 mdev 没有配置。内核有 devtmpfs 支持时,建议直接在启动脚本里挂载:
mount -t devtmpfs devtmpfs /dev如果内核配置里没开 CONFIG_DEVTMPFS,那就得靠 mdev 来扫描和创建设备节点。做法是确保 /dev 已经挂载(或为空),然后在启动脚本里运行:
echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -smdev -s 的作用是扫描 /sys 目录,根据设备信息在 /dev 下创建对应的节点。如果没有 /sys 支持,mdev 什么也扫不到,所以顺序必须是先挂载 /sys,再跑 mdev。
还有一个容易混淆的点:如果你手动用 mknod 创建了 /dev/console 或 /dev/null,然后又在启动脚本里 mount -t devtmpfs,devtmpfs 挂载时会覆盖整个 /dev 目录,把你手动创建的节点清掉。所以,要么依赖 devtmpfs 自动生成,要么依赖 mdev 扫描生成,不要混用,否则排查时你会觉得自己创建的东西“神秘消失”。
5.3 权限、属主和只读挂载的坑
权限类问题有时候比目录缺失更隐蔽。我举一个真实的例子:有一次目标板启动后,程序写日志时总是报 “Permission denied”,但 /var/log 明明存在。用 ls -ld 一看,权限是 755,属主是 root。普通用户程序在 /var/log 下没有写权限,日志自然写不进去。
解决方式是给 /var/log 设置合适的权限。考虑日志通常由 root 或专门的日志程序写,我一般设 755 然后让相关进程以 root 运行;如果你想普通用户也能写,就得改成 1777,但又可能带来安全性问题。最好按实际需求设权限,不要一刀切。
还有一类常见问题是 NFS 挂载时 root_squash 导致权限异常。NFS 服务端开启 root_squash 后,客户端 root 的请求会被映射成 nobody 用户,这样即使 Rootfs 里目录是 root 属主,远程的 root 进程也拿不到真正的写权限。嵌入式开发期间,我建议在 NFS 导出配置里加 no_root_squash:
$ROOTFS *(rw,sync,no_root_squash,no_subtree_check)这样宿主机和目标板的 root 身份基本等价,调试起来顺畅很多。
只读挂载是另一个大坑。如果 bootargs 里没有写 rw,内核会尝试以只读方式挂载根文件系统,这时 /tmp、/var 全部变成不可写,很多启动脚本中途就失败。我调试时习惯在 bootargs 里明确写 rw,避免依赖默认挂载选项。
5.4 高频问题速查表
| 现象 | 可能原因 | 快速排查与解决 |
|---|---|---|
| Kernel panic: No init found | /sbin/init 不存在或无法执行 | ls -l /sbin/init、file /bin/busybox,确认 ARCH 和工具链 |
| /bin/sh: not found | busybox 动态编译且库缺失 | 改为静态编译,或补齐 /lib 下的链接器和库 |
| 串口无输出 | /dev/console 缺失设备节点 | mknod -m 600 /dev/console c 5 1,或启用/挂载 devtmpfs |
| ps/top 报错或输出为空 | /proc 未挂载 | mount -t proc proc /proc |
| 插入 USB 无反应 | /sys 未挂载或 mdev 未运行 | 挂载 sysfs,运行 mdev -s,配置内核 hotplug |
| 日志写入失败 | /var/log 权限不当或根文件系统只读 | chmod 755 /var/log,确认 bootargs 带 rw |
| 临时文件创建失败 | /tmp 缺失或权限不足 | mkdir -p /tmp,chmod 1777 /tmp |
| 程序报“Permission denied” | 目录缺 x 权限或属主不对 | ls -ld 检查目录权限,chown root:root 或按需调整 |
| 设备节点手动创建后消失 | devtmpfs 挂载覆盖了目录 | 改为一律使用 devtmpfs,或一律使用 mdev,不要混用 |
写到这里,再分享一点我自己的体会。做过几次从零移植之后,我对“文件系统骨架”这四个字的理解已经完全变了:它不是一个名词,而是一套启动信任链。内核信任 /sbin/init 能被执行,init 信任 /etc/inittab 能被读取,shell 信任 /tmp 有可写空间,mdev 信任 /sys 能反映设备状态。每一条信任都建立在目录存在、权限正确、特殊节点齐全的基础上。
所以每次创建完 Rootfs 目录,我都会像查清单一样逐项核对权限和关键节点,宁愿多花十分钟,也不愿在板子上靠串口日志反复猜谜。这套流程走通之后,系统能顺利进入 shell 的那一刻,你会觉得之前所有关于目录的较真都是值得的。