Nuttx - VFS

从伪文件系统 inode 树到设备驱动注册、从 open() 路径查找到 mount() 文件系统接入,完整解析 NuttX 虚拟文件系统的统一 I/O 抽象机制。

本文回答以下问题:NuttX 如何用同一套 open/read/write/close 接口统一访问设备驱动和真实文件系统?伪文件系统的 inode 树是如何组织和查找的?一个 open("/dev/console", O_RDWR) 调用经历了哪些步骤?mount 如何将真实文件系统”嫁接”到伪文件系统上?读完后,你将能够从源码级别追踪任何文件操作的完整路径,并理解如何为 NuttX 编写自己的设备驱动或文件系统。


1. 开篇:VFS 解决什么问题?

嵌入式系统中有多种 I/O 资源:串口、SPI 设备、LED、Flash 文件系统、网络 socket、/proc 状态信息。如果每种资源用不同的 API 访问,应用代码将充斥条件分支和平台耦合。

VFS(Virtual File System)的核心思想是:一切皆文件——所有 I/O 资源都通过统一的 open()/read()/write()/close() 接口访问,用路径名(如 /dev/ttyS0/mnt/sd/config.txt)区分不同资源。

NuttX 的 VFS 有一个独特设计:根文件系统永远是伪文件系统(pseudo filesystem)。与 Linux 必须有物理块设备作为根不同,NuttX 的根是一棵内存中的 inode 树,不需要任何存储介质。设备驱动和真实文件系统都挂载到这棵树上。

NuttX 官方文档(Documentation/implementation/nuttx_tasking.rst:269-307)对此有描述:

“The NuttX root file system is always a pseudo-file system… Then you can mount real filesystem in the pseudo-filesystem.”

这种设计使得 NuttX 在没有任何存储设备的系统上也能正常启动和运行——所有设备驱动都注册为 /dev/xxx 节点,无需真实文件系统支持。

接下来看这棵伪文件系统树的具体结构。


2. 伪文件系统与 inode 树

路径具有天然的层级结构(/dev/ttyS0 = 根 → devttyS0)。用树结构表示可以:

  • 自然支持路径逐段查找
  • 中间节点自动充当目录(如 /dev 是一个伪目录)
  • 支持在任意节点挂载真实文件系统(mount point)

2.1 inode 树的三指针结构

文件:include/nuttx/fs/fs.h:401-421

1
2
3
4
5
6
7
8
9
10
11
struct inode
{
FAR struct inode *i_parent; /* 指向上一层(父目录)*/
FAR struct inode *i_peer; /* 指向同层下一个兄弟(按名字排序)*/
FAR struct inode *i_child; /* 指向下一层第一个子节点 */
atomic_t i_crefs; /* 引用计数 */
uint16_t i_flags; /* 节点类型标志 */
union inode_ops_u u; /* 操作函数表(driver/fs/block)*/
FAR void *i_private; /* 驱动私有数据 */
char i_name[1]; /* 节点名称(变长,分配时确定)*/
};

每个 inode 通过 i_parent/i_peer/i_child 三个指针形成一棵排序多叉树:同层兄弟按名字字母序通过 i_peer 串联,子节点通过 i_child 连接。

实例:启动后典型的 inode 树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
g_root_inode
|
v
"dev" (PSEUDODIR)
|
+--i_child--> "console" (DRIVER, i_ops=&g_serialops)
| |
| +--i_peer--> "null" (DRIVER, i_ops=&g_nullops)
| |
| +--i_peer--> "ttyS0" (DRIVER)
|
+--i_peer--> "proc" (MOUNTPT, i_mops=&procfs_operations)
|
+--i_peer--> "tmp" (MOUNTPT, i_mops=&tmpfs_operations)

下图展示了典型的 inode 树结构,绿色箭头表示 i_child(进入子层),蓝色箭头表示 i_peer(同层兄弟):

inode

全局根节点:

文件:fs/inode/fs_inodesearch.c:57

1
FAR struct inode *g_root_inode = NULL;

g_root_inodefs_initialize() 时被初始化为 / 节点。树的访问受读写信号量 g_inode_lock 保护(读操作并发,写操作互斥)。

2.2 inode 类型

文件:include/nuttx/fs/fs.h:118-130

