2. Rootfs基础概念:Rootfs的定义、组成结构与内核的关系
好,咱们正式开始聊Rootfs。说实话,很多刚入行的驱动工程师,对Rootfs的理解就是「一个文件系统,里面放了点东西」。嗯,这么说也没错,但太粗糙了。你想想看,内核启动之后,第一个挂载的文件系统就是Rootfs。没有它,内核就是个光杆司令,啥也干不了。
我当年第一次做嵌入式项目时,就踩过这个坑。当时内核启动后卡在VFS: Cannot open root device,折腾了两天才发现是Rootfs里缺了/dev/console这个设备节点。从那以后,我对Rootfs的敬畏心就上来了。
2.1 Rootfs到底是什么?
Rootfs,全称Root Filesystem,根文件系统。它不是一个具体的文件系统类型(比如ext4、squashfs),而是一个逻辑概念。说白了,它就是内核启动后挂载的第一个文件系统,是用户空间所有进程的起点。
内核启动到最后阶段,会调用init进程——这个init程序就在Rootfs里。如果Rootfs里没有/sbin/init或者/bin/sh,内核就会panic,直接死给你看。
2.2 Rootfs的组成结构
一个标准的Rootfs,目录结构是有讲究的。我习惯把它分成三类:必备目录、功能目录和动态目录。
2.2.1 必备目录(少了就启动不了)
| 目录 | 作用 | 我的经验 |
|---|---|---|
/bin |
存放用户空间的基本命令,如sh、ls、mount |
至少要有sh和mount,否则调试都进不去 |
/sbin |
系统管理命令,如init、fdisk |
/sbin/init是内核默认的init程序路径 |
/etc |
配置文件,如inittab、fstab |
我习惯把/etc/inittab作为第一个调试目标 |
/dev |
设备节点,如console、null |
缺/dev/console,内核启动后直接黑屏 |
/lib |
共享库和内核模块 | 动态链接的程序依赖这里,缺库就报No such file |
/proc |
进程信息虚拟文件系统 | 启动后必须挂载,否则ps用不了 |
/sys |
内核对象虚拟文件系统 | 驱动开发必备,查看设备树、驱动状态全靠它 |
/lib删了,结果所有动态链接的程序都跑不起来。最后老老实实加回去,还多花了半天时间排查。省空间可以,但别省核心库。
2.2.2 功能目录(按需添加)
/usr:用户程序和数据,嵌入式里常和/合并/var:可变数据,如日志、锁文件/tmp:临时文件,通常挂载为tmpfs/home:用户目录,嵌入式设备一般不用/mnt和/media:挂载点,用于挂载U盘、SD卡等
嗯,这里要注意:嵌入式设备资源有限,不是所有目录都要有。我见过的最小Rootfs只有/bin、/sbin、/etc、/dev、/lib、/proc、/sys这7个目录,照样跑得稳稳的。
2.3 Rootfs与内核的关系
内核和Rootfs的关系,我打个比方:内核是汽车的发动机和底盘,Rootfs就是驾驶舱和方向盘。发动机再牛,没有方向盘你也开不走。
具体来说,有这几个关键点:
2.3.1 内核如何找到Rootfs?
内核启动时,通过root=参数指定Rootfs的位置。比如:
root=/dev/mmcblk0p2 // 从SD卡第二个分区挂载
root=/dev/nfs // 从网络挂载(NFS)
root=31:02 // 用主次设备号指定
我调试驱动时最喜欢用NFS挂载Rootfs。为什么呢?因为修改Rootfs内容后不用重新烧录,直接重启就行。开发效率提升一大截。
2.3.2 内核模块与Rootfs
驱动编译成模块(.ko文件)时,这些模块通常放在/lib/modules/$(uname -r)/目录下。内核启动后,由init进程或modprobe命令加载。
这里有个坑:内核版本必须和模块版本一致。我遇到过好几次,升级内核后忘了更新Rootfs里的模块,结果驱动加载失败,报Invalid module format。嗯,血的教训。
2.3.3 设备节点与驱动
驱动注册成功后,会在/dev下创建设备节点。但注意:静态设备节点需要提前在Rootfs里创建好。比如:
mknod /dev/console c 5 1
mknod /dev/null c 1 3
mknod /dev/ttyS0 c 4 64
现在很多系统用devtmpfs或udev自动管理设备节点,但嵌入式设备里,我仍然习惯手动创建几个关键节点,防止自动管理出问题。
2.4 一张图看懂Rootfs
下面这张SVG图,是我自己总结的Rootfs与内核的关系。你看完应该就明白了。
2.5 驱动开发者的视角
作为驱动开发者,我关注Rootfs的这几个点:
- 设备节点:驱动注册后,
/dev下有没有对应的节点?没有的话,应用程序怎么访问? - 模块存放:驱动模块放在
/lib/modules/下,版本号必须匹配内核版本。 - 启动脚本:
/etc/init.d/或/etc/rc.d/里的脚本,负责加载驱动模块。 - 调试工具:
/bin里有没有insmod、lsmod、dmesg?没有的话,调试驱动会很痛苦。
/etc/init.d/S00debug脚本,启动时自动加载常用调试工具。比如:mount -t debugfs none /sys/kernel/debug这样驱动调试时可以直接看debugfs的输出。
2.6 常见误区
最后,说几个我见过的常见误区:
- 误区一:Rootfs越大越好。错!嵌入式设备存储空间有限,Rootfs应该精简到够用就行。
- 误区二:所有驱动都编译进内核,不需要Rootfs。错!即使驱动编译进内核,Rootfs仍然需要提供
init进程和必要的设备节点。 - 误区三:Rootfs可以随便删目录。错!删了
/lib或/dev,系统可能直接崩溃。
嗯,关于Rootfs的基础概念,今天就聊这么多。记住一句话:Rootfs是用户空间的基石,驱动是内核的延伸。两者配合好了,系统才能跑得稳。
可可精品资料库