文件与页缓存
ax-fs-ng 将打开文件描述、VFS 节点和共享文件内容状态分开。File 持有本次 open 的 flags 与游标,FileBackend 决定 cached/direct/path-only 行为,CachedFileShared 保存同一 inode 共享的文件长度、4 KiB 页面、写回和 mmap 协调状态。
1. 文件状态
文件访问状态分为打开实例、缓存页 owner 和 inode 共享状态。File 保存本次打开的 flags 与游标,PageCache 持有单页物理内存,CachedFileShared 则让同一 inode 的不同路径和文件描述共享内容与持久化状态。
1.1 打开实例
file/open.rs::OpenOptions 处理 read、write、append、truncate、create、create_new、directory、no_follow、direct、path、mode 和 uid/gid。open() 先解析父路径或现存 Location,再验证 flag 组合和节点类型,最终返回 OpenResult::File 或 OpenResult::Dir。
| 选择 | FileBackend | 数据路径 |
|---|---|---|
| 普通磁盘/内存文件 | Cached(CachedFile) | 共享页缓存 |
O_DIRECT | Direct(Location) | 直接调用 FileNodeOps,调用方承担对齐语义 |
O_PATH | path-only backend/flags | 只保留位置,不允许普通 read/write |
File 保存可选的 Mutex<u64> 游标;stream 节点没有 seek position,普通文件初始 position 为 0。fd clone/dup 是否共享打开文件描述状态由上层 fd owner 决定。read_at/write_at 不改变游标,Read/Write trait 路径在完成后推进 position。access check 在每次动作前核对 open flags,不能只依赖 syscall 初次校验。
1.2 页面所有权
PageCache 拥有一个由 FsPageProvider 分配的 4 KiB FsPage,并保存:
dirty:是否有未持久化内容;dirty_generation:每次写入递增,用于识别 writeback snapshot 后的并发写;- writeback protection 状态:mmap 写保护窗口及窗口内再次变脏事实。
ArceOS provider 使用 ax-alloc 的 UsageKind::PageCache 申请/释放物理页,并提供 virtual-to-physical 转换。FsPage 是 owner,Drop 恰好归还一次;cache、DMA request 或 listener 只能借用/转移明确对象,不能保存无 owner 的裸页地址。
1.3 缓存共享
CachedFile::get_or_create(Location) 首先读取 DirEntry::user_data():
- 已有
FileUserData时直接共享CachedFileShared; - ext4 节点还按
(filesystem pointer, inode)查询 weak 全局索引; - tmpfs/ramfs 创建无界 cache、没有 backing
FileNode; - 磁盘文件创建容量为 512 页的 LRU,并登记到全局 reclaim registry;
- 再次取得 user-data lock,只有首个创建者发布,竞争失败者使用已发布对象。
每个打开句柄拥有独立 ReadAheadState,因此一个随机读 fd 不会重置另一个顺序读 fd 的窗口;实际页内容、长度和 dirty 状态仍由 inode 共享。
2. 缓存读写
buffered I/O 在 CachedFileShared::io_lock 下协调 file length、page state 和 backing I/O,并通过 kernel scratch 与用户内存隔离。读路径可批量预读,写路径则根据覆盖范围决定是否读取旧 backing。
2.1 缓存读取
磁盘顺序读的预读窗口从 4 页开始,连续命中时倍 增,最大 32 页;随机读按本次请求页数读取并把下一次窗口重置到 4 页。tmpfs/ramfs 始终一次处理一页。
新分配页没有隐式清零保证,具体文件系统在 EOF 可 short-read,因此 miss 路径必须显式把未读取尾部填零。否则 mmap 或扩展后的读取可能泄露旧物理页内容。
目的 buffer 可能是 StarryOS 用户地址。实现先复制到临时 PageCache scratch,释放 cached-file 锁后再写入用户 buffer,使用户缺页可以取得 AddrSpace 锁,而不会形成 CachedFile -> AddrSpace 顺序。
2.2 缓存写入
write 在 io_lock 下串行修改 file length 和 cache:
- 写越过 EOF 时,先处理旧 EOF 所在 partial page 的零洞,再扩展 backing length;
- full-page overwrite 不读取旧 backing;partial overwrite 先填充原页;
- 从用户/
IoBuf复制到 scratch 后,在 page-cache lock 下写目标区间; - 磁盘页递增 dirty generation 并标 dirty;内存文件的 cache 就是唯一数据源;
append()在同一串行化边界内读取当前长度并返回实际起始 offset。
文件长度用 AtomicU64 发布:扩展使用 compare-exchange max,truncate 使用 Release store,读取使用 Acquire。它提供长度可见性,但不替代 io_lock 对 resize、页内容和 backing 操作的事务保护。