SylixOS 中的 Rootfs
1. rootfs 到底是什么
1.1 定义
rootfs(根文件系统)是运行中的操作系统用来提供根目录
/的文件系统实例。所有绝对路径都从/开始解析;其他文件系统可以挂载到它下面的目录,最终形成进程可见的完整文件系统目录树。
这里的 “文件系统实例” 是指一个已经被操作系统创建或挂载、能够实际接受文件操作的文件系统对象,而不是 FAT、ROMFS、ext4 这类文件系统格式名称。rootfs 可以采用不同的文件系统实现;它之所以称为“根文件系统”,关键在于它提供了根目录 /。
1.2 容易混淆的概念
| 概念 | 含义 |
|---|---|
| rootfs | 运行时提供根目录 / 的文件系统实例。 |
| rootfs 镜像或目录 | 制作固件时准备的一组根目录内容,是 rootfs 的数据来源或构建产物,不等同于运行中的文件系统实例。 |
| mount point | 当前目录树中的接入位置,其他文件系统通过它进入完整目录树。 |
如何理解 rootfs 镜像? rootfs 镜像是根目录内容在构建和部署阶段的保存形式,例如 SquashFS、ext4 镜像或 cpio 归档。镜像内容需要通过挂载、解包或路径映射等方式接入运行时目录树;某些镜像也可以直接提供
/,从而成为运行中的 rootfs。镜像是部署载体,rootfs 是运行时角色,动态创建的内存 rootfs 可以没有对应镜像。
1.3 为什么系统需要 rootfs
没有根目录,绝对路径就没有解析起点,其他文件系统也没有可接入的路径位置。系统必须先建立可用的 rootfs,才能继续组织目录、注册设备、接入其他文件系统,并为应用提供统一的路径访问方式。
2. rootfs 在文件访问中的位置
2.1 绝对路径从 rootfs 开始
以 open("/etc/config", O_RDONLY) 为例,路径以 / 开头,因此系统首先从根目录开始查找 etc,再从 etc 中查找 config。rootfs 提供了这个查找过程的起点。
一次文件访问通常可以抽象为:
应用调用 open/read/stat
|
v
规范化路径
|
v
从根目录 / 开始逐级查找路径分量
|
v
判断是否进入其他文件系统或设备节点
|
v
调用最终对象的文件操作函数
需要注意:路径从 rootfs 开始,不等于最终操作一定由 rootfs 完成。 rootfs 提供路径起点;路径命中其他文件系统或设备后,后续操作会转交给对应实现。
2.2 rootfs、VFS 和具体文件系统
很多操作系统会设置 VFS 或功能相近的 I/O 抽象层,将应用使用的 open()、read()、stat() 等统一接口分派给不同对象:
| 层次 | 主要职责 |
|---|---|
| rootfs | 提供根目录 /,作为绝对路径解析的起点。 |
| VFS 或 I/O 抽象层 | 组织路径查找,识别文件系统或设备,并选择操作函数。 |
| 具体文件系统 | 管理自己的节点、元数据和内容,实现最终的 open/read/write 等操作。 |
rootfs 自身也需要一种具体实现,但“rootfs”描述的是它在目录树中的位置和职责,不是某一种固定的磁盘格式。
2.3 进入挂载点后,路径归属发生变化
假设系统先建立内存 rootfs,再把 SD 卡上的 FAT 文件系统接入 /media/sdcard0。应用调用:
open("/media/sdcard0/etc/config", O_RDONLY);
路径处理过程可以表示为:
/ 由 rootfs 提供路径起点
└── media rootfs 中的目录
└── sdcard0 SD 卡 FAT 文件系统的接入点
└── etc 由 FAT 文件系统管理
└── config 由 FAT 文件系统查找和读取
系统先从 / 查找 media;到达 /media/sdcard0 后,剩余的 etc/config 由 FAT 文件系统处理。随后对该文件的 read()、write() 等操作也由 FAT 文件系统完成,rootfs 不保存它的文件内容。
3. rootfs 与完整目录树的形成
3.1 先建立根,再接入其他文件系统
这里讨论的不是所有驱动的严格初始化顺序,而是文件系统对象进入运行时目录树时的逻辑依赖:
初始化 I/O 或 VFS 基础设施
|
v
创建或挂载 rootfs,使 / 可用
|
v
建立 /dev、/proc、/media 等接入位置
|
v
接入设备文件系统、procfs 和磁盘文件系统
|
v
形成应用可以通过绝对路径访问的完整目录树
文件系统驱动可以提前完成注册,但只有在根目录、接入路径以及相应的挂载或设备登记关系建立后,应用才能通过绝对路径访问它。
3.2 根目录树可以由多个文件系统共同组成
rootfs 首先提供 / 和最初的目录结构,其他文件系统再接入其中。进程最终看到的是一棵统一目录树:
/ rootfs 提供根和路径起点
├── etc 可能由 rootfs 自身提供
├── dev 接入设备文件系统或设备节点
├── proc 接入 procfs
├── sys 接入 sysfs
└── media
└── sdcard0 接入 SD 卡上的 FAT 文件系统
从路径关系看,/dev、/proc、/sys 和 /media/sdcard0 都位于 / 下面;但这种“包含”是目录树中的包含,不是数据和实现上的包含。procfs 生成自己的节点,设备驱动处理设备操作,FAT 文件系统读写 SD 卡数据。rootfs 提供根和接入基础,各个对象共同组成进程最终看到的目录树。
3.3 常见目录只是约定
嵌入式系统的根目录树中经常出现以下目录:
| 目录 | 常见用途 |
|---|---|
/dev | 设备访问入口 |
/proc、/sys | 内核和系统状态的虚拟接口 |
/etc | 系统配置 |
/bin、/sbin、/lib | 程序和运行库 |
/tmp、/var | 临时数据和运行时可变数据 |
/mnt、/media | 其他文件系统的接入位置 |
这些目录是否存在、由哪个文件系统提供,取决于具体系统。rootfs 的必要条件不是拥有固定的目录清单,而是提供根目录 /,使路径解析和后续接入成为可能。
4. 阅读一个操作系统的 rootfs 实现时看什么
分析一个操作系统的 rootfs 实现,可以依次回答四个问题:
- 谁创建根目录
/,它对应哪个运行时对象? - 路径通过什么数据结构逐级查找?
- 目录、设备节点和符号链接分别如何创建?
- 路径命中其他文件系统或设备后,操作如何完成分派?
可以用三层模型组织源码:
路径命名层:目录、设备名、符号链接
|
路径分派层:确定由 rootfs、文件系统还是设备驱动处理
|
对象操作层:具体实现 open/read/write/stat 等操作
下面分析 SylixOS 时也沿用这条主线:先明确 rootFs 的职责,再看节点如何进入树中,最后跟踪路径如何转交给具体实现。
5. SylixOS rootFs:内存节点树与根设备
5.1 rootFs 的职责边界
SylixOS 的 rootFs 是一棵驻留在内存中的节点树,同时也是 I/O 层进行路径匹配和设备分派的基础。它主要保存:
- 根目录下的目录结构;
- 设备在目录树中的入口;
- 符号链接及其目标路径;
- socket、IPC key 等少量本地节点的元数据。
它不是一个用于保存普通文件内容的完整磁盘文件系统。
SylixOS 使用两个对象共同表示根:
| 对象 | 作用 |
|---|---|
_G_rfsrRoot | rootFs 节点树的根容器,保存第一层子节点、内存用量和节点数量。 |
_G_devhdrRoot | 注册到 I/O 系统中的根设备头,对应路径 / 和 rootFs 操作表。 |
因此,/ 本身不是一个普通的 LW_ROOTFS_NODE。第一层目录和设备节点直接挂在 _G_rfsrRoot.RFSR_plineSon 下;I/O 层则通过 _G_devhdrRoot 调用 rootFs 的文件操作。
5.2 核心数据结构
fs/rootFs/rootFsLib.h:30-68 定义了 rootfs 的主要结构:
/*********************************************************************************************************
rootfs 节点内容
*********************************************************************************************************/
typedef union lw_rootfs_node_value {
PCHAR RFSNV_pcName; /* 节点名字 */
PLW_DEV_HDR RFSNV_pdevhdr; /* 设备指针 */
} LW_ROOTFS_NODE_VALUE;
typedef LW_ROOTFS_NODE_VALUE *PLW_ROOTFS_NODE_VALUE;
/*********************************************************************************************************
rootfs 节点
*********************************************************************************************************/
typedef struct lw_rootfs_node {
LW_LIST_LINE RFSN_lineBrother; /* 兄弟节点 */
struct lw_rootfs_node *RFSN_prfsnFather; /* 父系节点 */
PLW_LIST_LINE RFSN_plineSon; /* 儿子节点 */
INT RFSN_iOpenNum; /* 打开次数 */
size_t RFSN_stAllocSize; /* 此节点占用内存大小 */
mode_t RFSN_mode; /* 模式 */
time_t RFSN_time; /* 创建时间 */
INT RFSN_iNodeType; /* 节点类型 */
uid_t RFSN_uid;
gid_t RFSN_gid;
LW_ROOTFS_NODE_VALUE RFSN_rfsnv; /* 节点的内容 */
PCHAR RFSN_pcLink; /* 链接目标 (不是链接文件为 0) */
} LW_ROOTFS_NODE;
typedef LW_ROOTFS_NODE *PLW_ROOTFS_NODE;
/*********************************************************************************************************
rootfs 根
*********************************************************************************************************/
typedef struct lw_rootfs_root {
PLW_LIST_LINE RFSR_plineSon; /* 指向第一个儿子 */
size_t RFSR_stMemUsed; /* 内存消耗量 */
ULONG RFSR_ulFiles; /* 文件总数 */
time_t RFSR_time; /* 创建时间 */
} LW_ROOTFS_ROOT;
typedef LW_ROOTFS_ROOT *PLW_ROOTFS_ROOT;
RFSN_prfsnFather、RFSN_plineSon 和 RFSN_lineBrother 共同组成父子树和兄弟链表。目录、链接等节点直接保存自身名称;DEV 节点保存 PLW_DEV_HDR,设备名称则可以从设备头取得。

