跳到主要内容

VFS 对象模型

axfs-ng-vfs 将“磁盘或伪文件系统中的对象”“目录树中的名字”和“挂载命名空间中的位置”拆成不同类型。这个分离是 hard link、bind mount、mount namespace、rename cache 失效和 file-backed mmap 能共存的基础。

1. 能力对象

VFS 通过文件系统级和节点级两组 trait 描述实现能力。文件系统对象发布根节点与持久化入口,节点对象承载 inode、metadata、数据和目录操作,两者都不包含挂载位置。

1.1 文件系统实例

FilesystemOps 描述一个已经构造完成的文件系统实例,Filesystem 则用 Arc<dyn FilesystemOps> 提供共享所有权。表中的方法决定实例级可观测行为和 shutdown 边界。

FilesystemOps 只暴露文件系统实例级操作:

方法语义
name()稳定的文件系统类型名,例如 ext4vfattmpfs
is_readonly()superblock/实现层只读事实,默认 false
root_dir()返回文件系统内部根 DirEntry
stat()返回容量、block size、inode 和 name-length 信息
flush()刷新该文件系统的持久状态,默认成功
shutdown()最终关闭;默认委托 flush()

FilesystemArc<dyn FilesystemOps> 的轻量包装。它不保存 mount flags 或父目录位置,因为同一个文件系统对象可以被多次挂载,每个挂载实例的只读标志、source、mount ID 和传播关系都不同。

1.2 节点能力

节点能力由 NodeOpsFileNodeOpsDirNodeOpsFsPollable 组合。继承关系表达“文件或目录同时具备哪些操作”,而不是共享具体文件系统内部状态。

NodeOps 保存所有节点共享的 inode、metadata、sync 和 downcast 能力。FileNodeOps 承担按 offset 的数据操作,DirNodeOps 承担名字空间变更。目录读取通过 DirEntrySink 流式返回,避免 VFS 强制每个实现先分配完整 entry vector;实现回调 sink 时不应再次操作当前目录,因为具体文件系统可能仍持有目录内部锁。

FsPollable 使用与 Linux poll 相同的 readiness bit,StarryOS 因而不需要把公共 VFS 事件重新编码。目录默认始终可读写;文件和设备节点可按各自状态注册 waker。

2. 节点身份

节点身份由 DirEntry 的实现对象、父目录引用和名称共同构成,目录缓存则保存这些身份的可重用视图。身份与缓存分离后,动态目录可以禁用缓存,hard link 也可以让不同名称共享同一内容 owner。

2.1 目录项

DirEntry 内部同时保存:

  • Node::File(FileNode)Node::Dir(DirNode)
  • 独立的 NodeType,可表示 regular file、directory、symlink 和各类 special node;
  • Reference { parent, name }
  • TypeMap user data。

Reference 的 key 是 (parent Arc address, name)。目录 cache 和 mount child map 使用这个 key 区分同名但不同父目录的 entry。根 entry 的 parent 为 None;普通子 entry 的 parent 是创建它时观察到的父 DirEntry

TypeMap 是节点级扩展槽,常见用途是把 CachedFileShared 绑定到 VFS 节点。hard link 创建新 DirEntry 时,DirNode::link() 会复制源节点 user data,使两个名字共享同一页缓存,而不是得到两份不一致的文件内容。ext4 还按 (filesystem pointer, inode) 建立 weak 索引,补偿同一 inode 由不同 lookup 路径重建 DirEntry 的情况。

2.2 查找缓存

每个 DirNode 保存具体 DirNodeOpsHashMap<String, DirEntry> cache、cache_generation 和可选 child mountpoint。generation 防止慢速底层 lookup 在并发名字空间变更后重新发布旧目录项。

DirNode::lookup_and_cache() 在内部锁外调用具体文件系统,再通过 generation 判断结果是否还能进入 cache。该过程保持底层 I/O 可睡眠,同时不让并发 unlink 或 rename 后的旧快照复活。