1
2
3
4
5
6
7
8
9
10
11
#define FSNODEFLAG_TYPE_PSEUDODIR   0x00   /* 伪目录(默认)*/
#define FSNODEFLAG_TYPE_DRIVER 0x01 /* 字符设备驱动 */
#define FSNODEFLAG_TYPE_BLOCK 0x02 /* 块设备驱动 */
#define FSNODEFLAG_TYPE_MOUNTPT 0x03 /* 挂载点 */
#define FSNODEFLAG_TYPE_NAMEDSEM 0x04 /* 命名信号量 */
#define FSNODEFLAG_TYPE_MQUEUE 0x05 /* 消息队列 */
#define FSNODEFLAG_TYPE_SHM 0x06 /* 共享内存 */
#define FSNODEFLAG_TYPE_MTD 0x07 /* MTD 设备 */
#define FSNODEFLAG_TYPE_SOFTLINK 0x08 /* 软链接 */
#define FSNODEFLAG_TYPE_SOCKET 0x09 /* Socket */
#define FSNODEFLAG_TYPE_PIPE 0x0a /* 管道 */

类型决定了 VFS 在 open()/read()/write() 时走哪条分发路径。

文件:fs/inode/fs_inodesearch.c:217-392_inode_search() 内部函数)

路径查找是 VFS 最频繁的操作。算法核心是逐段匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int _inode_search(FAR struct inode_search_s *desc)
{
FAR const char *name = desc->path; /* 例如 "/dev/console" */
FAR struct inode *inode = g_root_inode;

while (inode != NULL)
{
int result = _inode_compare(name, inode); /* 比较当前路径段与节点名 */

if (result < 0) /* 名字小于当前节点——不存在 */
{ break; }
else if (result > 0) /* 名字大于当前节点——继续查找兄弟 */
{ inode = inode->i_peer; }
else /* 匹配! */
{
name = inode_nextname(name); /* 前进到下一段(跳过 '/')*/

if (*name == '\0' || INODE_IS_MOUNTPT(inode))
{
/* 路径结束 或 遇到挂载点——停止搜索 */
desc->relpath = name;
break;
}

/* 还有更多路径段,进入子节点继续 */
inode = inode->i_child;
}
}
}

实例:查找 /dev/console 的过程

1
2
3
4
5
6
7
8
9
Step 1: name = "dev/console" (跳过前导 '/')
比较 "dev" vs g_root_inode->i_child 的各 peer
匹配 "dev" 节点
Step 2: name = "console" (inode_nextname 跳过 "dev/")
进入 dev->i_child
比较 "console" vs dev 的子节点
匹配 "console" 节点
Step 3: name = "" (到达路径末尾)
返回 desc->node = console inode

遇到挂载点时:如果路径是 /mnt/sd/dir/file.txt,搜索到 /mnt/sd(MOUNTPT 类型)时停止,desc->relpath = "dir/file.txt" 保存剩余路径。后续由挂载的文件系统(如 FAT)处理相对路径。

理解了 inode 树和路径查找,下面看核心数据结构如何将树节点与实际 I/O 操作连接起来。


3. 核心数据结构

3.1 struct file_operations:驱动的操作函数表

文件:include/nuttx/fs/fs.h:218-250

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct file_operations
{
CODE int (*open)(FAR struct file *filep);
CODE int (*close)(FAR struct file *filep);
CODE ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
CODE ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
CODE off_t (*seek)(FAR struct file *filep, off_t offset, int whence);
CODE int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);
CODE int (*mmap)(FAR struct file *filep, FAR struct mm_map_entry_s *map);
CODE int (*truncate)(FAR struct file *filep, off_t length);
CODE int (*poll)(FAR struct file *filep, FAR struct pollfd *fds, bool setup);
CODE ssize_t (*readv)(FAR struct file *filep, FAR struct uio *uio);
CODE ssize_t (*writev)(FAR struct file *filep, FAR struct uio *uio);
CODE int (*unlink)(FAR struct inode *inode);
};

每个设备驱动注册时提供这样一个函数指针表。VFS 通过 inode->u.i_ops 指针调用对应方法。

3.2 struct mountpt_operations:文件系统的操作函数表

文件:include/nuttx/fs/fs.h:285-363

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct mountpt_operations
{
/* open 的签名与 driver 不同:多了 relpath 和 mode 参数 */
CODE int (*open)(FAR struct file *filep, FAR const char *relpath,
int oflags, mode_t mode);

/* close/read/write/seek/ioctl/poll 签名与 file_operations 完全相同! */
CODE int (*close)(FAR struct file *filep);
CODE ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
CODE ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
/* ... */

/* 文件系统特有操作 */
CODE int (*bind)(FAR struct inode *blkdriver, FAR const void *data,
FAR void **handle);
CODE int (*unbind)(FAR void *handle, FAR struct inode **blkdriver,
unsigned int flags);
CODE int (*statfs)(FAR struct inode *mountpt, FAR struct statfs *buf);
CODE int (*mkdir)(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode);
CODE int (*unlink)(FAR struct inode *mountpt, FAR const char *relpath);
/* ... */
};