5.3 节点类型
fs/rootFs/rootFs.h:46-50 给出了节点类型:
| 常量 | 作用 |
|---|---|
| LW_ROOTFS_NODE_TYPE_DIR | rootFs 本地目录。 |
| LW_ROOTFS_NODE_TYPE_DEV | 设备或文件系统入口,值为 PLW_DEV_HDR。 |
| LW_ROOTFS_NODE_TYPE_LNK | 符号链接,RFSN_pcLink 保存目标路径。 |
| LW_ROOTFS_NODE_TYPE_SOCK | AF_UNIX socket 文件。 |
| LW_ROOTFS_NODE_TYPE_REG | 普通节点,主要用于 IPC key 等场景,不提供普通文件内容读写。 |
理解 SylixOS rootFs 的关键不是把它分成多套根文件系统,而是看同一棵节点树中的不同节点最终由谁处理:
DIR / REG / SOCK -> rootFs 自身处理节点操作
DEV -> 转交节点保存的设备或文件系统
LNK -> 改写路径后重新匹配
6. rootFs 节点树如何建立
6.1 _IosInit() 建立最初的根目录环境
system/ioLib/ioLib.c:523-545 在 I/O 子系统初始化阶段建立最初的 rootFs 环境:
INT _IosInit (VOID)
{
......
rootFsDrv(); /* 安装 rootFs 驱动 */
rootFsDevCreate(); /* 创建根设备 */
API_RootFsMakeNode("/dev", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* 设备目录 */
API_RootFsMakeNode("/dev/pty", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* 虚拟终端设备 */
API_RootFsMakeNode("/dev/pipe", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* 管道设备 */
API_RootFsMakeNode("/dev/input", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* 输入设备 */
API_RootFsMakeNode("/dev/blk", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* blk raw 设备 */
API_RootFsMakeNode("/mnt", LW_ROOTFS_NODE_TYPE_DIR, LW_ROOTFS_NODE_OPT_ROOTFS_TIME,
DEFAULT_DIR_PERM, LW_NULL); /* 文件系统挂载目录 */
API_RootFsMakeNode("/media", LW_ROOTFS_NODE_TYPE_DIR,
LW_ROOTFS_NODE_OPT_ROOTFS_TIME | LW_ROOTFS_NODE_OPT_ROOTFS_EXTDIR,
DEFAULT_DIR_PERM, LW_NULL); /* 热插拔存储器挂载目录 */
/* unix 兼容 null 设备路径 */
(VOID)API_IosDevAddEx(&_G_devhdrNull.NZ_devHdr, "/dev/null", iNullDrv, DT_CHR);
(VOID)API_IosDevAddEx(&_G_devhdrZero.NZ_devHdr, "/dev/zero", iZeroDrv, DT_CHR);
......
}
这几步的职责不同:
| 调用 | 作用 |
|---|---|
rootFsDrv() | 安装 rootFs 的 file_operations,取得驱动号。 |
rootFsDevCreate() | 将 _G_devhdrRoot 以名称 / 注册为根设备,并初始化 _G_rfsrRoot。 |
API_RootFsMakeNode() | 创建 /dev、/mnt、/media 等本地目录节点。 |
API_IosDevAddEx() | 登记 /dev/null、/dev/zero 的设备头,并创建相应 DEV 节点。 |
所以 SylixOS 的根、基础目录和第一批设备入口在 _IosInit() 中就已经建立。
6.2 API_RootFsMakeNode() 的核心流程
API_RootFsMakeNode() 位于 fs/rootFs/rootFsLib.c:352 起,是构造 rootFs 节点树的统一底层入口。其主要流程为:
- 检查绝对路径、节点类型以及
pvValue是否有效。 - 根据类型分配节点内存,并填写名称、设备头或链接目标。
- 在锁保护下调用
__rootFsFindNode(),检查重名、父节点和中间目录。 - 将新节点加入父目录的子链表;第一层节点直接加入
_G_rfsrRoot.RFSR_plineSon。 - 更新内存用量和节点计数。
节点插入逻辑可以概括为:
if (prfsnFather) {
prfsnNew->RFSN_prfsnFather = prfsnFather;
_List_Line_Add_Ahead(&prfsnNew->RFSN_lineBrother,
&prfsnFather->RFSN_plineSon);
} else {
prfsnNew->RFSN_prfsnFather = LW_NULL;
_List_Line_Add_Ahead(&prfsnNew->RFSN_lineBrother,
&_G_rfsrRoot.RFSR_plineSon);
}
父节点必须是 DIR,且中间目录必须已经存在。例如创建 /dev/ttyS0 前,/dev 必须先建立。函数不会自动补齐缺失的中间目录。
6.3 设备创建同时完成两种登记
设备创建函数通常调用 API_IosDevAddEx()。该接口不仅登记设备对象,还让设备名称进入 rootFs:
设备创建函数,例如 ttyDevCreate("/dev/ttyS0", ...)
|
v
API_IosDevAddEx(pdevhdr, "/dev/ttyS0", iDrvNum, DT_CHR)
|
+-- 将设备头加入 I/O 设备表
|
v
rootFsMakeDev("/dev/ttyS0", pdevhdr)
|
v
API_RootFsMakeNode(..., LW_ROOTFS_NODE_TYPE_DEV, ..., pdevhdr)
|
v
rootFs 节点树中出现 /dev/ttyS0
rootFsMakeDev() 是 fs/rootFs/rootFs.h 中的宏:
#define rootFsMakeDev(pcPath, pdevhdr) \
API_RootFsMakeNode(pcPath,
LW_ROOTFS_NODE_TYPE_DEV,
LW_ROOTFS_NODE_OPT_NONE,
0,
(PVOID)(pdevhdr))
因此,创建设备实际建立了两种关系:
| 登记位置 | 保存的信息 | 用途 |
|---|---|---|
| I/O 设备表 | 设备头、驱动号、设备号等 | 确定设备对象及其操作表。 |
| rootFs 节点树 | 设备路径到设备头的关联 | 使设备能够通过路径被匹配。 |
如果 rootFs 节点创建失败,API_IosDevAddEx() 会从 I/O 设备表中移除刚加入的设备头,避免留下“设备已经登记,但路径不可访问”的半完成状态。相关代码位于 system/ioLib/ioSys.c:544-637。
可以用一句话区分驱动安装与设备创建:
DrvInstall = 注册“如何操作”
DevCreate = 创建“哪个对象”,并把它接入 rootFs 路径树
6.4 链接和其他本地节点
符号链接的创建同样遵循 I/O 层的路径分派规则。symlink(pcLinkDst, pcSymPath) 根据第二个参数 pcSymPath(新链接的路径)确定负责创建链接的设备,再调用该设备驱动的符号链接操作;第一个参数 pcLinkDst 仅表示链接目标。相关代码位于 system/ioLib/ioSymlink.c:45-130:
symlink(pcLinkDst, pcSymPath)
|
v
ioFullFileNameGet(pcSymPath, &pdevhdr, ...)
|
+-- 检查新链接路径是否已存在
+-- 解析新链接路径中已有的中间链接
|
v
iosSymlink(pdevhdr, pcName, pcLinkDst)
|
v
API_IosSymlink() 调用该设备驱动的 fo_symlink
|
+-- rootFs 设备 -> __rootFsSymlink()
+-- ramfs 设备 -> __ramFsSymlink()
+-- 其他设备 -> 对应 fo_symlink,未实现则报错
有当 pcSymPath 由 _G_devhdrRoot 管理时,I/O 层才会调用 rootFs 操作表中的 __rootFsSymlink()。该函数随后创建 LNK 节点:
static INT __rootFsSymlink (PLW_DEV_HDR pdevhdr,
PCHAR pcName,
CPCHAR pcLinkDst)
{
......
API_RootFsMakeNode(pcName,
LW_ROOTFS_NODE_TYPE_LNK,
LW_ROOTFS_NODE_OPT_NONE,
DEFAULT_SYMLINK_PERM,
(PVOID)pcLinkDst);
......
}
例如,若 /mnt/ram 是 ramfs 卷,则 symlink("target", "/mnt/ram/link") 会进入 __ramFsSymlink();而 symlink("/media/sdcard1/apps", "/apps") 的新链接路径 /apps 属于 rootFs,因此会进入 __rootFsSymlink()。链接目标可以位于另一个文件系统中,但它不决定本次 symlink() 由哪个驱动执行。
__rootFsOpen() 在处理带 O_CREAT 的请求时,还可以通过 rootFsMakeDir()、rootFsMakeSock() 和 rootFsMakeReg() 创建目录、socket 或普通元数据节点。其中,rootFs 的 REG 节点不提供普通文件内容读写;持久文件应由 FAT、TPSFS、YAFFS2 等具体文件系统管理。
7. 其他文件系统如何接入 rootFs
7.1 procfs:动态信息树
API_ProcFsDevCreate() 在 fs/procFs/procFs.c:172-190 中调用:
INT API_ProcFsDevCreate (VOID)
{
......
iosDevAddEx(&_G_devhdrProc, "/proc", _G_iProcDrvNum, DT_DIR);
......
}
该调用在 rootFs 中创建 /proc 的 DEV 节点,节点保存 _G_devhdrProc。之后,各模块通过 API_ProcFsMakeNode() 在 procfs 自己的节点树中创建 kernel/version 等节点。
读取 proc 文件时,__procFsRead() 不从磁盘获取内容,而是调用节点绑定的读取回调动态生成数据。因此:
rootFs 负责 /proc 入口和设备分派
procfs 负责 /proc 内部节点和内容生成
7.2 ramfs:真正保存文件内容的内存文件系统
API_RamFsDevCreate() 创建 ramfs 卷、初始化锁和容量信息,随后调用:
INT API_RamFsDevCreate (PCHAR pcName, PLW_BLK_DEV pblkd)
{
......
iosDevAddEx(&pramfs->RAMFS_devhdrHdr,
pcName,
_G_iRamfsDrvNum,
DT_DIR);
......
}
假设 pcName 为 /mnt/ram,rootFs 中会出现一个指向 ramfs 卷设备头的 DEV 节点。/mnt/ram 以下的目录、普通文件和文件内容均由 ramfs 自己管理,而不是由 rootFs 保存。
7.3 存储设备上的文件系统
对于 eMMC、SD 或 SATA,应该区分三个阶段:
存储驱动发现介质和分区
|
v
mount 根据文件系统类型取得卷创建函数
|
v
FAT/TPSFS 等文件系统创建运行时卷
|
v
卷通过 API_IosDevAddEx() 以 DEV 节点接入 rootFs
fs/mount/mount.c 中的 __mount() 会规范化目标路径,根据文件系统名取得创建函数,并为多数磁盘文件系统准备 block-raw 设备,最后调用:
pfuncFsCreate(pcVolName, &pmnDev->MN_blkd);
如果文件系统类型是 vfat,这里最终进入 API_FatFsDevCreate()。该函数完成 FAT 卷初始化后调用:
INT API_FatFsDevCreate (PCHAR pcName, PLW_BLK_DEV pblkd)
{
......
iosDevAddEx(&pfatvol->FATVOL_devhdrHdr,
pcName,
_G_iFatDrvNum,
DT_DIR);
......
}
于是目标路径(例如 /media/sdcard0)成为 rootFs 中的 DEV 节点,节点以下的路径交给 FAT 文件系统处理。
分区由存储和块设备层提供;rootFs 只记录“/media/sdcard0 对应哪个文件系统卷设备”。这也不同于典型 Linux VFS 中“把文件系统覆盖到一个已经存在的目录项”这一具体实现:SylixOS 通过在目标路径注册文件系统设备来完成接入。
8. 路径如何分派到最终设备
8.1 __rootFsFindNode() 逐级查找
__rootFsFindNode() 位于 fs/rootFs/rootFsLib.c:65-211。它从 _G_rfsrRoot 的第一层子节点开始,将绝对路径按 / 分成多个分量,在当前层的兄弟链表中查找,再进入命中节点的子链表。
函数还会返回:
- 已匹配节点;
- 最接近的父节点;
- 尚未处理的路径尾部;
- 是否正在处理根目录或最后一个路径分量。
这些信息既用于创建节点,也用于判断一条路径应该交给哪个设备。
8.2 __rootFsDevMatch() 决定由谁处理
__rootFsDevMatch() 位于 rootFsLib.c:221-275,其核心规则是:
- 路径为
/时,返回根设备_G_devhdrRoot。 - 精确命中
DEV节点时,返回节点保存的设备头。 - 没有精确命中,但最接近的父节点是
DEV时,也返回该父设备,让它处理剩余路径。 - 命中 rootFs 本地节点,或只找到本地目录父节点时,返回
_G_devhdrRoot。
应用调用 open() 后,整体分派过程为:
open(path)
|
v
_IoOpen()
|
+-- ioFullFileNameGet()
| |
| +-- __rootFsDevMatch(path)
| |
| +-- _G_devhdrRoot
| | -> __rootFsOpen()
| |
| +-- RFSNV_pdevhdr
| -> 对应设备或文件系统的 open()
|
+-- iosOpen()
几个典型结果如下:
| 路径 | 匹配结果 | 最终 open 回调 |
|---|---|---|
/ | _G_devhdrRoot | __rootFsOpen() |
/dev | rootFs 本地 DIR | __rootFsOpen() |
/dev/null | null 设备的 DEV 节点 | null 驱动的 open() |
/proc/kernel/version | /proc 的 DEV 节点 | __procFsOpen() |
/media/sdcard0/a.txt | FAT 卷的 DEV 节点 | FAT 文件系统的 open() |
即使最终没有调用 __rootFsOpen(),rootFs 仍参与路径匹配和设备选择。完整过程是 rootFs 先找到设备头,再由 I/O 层调用该设备的操作表。
8.3 符号链接会触发重新匹配
假设 rootFs 中存在:
/etc -> /media/sdcard0/etc
应用调用 open("/etc/config", O_RDONLY) 时:
第一次匹配 /etc/config
-> /etc 是 LNK 节点
-> __rootFsDevMatch() 返回 _G_devhdrRoot
-> __rootFsOpen() 将路径改写为 /media/sdcard0/etc/config
-> 返回 FOLLOW_LINK_TAIL
_IoOpen() 使用新路径再次匹配
-> 命中 /media/sdcard0 的 DEV 节点
-> 调用 FAT 文件系统的 open()
对应源码位于 fs/rootFs/rootFs.c:344-376 和 system/ioLib/ioInterface.c:178-214。系统不是重新调用一次应用层公开的 open(),而是在同一次 _IoOpen() 中改写路径并循环匹配。
9. rootFsMap:把标准目录映射到文件系统卷
9.1 配置映射关系
fs/rootFs/rootFsMap.c:41-55 的默认配置以 /media/hdd1 作为多数标准目录的目标前缀:
typedef struct {
PCHAR RFSMN_pcDir;
CHAR RFSMN_cMapDir[LW_ROOTFS_MAP_LEN];
} LW_ROOTFS_MAP_NODE;
static LW_ROOTFS_MAP_NODE _G_rfsmapRoot = { "/" , "/media/hdd1" };
static LW_ROOTFS_MAP_NODE _G_rfsmapSubp[] = {
{ "/var" , "" },
{ "/usr" , "" },
{ "/tmp" , "" },
{ "/sbin", "" },
{ "/root", "" },
{ "/qt" , "" },
{ "/lib" , "" },
{ "/home", "" },
{ "/etc" , "" },
{ "/boot", "/media/hdd0" },
{ "/bin" , "" },
{ "/apps", "" }
};
API_RootFsMapInit() 只解析并保存配置,不创建目录或链接。函数入参 pcMap 为映射关系字符串。例如:
pcMap = rfsmap=/boot:/media/sdcard0,/:/media/sdcard1
INT API_RootFsMapInit (CPCHAR pcMap)
{
......
pcDir = cMap;
pcNext = cMap;
pcMdir = LW_NULL;
while (*pcNext) {
if (*pcNext == ',') {
*pcNext = PX_EOS;
pcNext++;
__mapsave:
if (pcMdir) {
if (lib_strcmp(pcDir, _G_rfsmapRoot.RFSMN_pcDir) == 0) {
lib_strlcpy(_G_rfsmapRoot.RFSMN_cMapDir, pcMdir, LW_ROOTFS_MAP_LEN);
} else {
for (i = 0; i < LW_ROOTFS_ARRAY_SIZE(_G_rfsmapSubp); i++) {
if (lib_strcmp(pcDir, _G_rfsmapSubp[i].RFSMN_pcDir) == 0) {
lib_strlcpy(_G_rfsmapSubp[i].RFSMN_cMapDir, pcMdir, LW_ROOTFS_MAP_LEN);
}
}
}
pcMdir = LW_NULL;
pcDir = pcNext;
}
} else if (*pcNext == ':') {
*pcNext = PX_EOS;
pcNext++;
pcMdir = pcNext;
} else {
pcNext++;
if (*pcNext == PX_EOS) {
goto __mapsave;
}
}
}
......
}
解析后,/boot 使用 /media/sdcard0,/ 使用 /media/sdcard1’'。
static LW_ROOTFS_MAP_NODE _G_rfsmapRoot = { "/" , "/media/sdcard1" };
static LW_ROOTFS_MAP_NODE _G_rfsmapSubp[] = {
{ "/var" , "" },
{ "/usr" , "" },
{ "/tmp" , "" },
{ "/sbin", "" },
{ "/root", "" },
{ "/qt" , "" },
{ "/lib" , "" },
{ "/home", "" },
{ "/etc" , "" },
{ "/boot", "/media/sdcard0" },
{ "/bin" , "" },
{ "/apps", "" }
};
9.2 API_RootFsMap() 创建实际链接
API_RootFsMap() 才会检查目标目录并调用 symlink():
INT API_RootFsMap (ULONG ulFlags)
{
......
if (lib_strcmp(_G_rfsmapRoot.RFSMN_cMapDir, "/dev/ram") == 0) {
lib_bzero(&blkdevRam, sizeof(LW_BLK_DEV));
blkdevRam.BLKD_pcName = "0";
ramFsDevCreate("/dev/ram", &blkdevRam);
}
for (i = 0; i < LW_ROOTFS_ARRAY_SIZE(_G_rfsmapSubp); i++) {
if (_G_rfsmapSubp[i].RFSMN_cMapDir[0]) {
if (access(_G_rfsmapSubp[i].RFSMN_cMapDir, R_OK) < 0) {
mkdir(_G_rfsmapSubp[i].RFSMN_cMapDir, DEFAULT_DIR_PERM);
}
symlink(_G_rfsmapSubp[i].RFSMN_cMapDir, _G_rfsmapSubp[i].RFSMN_pcDir);
} else {
/* 这里注意,对于 _G_rfsmapSubp 中暂未映射的,默认都映射到 _G_rfsmapRoot.RFSMN_cMapDir 目录下 */
snprintf(cMap, MAX_FILENAME_LENGTH, "%s%s",
_G_rfsmapRoot.RFSMN_cMapDir, _G_rfsmapSubp[i].RFSMN_pcDir);
if (access(cMap, R_OK) < 0) {
mkdir(cMap, DEFAULT_DIR_PERM);
}
/* 这里会去创建名为 _G_rfsmapSubp[i].RFSMN_pcDir 的 rootfs node 节点 */
symlink(cMap, _G_rfsmapSubp[i].RFSMN_pcDir);
}
}
......
}
symlink 的第二个参数是 /apps、/etc 等 rootFs 路径,因此 I/O 层匹配到 _G_devhdrRoot,最终由 __rootFsSymlink() 创建 rootFs LNK 节点;第一个参数 cMap 只是链接目标,不参与本次驱动选择。
按照上面的配置,结果类似:
/boot -> /media/sdcard0
/apps -> /media/sdcard1/apps
/etc -> /media/sdcard1/etc
/lib -> /media/sdcard1/lib
因此,rootFsMap 不是把整个 rootFs 换成磁盘文件系统,也不是执行 Linux 意义上的根挂载切换。SylixOS 的根设备和内存节点树仍然存在;/apps、/etc 等 LNK 节点只是把访问引导到已经接入的持久化文件系统卷。
如果根映射目标配置为 /dev/ram,API_RootFsMap() 会先创建 ramfs 卷,再建立标准目录链接。这再次说明:映射目标既可以是持久存储文件系统,也可以是运行时内存文件系统。
10. 两个完整的访问案例
10.1 打开 /proc/kernel/version
open("/proc/kernel/version", O_RDONLY)
|
+-- _IoOpen() 请求匹配设备
|
+-- rootFs 节点树命中 /proc 的 DEV 节点
|
+-- 取得 _G_devhdrProc
|
+-- 调用 __procFsOpen()
|
+-- procfs 在自己的节点树中查找 kernel/version
|
+-- read() 进入 __procFsRead()
|
+-- 调用节点绑定的 PFSNO_pfuncRead 回调
+-- 动态生成文本并返回
这个过程没有读取磁盘上的普通文件。rootFs 负责找到 /proc 入口并完成设备分派,procfs 负责内部节点和内容生成。
10.2 打开映射后的 /apps/a.txt
假设:
/media/sdcard1 已接入 FAT 或 TPSFS 文件系统卷
/apps -> /media/sdcard1/apps
应用只需要调用一次:
open("/apps/a.txt", O_RDONLY);
内部处理过程为:
第一次路径匹配
/apps/a.txt
|
+-- rootFs 找到 /apps 的 LNK 节点
+-- __rootFsOpen() 改写为 /media/sdcard1/apps/a.txt
+-- 返回 FOLLOW_LINK_TAIL
第二次路径匹配
/media/sdcard1/apps/a.txt
|
+-- rootFs 找到 /media/sdcard1 的 DEV 节点
+-- I/O 层取得 FAT/TPSFS 卷设备头
+-- 文件系统自己的 open() 查找 apps/a.txt
后续 read()
|
+-- 由 FAT/TPSFS 文件系统读取实际存储数据
这个案例把三种职责连接起来:
| 层次 | 职责 |
|---|---|
rootFs 的 LNK 节点 | 将稳定的逻辑路径 /apps 转换为实际存储路径。 |
rootFs 的 DEV 节点 | 把 /media/sdcard1 关联到文件系统卷设备头。 |
| FAT/TPSFS 文件系统 | 查找 apps/a.txt 并读写实际数据。 |
应用不需要知道 /apps 最终位于 SD、eMMC 还是 ramfs,只要系统启动时建立正确的卷接入和映射关系即可。
11. 总结
理解 SylixOS rootFs,可以抓住以下几点:
- rootfs 是运行时提供根目录
/的角色;SylixOSrootFs是这一角色的具体实现。 - SylixOS rootFs 是一棵内存节点树,主要保存目录、设备入口、链接和少量元数据,不负责普通文件内容读写。
API_RootFsMakeNode()是节点创建的底层入口;API_IosDevAddEx()同时登记设备头并创建 rootFsDEV节点。- procfs、ramfs 和磁盘文件系统各自管理内部节点与数据,只通过
DEV节点接入以/为起点的统一目录树。 __rootFsDevMatch()根据路径选择根设备或具体设备;LNK节点由__rootFsOpen()改写路径后触发重新匹配。rootFsMap批量创建符号链接,将/apps、/etc等逻辑目录映射到实际文件系统卷,但不会替换 rootFs 本身。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)