DirNode::lookup(name)
-> verify_entry_name()
-> 若实现不可缓存,直接 ops.lookup()
-> 记录 cache_generation
-> cache hit 则返回
-> cache miss 时调用 ops.lookup()
-> 再取 cache lock
-> generation 未变化才插入;否则返回刚查到的节点但不发布旧快照

generation 使用 Acquire/AcqRel,解决“慢 lookup 在并发 unlink/rename 后把旧 entry 重新插入”的问题。动态 procfs/sysfs 类目录可以让 is_cacheable() 返回 false,每次查找都由实现生成当前视图。

2.3 名字空间变更

create、link、unlink 和 rename 都先让具体文件系统提交名字空间变化,再更新 VFS dentry cache。这样底层失败不会留下只存在于缓存的虚假 entry。

操作底层成功后的 cache 动作
create/open-create插入新 entry,递增 generation
link共享源 user data,再插入新名字
unlink删除名字,递增 generation;目录 entry 递归 forget
rename(同目录)在一把 cache lock 内移除 source/destination
rename(跨目录)分别使 source 和 destination cache 失效

rename 后不能直接把旧目录的 children cache 搬到新目录。缓存中的子 DirEntry::Reference.parent 仍指向旧目录,会使后续 absolute path、unlink 和 rename 回到已经不存在的路径。当前实现只转移节点 user data 和挂载点引用,再从目标文件系统重新 lookup 新 entry;子项按需重新加载。

3. 可见位置

Location 在节点身份之上增加 Mountpoint,从而表达同一节点在特定命名空间中的可见位置。metadata 和错误域则提供跨具体文件系统的稳定结果,使上层不需要识别 ext4、FAT 或伪文件系统内部类型。

3.1 命名空间位置

Location 保存 Arc<Mountpoint>DirEntry。它委托 inode、metadata、sync、poll 等节点动作,同时在 lookup 和 parent traversal 中解释 mount 边界:

  • lookup 到一个挂载槽后,resolve_mountpoint() 进入最上层 child mount root;
  • 位于 mount root 的 parent() 返回挂载发生处的父位置;
  • 全局/namespace root 没有父位置;
  • absolute_path() 按解析后的 mount tree 生成路径,bind mount 的根使用目标挂载位置重定位;
  • metadata update、create、link、rename 和 unlink 先检查当前 mount 是否只读。

同一个 DirEntry 可以出现在多个 Location 中。例如把 /srv/data bind 到 /mnt 后,两边共享节点和页缓存,但 absolute_path()、父遍历、mount flags 和 namespace visibility 由各自 Location 决定。

3.2 元数据

Metadata 包含 device、inode、size、block count、时间、节点类型、权限、link count、uid/gid 和 device ID;MetadataUpdateOption 表示一次操作明确修改的字段。具体文件系统可以拒绝不支持的更新并返回结构化 VfsError,而不需要向上层暴露内部 inode 结构。

VfsError 是公共 VFS 唯一错误域,不依赖 Linux errno。下表按名字空间、权限、资源和能力缺失对变体分组,便于格式 adapter 和 syscall 层保持一对一语义。

类别代表变体
名字空间NotFoundAlreadyExistsNotADirectoryDirectoryNotEmpty
路径限制NameTooLongFilesystemLoopCrossesDevices
权限/状态PermissionDeniedReadOnlyFilesystemResourceBusyBadState
资源/I/ONoMemoryStorageFullTimedOutWouldBlockIo
能力缺失UnsupportedOperationNotSupportedNotATty

ax-fs-ngVfsErrorax_io::IoErrorBlockError 间按稳定语义转换;StarryOS 只在 syscall 边界转换为 Linux errno。底层实现不得提前把结构化失败压成整数或字符串。新的磁盘或伪文件系统必须维持稳定 root entry、正确 parent/name、hard-link 内容共享、开放后 unlink 生命周期、EOF 清零和可睡眠锁边界,这些要求都由上述对象模型直接产生。