关键设计file_operationsmountpt_operationsclose/read/write/seek/ioctl/poll 方法签名和位置完全相同。VFS 在打开文件后统一通过 inode->u.i_ops->read() 调用——无论底层是设备驱动还是文件系统。区别只在 open() 时:驱动收到 open(filep),文件系统收到 open(filep, relpath, oflags, mode)

3.3 struct file:打开的文件

文件:include/nuttx/fs/fs.h:457-467

1
2
3
4
5
6
7
8
struct file
{
int f_oflags; /* open 时的标志(O_RDONLY 等)*/
atomic_t f_refs; /* 引用计数 */
off_t f_pos; /* 当前读写位置 */
FAR struct inode *f_inode; /* 关联的 inode(指向驱动或文件系统)*/
FAR void *f_priv; /* 驱动/文件系统的私有数据 */
};

每次成功 open() 都创建一个 struct file,它绑定到对应的 inode。后续 read()/write() 通过 file->f_inode->u.i_ops 分发到正确的驱动或文件系统。

3.4 union inode_ops_u:操作指针联合体

文件:include/nuttx/fs/fs.h:380-397

1
2
3
4
5
6
7
8
union inode_ops_u
{
FAR const struct file_operations *i_ops; /* 字符驱动操作 */
FAR const struct block_operations *i_bops; /* 块驱动操作 */
FAR const struct mountpt_operations *i_mops; /* 文件系统操作 */
FAR struct mtd_dev_s *i_mtd; /* MTD 设备 */
FAR char *i_link; /* 软链接目标路径 */
};

同一个 inode 根据类型使用不同的联合体成员。VFS 根据 i_flags 判断类型后选择正确的成员访问。

数据结构建立了”谁是谁”的关系。接下来看设备驱动如何将自己注册到 inode 树中。


4. 设备驱动注册:register_driver()

设备驱动必须出现在 inode 树中,用户代码才能通过路径名找到它。register_driver() 的职责是:在 inode 树的指定路径创建一个 DRIVER 类型的节点,并将操作函数表关联上去。

4.1 注册流程

文件:fs/driver/fs_registerdriver.c:67-134

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int register_driver_with_size(FAR const char *path,
FAR const struct file_operations *fops,
mode_t mode, FAR void *priv, size_t size)
{
FAR struct inode *node;
int ret;

inode_lock(); /* 获取写锁 */
ret = inode_reserve(path, mode, &node); /* 在树中创建节点 */
if (ret >= 0)
{
INODE_SET_DRIVER(node); /* 设置类型 = DRIVER */
node->u.i_ops = fops; /* 绑定操作函数表 */
node->i_private = priv; /* 绑定驱动私有数据 */
node->i_size = size;
}
inode_unlock(); /* 释放写锁 */
return ret;
}

