Core/IO 使用指南——文件 IO 原语层(IFileSystem / IFileHandle)
给谁看:需要文件级 IO 的组件开发者(引擎、数据结构、工具);外部业务通常经
IStorageEngine间接用(见../TC.Tier.Runtime/docs/storage-engine.md)。 回答什么:怎么构造文件系统、怎么读写、怎么落盘、能力怎么协商、什么不要做。 本篇只讲怎么用——机制细节(spec 全参数/句柄池内部/网络桥内部)在链接与源码注释。
0. 快速开始(30 秒上手)
using TC.Tier.Core.IO;
// ── 1. 构造文件系统(spec 一行切换——§1.1)──
using var fs = TierFs.New("local:///" + rootDir); // 本地文件系统(New = 建空镜像)
using var mem = TierFs.Open("memory:"); // 内存文件系统私有卷(测试隔离)
using var remote = TierFs.Open("network:///s3/minio:9000/tier-logs?tls=0&cred=env:MINIO_KEY");
// ── 2. 建结构 + 提前创建(建段协议:创建成本移出运行时热路径)──
fs.EnsureRoot(); // 根存在保证(幂等)
fs.CreateDirectory("struct1/eng0/compact"); // mkdir -p 幂等 + 耐久
fs.CreateFile("struct1/eng0/data.0", preallocateSize: 1 << 30,
extra: header); // 真预留 + FileExtra 创建即写;已存在抛 AlreadyExists
// ── 3. 运行时打开读写 ──
using (var h = fs.Open("struct1/eng0/data.0", new FileOpenOptions
{ Access = AccessMode.ReadWrite, Mode = FileOpenMode.OpenExisting, Sharing = FileSharing.ReadWrite }))
{
h.Write(4096, record); // pwrite:不读不推进游标;越过 EOF 零洞扩展
var landing = h.Append(record); // 多写者原子追加(返回落点)
h.SetFileExtra(meta); // FileExtra ≤1.5K
h.Flush(); // ★ 网络文件系统 = 唯一持久化点(置顶一)
}
// ── 4. 枚举 / 恢复扫描 ──
var segs = fs.EnumerateFiles("struct1/eng0", "data.*"); // 模式匹配
var all = fs.EnumerateEntries("struct1", "*", recursive: true); // 混合递归——恢复扫盘
var st = fs.Stat("struct1/eng0/data.0"); // 完整信息(含 FileExtra)
1. 概念(术语在此定义)
- 两平面 × 四类文件系统 × 一个注入点:
- 命名空间平面
IFileSystem(目录/创建/枚举/FileExtra/卷锁——一个实例 = 根空间); - 数据平面
IFileHandle(pread/pwrite/追加/空间管理/映射/锁); - 四类文件系统:本地(
DiskFileSystem·local://)/ 内存(MemoryFileSystem·memory:)/ 网络(RemoteFileSystem·network://,对象存储协议)/ 虚拟(TierVolumeFs·virtual://,.tier文件 / Linux 块设备,自持一致性 + 自管页缓存;深指南virtual-file-system.md); - 一个注入点:网络文件系统的扩展面 =
IObjectStore(厂商实现它即接入完整网络文件系统栈; 网络文件系统桥层语义见network-file-system.md; S3 参考实现见../TC.Tier.Core.IO.S3/docs/s3-protocol.md)。
- 命名空间平面
- spec = 协议头即术语:
local:///memory:/virtual:///network:///s3——换 URI 即换文件系统, 代码零改动(TierFs工厂,可序列化、可配置驱动)。 - 能力协商:每文件系统/句柄的
Capabilities/UnbufferedSupport如实报告能力位(DIO/稀疏/回退), 消费者按能力位决策,不按文件系统硬编码分支。
⚠️ 置顶一(网络文件系统):持久化唯一入口是 Flush
RemoteFileSystem 写句柄是 staging 写回层:Write/Append 只进 staging 即返回;任何 Dispose 都不上传
(池内 = 归还;池外 = 关闭且未 Flush 的 staging 丢弃——语义同构"未 fsync 即丢")。用完即持久必须显式
h.Flush()。池的三出口(Release(close:true)/RemoveAll/pool.Dispose)同样不 flush——RemoveAll
不得 flush(删段后 flush 会复活已删对象)。
⚠️ 置顶二(内存/虚拟):Dispose 方向差异(最反直觉)
| 文件系统 | fs.Dispose 语义 | 已开句柄 |
|---|---|---|
| 本地 / 网络 | "离开目录"——仅释放 fs 自持资源 | 继续有效(OS 句柄/staging 归消费者) |
| 内存文件系统 | "拔盘"——销毁卷(数据内存可能复用,必须失效) | 抛 ObjectDisposedException |
| 虚拟文件系统 | "关卷"——提交 + 置 clean + superblock 轮写 | 抛 ObjectDisposedException |
可移植规范:按"先映射、再句柄、最后 fs"顺序释放。
2. 怎么选(四类文件系统)
| 文件系统 | spec 头 | 生产特长 | 关键支撑 | 深指南 |
|---|---|---|---|---|
| 内存文件系统 | memory: |
高性能运行时——直址零拷贝、纳秒级元数据 | Reserved 直址;持久化 = 运行时导出镜像(§5.2) | memory-file-system.md |
| 本地文件系统 | local:// |
可视化目录 / 性能稳定——宿主工具直接查看 | 目录树形态;fsync 持久化 | local-file-system.md |
| 网络文件系统 | network:// |
本地不落地 / 容量无界 / 共享 | staging 写回层;Flush = PUT | network-file-system.md |
| 虚拟文件系统 | virtual:// |
功能最全 / 稳定性最高 / 快速迁移 | 自持崩溃一致性;能力位覆盖最全;dd 快道 | virtual-file-system.md |
"易失"是内存文件系统的机制属性不是概念缺席——持久化经导出镜像全额成立(§5.2);单元测试/CI
无需本地盘只是它的场景之一。选文件系统 = 选运行形态,不是选完成度——数据面速度四类带内持平,
差异在元数据面与一致性语义(实测见 perf/io-performance.md §9)。
2.1 构造(spec / 动词 / options 三件套)
using var db = TierFs.New ("local:///var/lib/tier?quota=100G&label=prod");
using var mem = TierFs.Open("memory:?label=test-a");
using var vol = TierFs.New ("virtual:///data/vol.tier?label=wal-a"); // 无 quota = 按需自动扩容
using var s3 = TierFs.Open("network:///s3/cos.example.com/bucket/pfx?vhost=1&cred=env:TIER_S3");
// 工厂 × options 合流(spec 定身份 + options 补调优;优先级 = spec 显式胜出 → options → 类型缺省)
using var db2 = TierFs.New("local:///var/lib/tier", new DiskFileSystemOptions { MetadataMode = DiskMetadataMode.Sidecar });
// 类型层直构(工厂底座——三段式签名 (位置[, options][, 日志]))
using var d3 = DiskFileSystem.New("/var/lib/tier", new DiskFileSystemOptions { QuotaBytes = 100L << 30 });
using var v3 = TierVolumeFs.New(TierVolumeCarrier.File("/data/vol.tier"), new TierVolumeFormatOptions { BlockSize = 4096 });
动词:New = 创建空镜像(已存在且非空抛 AlreadyExists);Open = 打开既有(不存在抛 NotFound);
OpenOrCreate = 懒初始化糖。四类文件系统全部同契约。
2.2 spec 语法(一分钟)
local:///var/... 本地文件系统(绝对) local:data/tier 相对(构造时对 CWD 固化)
local:///C:/data Windows 盘符 local://host/share UNC
local | local: 快捷:CWD 为根 memory: 内存文件系统(私有卷)
virtual:///data/v.tier 虚拟文件系统·文件载体 virtual:///dev/nvme0n1 设备载体
network:///s3/host[:port]/bucket/prefix 网络文件系统(协议首段必填——s3 随 IO.S3 程序集自动注册)
?label=.."a=100G&access=ro|wo|rw&exclusive=1&cred=env:NAME&spill=local:///var/tmp
quota 一词制 = 空间根硬上限(-1/缺省 = 无上限;虚拟文件系统文件载体 = 按需自动扩容)。
cred 永远是引用(env:NAME)不携值。完整参数表见 medium-parity-matrix.md。
3. 命名空间平面(IFileSystem)
- 路径契约:层级相对路径,
'/'唯一合法分隔符(\拒);空组件/./..越根/盘符/保留集 → 抛。 - 目录族:
CreateDirectory(mkdir -p 幂等 + 耐久)/DirectoryExists/DeleteDirectory(仅限空——不提供递归删)/MoveDirectory(整树移动,原子性看能力位)。 - 文件操作与创建解耦:
Exists/Delete(耐久)/Move(overwrite: true)(overwrite 必须显式)/CreateFile(preallocateSize:, extra:)(提前创建——与 Open 解耦,创建成本移出热路径)。 - 枚举族:
EnumerateFiles/Directories/Entries(*/?模式匹配;任一组件以.开头 → 枚举不可见, pattern 首字符.豁免;递归 Name = 相对所枚举目录的多组件路径)。 - 卷锁:
fs.AcquireExclusive(Timeout)(RAII + 异常安全;非重入;本地 = lock file 崩溃自愈; 网络 = 尽力型 fencing;内存 = 进程内真锁)。
4. 数据平面(IFileHandle)
4.1 打开语义四要素
using var h = fs.Open("log-0001.data", new FileOpenOptions
{
Access = AccessMode.ReadWrite, // Read / Write / ReadWrite
Mode = FileOpenMode.OpenOrCreate, // OpenExisting / OpenOrCreate / CreateNew / Truncate / Append
Sharing = FileSharing.ReadWrite, // advisory:None / Read / Write / ReadWrite / Delete
Hints = FileOpenHints.None, // NoBuffering(DIO) / WriteThrough / SequentialScan / RandomAccess
PreallocateSize = 64L << 20, // >0 → open 即幂等预分配
});
- 组合合法性构造时校验(写模式配
Access=Read→ 抛)。 - DIO 语义链(
Hints.NoBuffering):句柄UnbufferedSupport/RequiredAlignment= 三重对齐单一 事实源——对齐 buffer 必须走AlignedMemoryManager/PinnedBufferPool(§7 陷阱 1)。
4.2 位置读写与追加
h.Write(4096, record); // pwrite:不读不推进游标;越过 EOF 零洞扩展
int n = h.Read(4096, dst); // 返回实际读数(EOF 处可能 < dst.Length)
await h.WriteAsync(4096, mem, ct); // 异步族语义同同步
var landing = h.Append(record); // ★ 文件级原子预留(同 fs 任意句柄并发追加——落点两两不交)
三个位置概念各管各的:Write/Read(offset) 无状态 / Position/Seek 句柄级书签 /
Append 文件级原子预留。FileOpenMode.Append 只初始化游标于 EOF——不是强制追加。
4.3 空间管理
Preallocate()(幂等)/ SetLength()(截断/零扩展)/ PunchHole(0, len)(打洞,按 AllocationUnit
对齐;区间读零)/ AllocatedSize(物理占用)/ EnumerateAllocatedRanges()(块粒度区间)/
CollapseRange(区间移除/插入,看 RangeShift 位)。
4.4 持久化(持久化 = 把数据从"会丢的层"推进到"不会丢的层")
h.Flush(); // fsync/FlushFileBuffers/F_FULLFSYNC;网络文件系统 = PUT(唯一持久化点)
h.FlushData(); // fdatasync 语义(仅 Linux 与 Flush 可区分;否则 ≡ Flush 不抛)
- 本地/虚拟/网络经
Flush持久;内存文件系统无原地持久化层——持久化点 = 运行时导出镜像(§5.2)。 - group commit 模式:写 → 攒批 →
Flush()一次。
4.5 FileExtra 平面(文件附加数据——统一唯一概念)
一个文件 = 一份不透明附加数据 ≤1536 字节。h.FileExtra(全量读)/ ReadFileExtra(offset, dst) /
WriteFileExtra(offset, patch) / SetFileExtra(meta)(完全覆盖)。Delete 必清除 / Move 必保留。
"1536 数学":S3 用户元数据 HTTP 头 2048 字节总预算 − 键前缀,base64 3:4 折算——三类文件系统同限。
4.6 映射 / 范围锁 / 向量与拷贝
h.Map(0, 4096, AccessMode.ReadWrite)(必须 Dispose——独立引用,泄漏)/ Lock/TryLock(advisory
契约)/ WriteVector(readv/writev 或回退)/ src.CopyRange(dst, ...)(部分失败不回滚,
CompletedLength 携带)/ CloneRange(整文件克隆)。
5. 句柄池与镜像导出
5.1 句柄池(FileHandlePool——键控共享缓存)
| 场景 | 选择 |
|---|---|
| 长生命周期反复访问同一批文件(引擎段、索引页) | 池(同 key 命中同实例——省 open/探测开销) |
| 一次性临时文件(Compact 临时段) | 裸 fs.Open + using |
| 需要"现在就关" | 裸 Open 或 Release(h, close: true) |
真关闭只有三条出口:Release(h, close: true) / RemoveAll(pred) / pool.Dispose()(须先于 fs.Dispose)。
归还后不得再触碰该引用。删段顺序:pool.RemoveAll(...) → fs.Delete(...)(Windows 句柄打开时删除被拒)。
5.2 采集/还原/迁移(RootSpaceImage——四类文件系统全部支持)
一套代码通吃四类文件系统(只认 IFileSystem 接口平面):4 源 × 4 目标 = 16 格全部成立
——任意文件系统可导出为存档、任意存档可还原到任意文件系统、任意两文件系统可直转。
产物两种形态:
- TCA1 结构化流(清单 + 数据帧 + CRC)——内容镜像,可跨文件系统(内存 → 本地 / 本地 → 网络等);
- 字节直拷快道(
ContiguousCapture)——整卷镜像(仅虚拟 ↔ 虚拟,.tier文件 ↔ 块设备互拷, dd 快道;镜像后目标重载内存态)。
using TC.Tier.Core.IO.Image; // RootSpaceImage
// 导出:任何文件系统 → TCA1 存档
using (var out_ = File.Create("backup.tca"))
RootSpaceImage.Capture(sourceFs, out_, new ImageOptions
{
Compression = ImageCompression.Zstd, // None(配快道零拷贝)/ ZLib / Zstd
QuietSource = true, // ★ 自动进维护门闩——静默快照
});
// 还原:存档 → 任何空根空间(目标必须为空,非空抛 AlreadyExists)
using (var in_ = File.OpenRead("backup.tca"))
RootSpaceImage.Restore(in_, targetFs, new ImageOptions { VerifyChecksums = true });
// 直转:任意两文件系统(能力位自动路由——虚拟↔虚拟走字节快道,其余走 TCA1)
var summary = RootSpaceImage.Transfer(sourceFs, targetFs, options);
启动的四种形态(同一份存档,按需选择落点):
| 启动形态 | 落点 | 一句话 |
|---|---|---|
| 测试环境复活 | 新内存文件系统 | CI/调试无需本地盘启动——内容/Extra/稀疏全保真 |
| 落盘部署 | 本地文件系统目录 | 解档为宿主可直接查看的目录树 |
| 制成存档卷 | 虚拟文件系统 | 还原进 .tier = 单工件(存档即活卷——可只读挂载检视/抽取) |
| 云归档 | 网络文件系统 | 存档上云(TCA1 + 压缩) |
保真清单:目录树全结构、文件内容(逐帧 CRC)、稀疏洞边界、unwritten 预分配(PreallocateSize
语义重建——物理预留 + 读零 + 写转换)、FileExtra(≤1536B)。
⚠️ 压缩会杀死文件→文件零拷贝(必须过用户态);采集前静默责任:QuietSource=true 只挡 fs 层写——
消费者自己的后台任务先自行收敛再采集。
6. 想深入?指路
| 想懂什么 | 去哪 |
|---|---|
| 网络文件系统(差异表/staging/Flush 编排/fencing) | network-file-system.md 与源码 IO/Remote/ |
| S3 协议(SigV4/厂商矩阵/COS vhost/chunked 直传) | ../TC.Tier.Core.IO.S3/docs/s3-protocol.md |
| 虚拟文件系统(TierVolume 深指南:多载体/两档 IO/维护门闩/dd 快道) | virtual-file-system.md |
| 能力位矩阵与回退表(逐能力详解) | 源码 IO/ 注释(使用面按能力位决策即可) |
| 常见陷阱(精简版) | 本篇 §7;完整版见源码 IO/ 注释 |
| 性能基线(四类文件系统矩阵/并发扩展) | perf/io-performance.md |
| 采集/还原/迁移管线(TCA1/快道/启动四形态) | IO/Image/RootSpaceImage.cs 注释与设计稿 |
7. 常见陷阱(踩坑概率排序,精简版)
| # | 陷阱 | 要点 |
|---|---|---|
| 1 | DIO 三重对齐 | offset/length/buffer 按 h.RequiredAlignment;对齐 buffer 必须走池 |
| 2 | FileSharing 是 advisory | 仅约束同进程同 fs 实例;跨进程用卷锁 |
| 3 | 删除语义平台差异 | Win 拒删被开文件;POSIX/内存延迟释放——删前先回收句柄 |
| 4 | Append ≠ 强制追加 |
Mode.Append 只初始化游标;多写者追加 = h.Append() |
| 5 | Append 失败预留不回滚 | 回退会吞噬他人预留;异常带 ReservedOffset(失败区间 = 读零洞) |
| 6 | mem Sparse 映射写时差 | 视图写 Flush/Dispose 才写回;实时可见用 Reserved 或 Write 路径 |
| 7 | IMappedSection 必须 Dispose |
独立 fd——不 Dispose = 泄漏;View 越界 = 段错误 |
| 8 | 池归还协议 | Dispose=归还不是关闭;真关闭仅三出口;归还后不得触碰引用 |
| 9 | memfs Default 全局共享 |
测试隔离一律 TierFs.Open("memory:") 私有卷 |
| 10 | mmap × DIO 混用 | 一致性边界由内核决定;视图生命周期与 Move/Delete 互斥由调用方协调 |
| 11 | Dispose × 在途异步 | 先收敛(await/取消)再 Dispose |
完整 18 条(含 memfs 分配模式选型/模拟边界/范围锁 OFD/卷锁成对释放)见源码
IO/注释。