inode_reserve()(文件:fs/inode/fs_inodereserve.c:176)负责:

  1. 调用 inode_search() 找到插入位置
  2. 为路径中不存在的中间目录创建 PSEUDODIR 节点(如 /dev 不存在则自动创建)
  3. 为最终节点分配内存(fs_heap_zalloc(FSNODE_SIZE(namelen))
  4. 按字母序插入到 i_peer 链表中

实例:register_driver("/dev/ttyS0", &uart_ops, 0666, dev) 的效果

1
2
3
4
5
6
7
Before:
g_root_inode -> "dev" (PSEUDODIR) -> i_child -> "console" (DRIVER)

After:
g_root_inode -> "dev" (PSEUDODIR) -> i_child -> "console" -> i_peer -> "ttyS0" (DRIVER, NEW)
u.i_ops = &uart_ops
i_private = dev

设备注册完成后,用户代码就可以通过 open("/dev/ttyS0", ...) 找到这个节点。接下来看 open() 的完整路径。


5. open() 完整调用链

open() 需要完成多件事:路径解析(可能跨越伪文件系统和真实文件系统)、权限检查、文件描述符分配、驱动/文件系统的初始化回调。这是整个 VFS 唯一需要区分”驱动”和”文件系统”的地方——之后 read/write/close 走统一路径。

5.1 调用链概览

下图展示了 open("/dev/console", O_RDWR) 从路径查找到驱动调用的完整时序:

open

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
User: open("/dev/console", O_RDWR)
|
v [KERNEL 模式: syscall 进入内核]
open() fs/vfs/fs_open.c:430
|
+-> nx_vopen(fdlist, path, oflags) fs/vfs/fs_open.c:270
|
+-> file_allocate() 分配 struct file
|
+-> file_vopen(filep, path) fs/vfs/fs_open.c:74
| |
| +-> inode_find(&desc) 加锁 + 路径搜索 + 引用计数++
| | +-> inode_search() 走 inode 树
| |
| +-> [DRIVER?] inode->u.i_ops->open(filep)
| +-> [MOUNTPT?] inode->u.i_mops->open(filep, relpath, oflags, mode)
|
+-> fdlist_dupfile(list, filep) 分配 fd 编号
返回 int fd

5.2 file_vopen():核心分发逻辑

文件:fs/vfs/fs_open.c:74-246(关键摘录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int file_vopen(FAR struct file *filep, FAR const char *path,
int oflags, mode_t umask, va_list ap)
{
struct inode_search_s desc;
FAR struct inode *inode;

SETUP_SEARCH(&desc, path, (oflags & O_NOFOLLOW) != 0);
ret = inode_find(&desc); /* 路径查找 + 加引用 */
inode = desc.node;

filep->f_oflags = oflags;
filep->f_inode = inode; /* 绑定 inode 到 file */

/* 根据 inode 类型分发 */
if (INODE_IS_MOUNTPT(inode))
{
ret = inode->u.i_mops->open(filep, desc.relpath, oflags, mode);
}
else if (INODE_IS_DRIVER(inode))
{
ret = inode->u.i_ops->open(filep);
}
else
{
ret = -ENXIO;
}

return ret;
}

分发逻辑的关键:对于驱动节点(如 /dev/console),直接调用 i_ops->open(filep)。对于挂载点(如 /mnt/sd/file.txt),调用 i_mops->open(filep, "file.txt", oflags, mode)——将挂载点以下的相对路径传给文件系统处理。

5.3 文件描述符分配

文件:fs/inode/fs_files.c:566-633

NuttX 的文件描述符表(struct fdlist)是一个二维数组

1
2
3
4
5
struct fdlist
{
uint8_t fl_rows; /* 行数 */
FAR struct fd **fl_fds; /* 二维数组指针 */
};

fd 值映射为:row = fd / BLOCK_SIZEcol = fd % BLOCK_SIZEBLOCK_SIZE = CONFIG_NFILE_DESCRIPTORS_PER_BLOCK)。

分配时线性扫描找第一个空位(fl_fds[i][j].f_file == NULL),将 struct file 指针存入该位置,返回 i * BLOCK_SIZE + j 作为 fd。

open() 完成后,后续的 read/write/close 都通过 fd → file → inode → ops 的路径分发。下面看 read/write 如何工作。


6. read()/write() 分发机制

因为 open() 时已经将 inode 绑定到 struct file,而 file_operationsmountpt_operationsread/write 方法签名完全相同、位于结构体的相同偏移。VFS 只需要 file->f_inode->u.i_ops->read(filep, buf, len)——无论底层是什么。

6.1 read() 调用链

文件:fs/vfs/fs_read.c:158file_readv() 核心)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ssize_t file_readv(FAR struct file *filep,
FAR struct uio *uio)
{
FAR struct inode *inode = filep->f_inode;

if (inode->u.i_ops->readv != NULL)
{
return inode->u.i_ops->readv(filep, uio);
}
else if (inode->u.i_ops->read != NULL)
{
/* 兼容路径:逐 iov 段调用 read() */
return file_readv_compat(filep, uio);
}

return -ENOSYS;
}

完整路径:read(fd, buf, n)file_get(fd) 获取 struct file *file_readv()i_ops->read(filep, buf, n)

6.2 分发路径图

1
2
3
4
5
6
7
8
9
10
11
12
13
fd (int)
| file_get(fd)
v
struct fdlist [row][col]
| .f_file
v
struct file
| .f_inode
v
struct inode
| .u.i_ops (= file_operations* OR mountpt_operations*)
v
i_ops->read(filep, buf, len) <- driver or filesystem, same signature!

对比 Linux:Linux VFS 中 read/write 还经过 page cache 层(generic_file_read_iter),而 NuttX 是直通式——没有 page cache,数据直接在驱动/文件系统和用户缓冲区之间传递。这使得 NuttX VFS 延迟更低、更可预测,但不支持 mmap 文件映射(需要 page cache)。

read/write 的简洁性源于 open() 时已完成了所有”类型判断”工作。接下来看更复杂的 mount() 如何将真实文件系统接入 inode 树。


7. mount() 机制:真实文件系统的接入

伪文件系统只能存放设备驱动节点,不能存储持久化数据。要访问 SD 卡、Flash 上的文件,需要将真实文件系统”嫁接”到伪文件系统的某个节点上。mount 就是建立这个嫁接关系的操作。

7.1 文件系统注册表

文件:fs/mount/fs_mount.c:109-195

NuttX 在编译时通过静态数组注册所有支持的文件系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 基于块设备的文件系统 */
static const struct fsmap_t g_bdfsmap[] = {
{ "vfat", &fat_operations },
{ "romfs", &romfs_operations },
{ "smartfs", &smartfs_operations },
{ "littlefs", &littlefs_operations },
{ NULL, NULL }
};

/* 基于 MTD 的文件系统 */
static const struct fsmap_t g_mdfsmap[] = { ... };

/* 无需驱动的文件系统 */
static const struct fsmap_t g_nonbdfsmap[] = {
{ "tmpfs", &tmpfs_operations },
{ "procfs", &procfs_operations },
{ "nfs", &nfs_operations },
{ "binfs", &binfs_operations },
{ NULL, NULL }
};

mount() 调用时,内核遍历上述三个数组匹配 filesystemtype 字符串(如 "vfat"),找到对应的 mountpt_operations 函数表。如果没有匹配的文件系统类型,返回 -ENODEV

7.2 mount() 流程

文件:fs/mount/fs_mount.c:286-570nx_mount() 核心,以下为关键步骤摘录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mount("/dev/mmcsd0", "/mnt/sd", "vfat", 0, NULL):

Step 1: 根据 source 找到块设备 inode
find_blockdriver("/dev/mmcsd0") → 得到块设备 inode

Step 2: 在注册表中查找文件系统类型
遍历 g_bdfsmap[] 找到 "vfat" → 得到 &fat_operations

Step 3: 检查目标路径是否存在且为伪目录
inode_find("/mnt/sd") → 必须是 PSEUDODIR

Step 4: 调用文件系统的 bind() 初始化
fat_operations->bind(blk_inode, data, &fshandle)
→ FAT 读取超级块、初始化内部状态

Step 5: 将目标 inode 转换为挂载点
inode_reserve("/mnt/sd", 0777, &mountpt_inode)
INODE_SET_MOUNTPT(mountpt_inode)
mountpt_inode->u.i_mops = &fat_operations
mountpt_inode->i_private = fshandle

mount 完成后,inode 树中 /mnt/sd 节点的类型从 PSEUDODIR 变为 MOUNTPT。后续对 /mnt/sd/xxxopen() 会在路径搜索时被该挂载点”截断”,剩余路径交给 FAT 文件系统处理。

7.3 挂载点如何”截断”路径搜索

回忆 _inode_search() 中的关键判断:

1
2
3
4
5
if (*name == '\0' || INODE_IS_MOUNTPT(inode))
{
desc->relpath = name; /* 剩余路径传给文件系统 */
break; /* 停止在伪文件系统中搜索 */
}

例如 open("/mnt/sd/photos/img.jpg")

  • 搜索到 /mnt/sd 时发现是 MOUNTPT,停止
  • desc->relpath = "photos/img.jpg"
  • file_vopen() 调用 fat_operations->open(filep, "photos/img.jpg", oflags, mode)
  • FAT 文件系统在自己的目录结构中解析 photos/img.jpg

这就是 NuttX 伪文件系统与真实文件系统的”交接”机制。


8. 文件描述符管理

POSIX 要求每个进程有独立的文件描述符空间。在 NuttX 中,struct fdlist 存储在 task_group_s 中——同一任务组(进程)的所有线程共享文件描述符表。

8.1 stdin/stdout/stderr 与 /dev/console

启动时第一个任务(IDLE)打开 /dev/console 三次,获得 fd 0、1、2:

文件:sched/init/nx_start.c:540-545

1
2
3
nx_open("/dev/console", O_RDWR);   /* fd 0 = stdin */
nx_open("/dev/console", O_RDWR); /* fd 1 = stdout */
nx_open("/dev/console", O_RDWR); /* fd 2 = stderr */

后续通过 task_create()posix_spawn() 创建的子任务/进程会继承父任务的文件描述符表(group_setuptaskfiles() 中完成)。所有任务默认共享对 /dev/console 的 stdin/stdout/stderr。

8.2 I/O 重定向

如果一个任务关闭 fd 1 然后打开一个新文件,新文件会获得 fd 1(因为分配时取最小空闲 fd)。之后该任务的所有 printf()(内部写 stdout = fd 1)就输出到了新文件——这就是 I/O 重定向的原理。NuttX 的 telnet 服务器就是这样实现的:将 /dev/telnet 设备重定向为子任务的 stdin/stdout。


9. 对比分析

特性 NuttX VFS Linux VFS FreeRTOS
根文件系统 伪文件系统(内存树) 必须是物理块设备 无 VFS 概念
inode 结构 树(parent/peer/child) 哈希表 + dentry cache
Page Cache 有(read/write 经过 page cache)
文件描述符 per-task_group 二维数组 per-process fd_table per-task 链表(+FAT 扩展)
设备访问 open(“/dev/xxx”) open(“/dev/xxx”) 直接调用驱动 API
挂载点 inode 类型=MOUNTPT vfsmount + super_block ff_mount (FatFs only)
支持的 FS FAT, ROMFS, LittleFS, TMPFS, PROCFS, NFS… ext4, btrfs, XFS, NFS… FatFs (通常只有一个)
路径缓存 无(每次全路径搜索) dentry cache(O(1) 热路径)
统一 I/O open/read/write/close/ioctl 无统一接口

NuttX VFS 的设计取舍

  • 无 dentry cache——每次 open() 都要走完整的树搜索。对嵌入式系统可接受(inode 树通常很浅,几十个节点),但对大型文件系统性能不如 Linux。
  • 无 page cache——read/write 直通驱动,延迟低、内存占用小,但无法利用缓存加速重复读取。
  • 伪文件系统根——不需要任何存储设备就能启动,特别适合 MCU + 外设的场景。

10. 关键要点

  1. NuttX 的根永远是伪文件系统——一棵内存中的 inode 树,设备驱动和真实文件系统都挂载其上。

  2. inode 树使用 parent/peer/child 三指针结构——同层兄弟按名字字母序排列,路径查找逐段匹配。

  3. file_operations 和 mountpt_operations 共享 read/write/close 签名——open() 之后,VFS 对驱动和文件系统的分发完全统一。

  4. register_driver() 在 inode 树中创建 DRIVER 节点,绑定操作函数表和私有数据。

  5. open() 是 VFS 唯一需要区分类型的地方——驱动收到 open(filep),文件系统收到 open(filep, relpath, oflags, mode)

  6. mount() 将伪目录节点转换为 MOUNTPT,后续路径搜索在该节点”截断”,剩余路径交给文件系统。

  7. 文件描述符是 per-task_group 的二维数组,子任务继承父任务的 fd 表(包括 stdin/stdout/stderr)。

  8. NuttX VFS 是直通式(无 page cache)——延迟低、确定性好,适合实时系统。


11. 参考文件索引

文件路径 关键内容 引用行号
include/nuttx/fs/fs.h inode, file, file_operations, mountpt_operations, fdlist 118-505
fs/inode/inode.h inode_search_s, SETUP_SEARCH 宏 50-143
fs/inode/fs_inodesearch.c g_root_inode, inode_search(), _inode_search(), _inode_compare() 57-573
fs/inode/fs_inodereserve.c inode_reserve(), inode_alloc(), inode_insert() 176-264
fs/inode/fs_inodefind.c inode_find()(加锁 + 搜索 + 引用计数) 52-76
fs/inode/fs_inode.c g_inode_lock, inode_lock/unlock, inode_checkperm() 49-153
fs/inode/fs_files.c fdlist_dupfile(), file_allocate(), file_get() 566-982
fs/driver/fs_registerdriver.c register_driver(), register_driver_with_size() 67-134
fs/vfs/fs_open.c open(), nx_vopen(), file_vopen() 74-455
fs/vfs/fs_read.c read(), file_readv() 158-413
fs/vfs/fs_write.c write(), file_writev() 148-467
fs/mount/fs_mount.c mount(), nx_mount(), g_bdfsmap/g_nonbdfsmap 109-585
fs/fs_initialize.c fs_initialize() 启动初始化 全文
drivers/serial/serial.c uart_register(), uart_open(), g_serialops 139-713
Documentation/implementation/nuttx_tasking.rst 官方 VFS 设计描述 269-307