From 975afaf3f0ae55ca3ff2fe6559e3cf959daf9e3d Mon Sep 17 00:00:00 2001 From: 1iaan Date: Tue, 3 Mar 2026 14:24:17 +0000 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E5=9D=97=E5=86=99=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plan/baseline_commands.md | 98 -------- plan/codexplan.md | 194 +++++++++++++++ plan/phase1_validation.md | 168 ------------- plan/phase2_validation.md | 82 ------ plan/plan.md | 369 +++++++++++++-------------- plan/rocksdb_syscall_matrix.md | 87 ------- plan/userplan.md | 108 ++++++++ test/test_single_file_perf.c | 1 + zvfs/zvfs.h | 9 +- zvfs/zvfs.old/Makefile | 16 -- zvfs/zvfs.old/zvfs.c | 442 --------------------------------- zvfs/zvfs.old/zvfs.json | 17 -- zvfs/zvfs.old/zvfs.old.c | 176 ------------- zvfs/zvfs_hook.c | 221 ++++++++++++++++- 14 files changed, 690 insertions(+), 1298 deletions(-) delete mode 100644 plan/baseline_commands.md create mode 100644 plan/codexplan.md delete mode 100644 plan/phase1_validation.md delete mode 100644 plan/phase2_validation.md delete mode 100644 plan/rocksdb_syscall_matrix.md create mode 100644 plan/userplan.md delete mode 100755 zvfs/zvfs.old/Makefile delete mode 100755 zvfs/zvfs.old/zvfs.c delete mode 100755 zvfs/zvfs.old/zvfs.json delete mode 100755 zvfs/zvfs.old/zvfs.old.c diff --git a/plan/baseline_commands.md b/plan/baseline_commands.md deleted file mode 100644 index 49da87c..0000000 --- a/plan/baseline_commands.md +++ /dev/null @@ -1,98 +0,0 @@ -# Phase 0 - 基线命令与验证记录 - -- 日期: 2026-03-02 -- 主机: `ubuntu`(本地开发机) -- db_bench 路径: `/home/lian/env/rocksdb-test/db_bench` -- LD_PRELOAD 库: `/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so` - -## 1) 成功基线(不启用 LD_PRELOAD) - -### 命令 -```bash -rm -rf /tmp/rdb_phase0_plain && \ -strace -f -qq -o /tmp/phase0_plain.strace \ - -e trace=%file,%desc,fsync,fdatasync,ftruncate,fallocate,pread64,pwrite64,lseek,rename,renameat,renameat2 \ - /home/lian/env/rocksdb-test/db_bench \ - --benchmarks=fillseq \ - --db=/tmp/rdb_phase0_plain \ - --num=5000 \ - --value_size=128 \ - --threads=1 \ - --compression_type=none \ - --stats_interval_seconds=0 -``` - -### 结果摘要 -- 命令退出码: `0` -- db_bench 输出: `fillseq ... 5000 operations`(成功) -- errno 分布(trace 全局): - - `ENOENT x11` - - `EEXIST x9` -- 关键 syscall(全局计数): - - `openat 91`, `fcntl 82`, `pread64 8`, `fsync 10`, `fdatasync 10`, `ftruncate 5`, `fallocate 5`, `rename 9` -- DB 路径相关 syscall(`/tmp/rdb_phase0_plain`): - - `openat 61`, `mkdir 12`, `access 10`, `rename 9`, `unlink 12`, `rmdir 2` - -## 2) 失败分支(启用 LD_PRELOAD,/zvfs 路径) - -### 命令 -```bash -strace -f -qq -o /tmp/phase0_preload.strace \ - -e trace=%file,%desc,fsync,fdatasync,ftruncate,fallocate,pread64,pwrite64,lseek,rename,renameat,renameat2 \ - env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so \ - /home/lian/env/rocksdb-test/db_bench \ - --benchmarks=fillseq \ - --db=/zvfs/rdb_phase0_preload \ - --num=1000 \ - --value_size=128 \ - --threads=1 \ - --compression_type=none \ - --stats_interval_seconds=0 -``` - -### 结果摘要 -- 命令退出码: `1` -- 关键报错: - - `Cannot use IOVA as 'PA'` - - `Failed to initialize DPDK` - - `open error: ... While mkdir if missing: /zvfs/rdb_phase0_preload: No such file or directory` -- `/zvfs/rdb_phase0_preload` 相关错误码: - - `ENOENT x4` -- 直接证据(trace): - - `openat(..., "/zvfs/rdb_phase0_preload", ... O_DIRECTORY) = -1 ENOENT` - - `mkdir("/zvfs/rdb_phase0_preload", 0755) = -1 ENOENT` - -## 3) 提取统计的复用命令 - -### 3.1 syscall 频次(全局) -```bash -sed -E 's/^\[pid +[0-9]+\] +//; s/^[0-9]+ +//' /tmp/phase0_plain.strace \ - | grep -oP '^[a-zA-Z_][a-zA-Z0-9_]*(?=\()' \ - | sort | uniq -c | sort -nr -``` - -### 3.2 errno 分布 -```bash -grep -oP '= -1 [A-Z0-9]+' /tmp/phase0_plain.strace | awk '{print $3}' | sort | uniq -c | sort -nr -grep -oP '= -1 [A-Z0-9]+' /tmp/phase0_preload.strace | awk '{print $3}' | sort | uniq -c | sort -nr -``` - -### 3.3 DB 路径相关 syscall -```bash -grep '/tmp/rdb_phase0_plain' /tmp/phase0_plain.strace \ - | sed -E 's/^\[pid +[0-9]+\] +//; s/^[0-9]+ +//' \ - | grep -oP '^[a-zA-Z_][a-zA-Z0-9_]*(?=\()' \ - | sort | uniq -c | sort -nr -``` - -## 4) Phase0 正确性验证清单(执行记录) - -- [x] 每个“phase2 必须实现 syscall”都在矩阵中给出复现证据或来源。 -- [x] 基线命令可重复执行(成功路径与失败路径各 1 组)。 -- [x] 输出包含 syscall 统计与错误码分布。 -- [x] 验证记录已写入 `plan/baseline_commands.md`。 - -## 5) 已知限制(phase1 前需处理) - -1. 当前环境中 SPDK 初始化失败(IOVA/PA),导致无法在本机直接完成完整 `/zvfs` I/O 链路验证。 -2. phase0 主要覆盖 `fillseq`;phase1/phase2 开始前应补 `readrandom/overwrite` 的 syscall 采样。 diff --git a/plan/codexplan.md b/plan/codexplan.md new file mode 100644 index 0000000..2c94c5d --- /dev/null +++ b/plan/codexplan.md @@ -0,0 +1,194 @@ +# ZVFS 高性能框架设计(修订版) + +## 0. 当前实现进展(2026-03-03) + +- 已落地: + - stale blob 自愈(open/create/unlink/rename 路径) + - hook 层小写合并(per-fd writeback buffer,默认 128KB)+ 关键系统调用前 flush +- 仍待重点优化: + - 小块读路径仍是“同步提交 + 单请求往返 + 拷贝返回”,延迟和吞吐偏弱 + +## 1. 现状代码中的关键问题(先于方案) + +基于 `zvfs.c`、`zvfs.h`、`zvfs_hook.c`,当前主要瓶颈和风险如下: + +1. **单全局执行上下文串行化** + - 所有 IO 都通过 `global_thread` + `waiter()` 同步等待,天然把多线程请求串到一个 SPDK thread。 + - `zvfs_t` 里只有一个 `channel`,读写都走这一个 channel,无法利用多核并行。 + +2. **等待模型是忙轮询,CPU 成本高** + - `waiter()` 用紧循环 `spdk_thread_poll()`,没有阻塞等待/退避策略。 + - 在高并发小 IO 下,系统容易进入“高 CPU + 低有效 QD”。 + +3. **全局元数据无并发保护** + - `dirents/fd_table/g_dirs/g_dirfd_table/open_count/file_size` 读写没有统一锁。 + - hook 层是多线程入口,当前实现有明显竞态和可见性问题。 + +4. **持久化与语义不完整** + - `fsync/fdatasync/sync_file_range` 对 zvfs fd 基本直接返回 0,和数据库预期不一致。 + - `meta_load()` 只读固定 4KB 文本,规模稍大就截断;`meta_save()` 也无崩溃一致性保证。 + +5. **数据路径的放大和额外开销** + - 小块随机写依赖 read-modify-write;无写回缓存、无批量提交、无 IO 合并。 + - per-file `dma_buf` 增长时可能反复 realloc,缺少池化和复用策略。 + +6. **可扩展性不足** + - `dirent_find/fd_alloc` 等是线性扫描。 + - 元数据、目录结构、fd 分配都偏“单点共享结构”,随着文件数/线程数增长会抖动。 + +--- + +## 2. 对 userplan.md 的补全与修正 + +`plan/userplan.md` 的方向(TLS + per-thread channel + 缩小全局锁)是正确的,但有几个需要补全的点: + +1. **“每个 pthread 一个 spdk_thread”要可配置** + - 对 MySQL 这类线程数可能很大的进程,严格 1:1 会导致线程对象和 channel 爆炸。 + - 建议改为:默认“线程绑定 worker 池(N:M)”,支持配置成 1:1 调试模式。 + +2. **需要明确“文件句柄跨线程访问”的所有权规则** + - 同一 fd 可能被不同 pthread 使用,必须定义 offset、cache、flush 的同步策略。 + +3. **batch poll 需要配套“提交队列 + 背压 + 超时”** + - 仅有 `pending_queue` 不够,必须定义入队失败/队列满/超时处理。 + +4. **必须补上 fsync/fdatasync 的严格语义** + - 尤其面向数据库:fsync 成功后应保证数据页 + 必要元数据已持久化。 + +5. **元数据持久化需要从“文本快照”升级为“日志+检查点”** + - 否则崩溃恢复和规模都不可靠。 + +--- + +## 3. 新框架设计(面向高性能与可重入改造) + +### 3.1 分层与职责 + +- **Control Plane(全局)** + - 管理 mount/unmount、命名空间、inode 元数据、fd 表、恢复日志。 + - 低频操作(open/create/unlink/rename/mkdir/rmdir)在此层处理。 + +- **Data Plane(worker)** + - 处理 read/pread/write/pwrite/fsync 的数据 IO。 + - 每个 worker 持有:`spdk_thread + io_channel + submission_queue + completion_queue`。 + +- **Persistence Plane(元数据持久化)** + - 元数据 WAL(append-only)+ 周期 checkpoint。 + - 保障崩溃恢复和 fsync 语义。 + +### 3.2 全局运行时结构 + +```c +typedef struct { + // init/mount 生命周期 + pthread_once_t init_once; + pthread_mutex_t mount_mu; + _Atomic int mount_state; // UNINIT/INITING/READY/FAILED/STOPPING + + // core spdk objects + struct spdk_blob_store *bs; + struct spdk_bs_dev *bs_dev; + + // metadata indexes + pthread_rwlock_t inode_rwlock; + inode_table_t *inode_by_path; // hash map: path -> inode + inode_table_t *inode_by_blobid; // hash map: blobid -> inode + + pthread_rwlock_t fd_rwlock; + fd_table_t *fd_table; // pseudo fd -> file handle + + // durability + meta_journal_t *journal; // WAL + checkpoint + + // worker routing + worker_pool_t *workers; // configurable N workers +} zvfs_runtime_t; +``` + +### 3.3 worker 模型(建议默认 N:M,可切 1:1) + +- 默认:`worker_count = min(online_cpu, ZVFS_IO_WORKERS)`。 +- 线程第一次进入时做 TLS 绑定:`pthread_id -> worker_id`(固定绑定,减少迁移)。 +- 每个 worker 独占一个 io_channel,避免全局 channel 争用。 +- 等待机制:优先 `eventfd/futex + poll` 混合,避免纯忙轮询。 + +> 说明:若用户确认线程数有限,可配置 `ZVFS_WORKER_MODE=THREAD_LOCAL` 切 1:1,以追求极致低延迟。 + +### 3.4 元数据模型 + +- `inode`(文件级共享对象) + - `blob_id, logical_size, allocated_clusters, link/open_ref, flags` + - 每 inode 一把细粒度锁(mutex/spin + 原子字段)。 +- `file handle`(open 实例) + - `inode*`, `flags`, `current_offset`, `handle-local state`。 +- 路径索引与 blob 索引用哈希表替代线性数组。 +- 目录树从 `g_dirs[]` 升级为前缀树或 hash+parent 索引,避免全表扫描。 + +### 3.5 IO 路径设计 + +#### Read/Pread +- 快路径:命中页缓存(clean/dirty)直接拷贝。 +- 慢路径:提交到绑定 worker。 +- 对齐大读支持直接 DMA 到用户对齐缓冲(满足约束时)。 + +#### Write/Pwrite +- 小块随机写:写入 per-inode 页缓存(4KB 粒度),标记 dirty。 +- 大块或顺序写:绕过缓存直写(或写穿策略),减少二次拷贝。 +- 扩容策略:按 chunk 预分配(例如 1~8MB)减少 `resize + sync_md` 频率。 +- flush 策略: + - 后台刷脏(阈值/时间) + - 前台 fsync 强制刷 + - 合并连续页为 writev/batch IO + +### 3.6 fsync/fdatasync 语义(数据库场景) + +- `fdatasync(fd)`: + 1) 刷新该 fd 对应 inode 的脏数据页; + 2) 若发生扩容,确保 blob 元数据同步完成; + 3) 返回前确认提交完成。 +- `fsync(fd)`: + - 在 `fdatasync` 基础上,额外保证需要的命名空间/元数据日志落盘(如 size、rename 可见性)。 + +### 3.7 崩溃一致性与恢复 + +- `meta_journal.log`(append-only,带 magic/version/CRC/seq)。 +- 操作记录:`CREATE/UNLINK/RENAME/TRUNCATE/SIZE_UPDATE/ALLOC_UPDATE`。 +- 启动恢复:`checkpoint -> replay WAL`。 +- 周期 checkpoint(按时间或日志大小触发),避免恢复时间无限增长。 + +### 3.8 锁策略与死锁规约 + +- 固定锁顺序:`fd_table lock -> inode lock -> journal lock`。 +- IO 快路径不拿全局写锁。 +- 元数据读多写少:读写锁 + inode 细粒度锁组合。 + +### 3.9 可观测与调优 + +- 统计项(至少): + - read/write IOPS、带宽、P50/P99 延迟 + - cache hit ratio、dirty page 数 + - flush 次数、merge 比例、resize 次数 + - queue depth、排队延迟 +- debug 开关: + - `ZVFS_TRACE_IO=1` + - `ZVFS_TRACE_META=1` + - `ZVFS_WORKER_MODE`, `ZVFS_IO_WORKERS` + +--- + +## 4. 关键行为约束(必须保持) + +1. POSIX 语义不回退:`openat/rename/unlink/ftruncate/fstat/fsync` 的错误码与行为保持一致或更严格。 +2. 在无 root 环境下可跑功能测试(至少支持 Malloc bdev 或已有可用 SPDK 配置)。 +3. 旧接口兼容:外部仍通过 `LD_PRELOAD=.../libzvfs.so` 使用。 +4. 改造过程可分阶段落地,任何阶段都可独立编译、回归、继续下一阶段。 + +--- + +## 5. 性能目标(建议) + +- 与当前实现相比: + - 多线程随机写 IOPS 提升 >= 2x(4~16 线程场景) + - P99 延迟下降 >= 30% + - CPU busy-poll 占比显著下降(可通过 perf/top 观测) +- `test_single_file_perf`、`test_single_file_random_perf` 在同配置下持续稳定,无明显长尾抖动。 diff --git a/plan/phase1_validation.md b/plan/phase1_validation.md deleted file mode 100644 index 19ab4be..0000000 --- a/plan/phase1_validation.md +++ /dev/null @@ -1,168 +0,0 @@ -# Phase 1 验证方案(用户执行) - -> 背景:本轮已完成 phase1 代码改造,但当前代理环境无法在 root + SPDK 运行条件下完成端到端验证。下面步骤请你在本机执行。 - -## 0) 目标 - -验证 3 件事: -1. `zvfs_io_req` 解耦后,`read/write` 旧路径行为不回退。 -2. 新增 `zvfs_pread/zvfs_pwrite` API 能正确处理 offset I/O。 -3. 构建产物 `libzvfs.so` 可正常编译链接。 - ---- - -## 1) 构建验证 - -```bash -cd /home/lian/share/10.1-spdk/zvfs -make -C zvfs -j -``` - -期望: -- 编译成功,无报错。 -- 生成 `/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so`。 - ---- - -## 2) Phase1 新 API 验证(不依赖 LD_PRELOAD hook) - -### 2.1 生成临时测试程序 - -```bash -cat >/tmp/phase1_api_check.c <<'EOF' -#include -#include -#include -#include "zvfs.h" - -static int expect_eq(const char *name, const void *got, const void *exp, size_t n) { - if (memcmp(got, exp, n) != 0) { - fprintf(stderr, "[FAIL] %s mismatch\n", name); - return -1; - } - printf("[OK] %s\n", name); - return 0; -} - -int main(void) { - int rc = 1; - int mounted = 0; - int created = 0; - if (zvfs_env_setup() != 0) { - fprintf(stderr, "zvfs_env_setup failed\n"); - return rc; - } - - zvfs_t *fs = calloc(1, sizeof(*fs)); - zvfs_file_t *file = calloc(1, sizeof(*file)); - zvfs_dirent_t *dirent = calloc(1, sizeof(*dirent)); - if (!fs || !file || !dirent) { - rc = 2; - goto out; - } - - if (!zvfs_mount(fs)) { - fprintf(stderr, "zvfs_mount failed\n"); - rc = 3; - goto out; - } - mounted = 1; - - file->fs = fs; - file->dirent = dirent; - if (!zvfs_create(file)) { - fprintf(stderr, "zvfs_create failed\n"); - rc = 4; - goto out; - } - created = 1; - - /* 验证 pwrite + pread 的 offset 语义 */ - const char *a = "AAAA"; - const char *b = "BBBB"; - if (zvfs_pwrite(file, (const uint8_t *)a, 4, 0) != 4) { rc = 5; goto out; } - if (zvfs_pwrite(file, (const uint8_t *)b, 4, 8) != 4) { rc = 6; goto out; } - - uint8_t got[12] = {0}; - uint8_t exp[12] = {'A','A','A','A',0,0,0,0,'B','B','B','B'}; - if (zvfs_pread(file, got, sizeof(got), 0) != 12) { rc = 7; goto out; } - if (expect_eq("pread/pwrite-hole", got, exp, sizeof(exp)) != 0) { rc = 8; goto out; } - - /* 验证旧 read/write 顺序语义未回退 */ - file->current_offset = 0; - const char *c = "CCCC"; - if (zvfs_write(file, (const uint8_t *)c, 4) != 4) { rc = 9; goto out; } - file->current_offset = 0; - uint8_t got2[4] = {0}; - if (zvfs_read(file, got2, sizeof(got2)) != 4) { rc = 10; goto out; } - if (expect_eq("read/write-seq", got2, c, 4) != 0) { rc = 11; goto out; } - - rc = 0; - printf("[PASS] phase1_api_check\n"); - -out: - if (created) { - (void)zvfs_close(file); - (void)zvfs_delete(file); - } - if (mounted) { - (void)zvfs_umount(fs); - } - - free(dirent); - free(file); - free(fs); - return rc; -} -EOF -``` - -### 2.2 编译并运行 - -```bash -gcc -O2 -Wall -Wextra -std=gnu11 \ - -I/home/lian/share/10.1-spdk/zvfs/zvfs \ - -I/home/lian/share/10.1-spdk/zvfs/spdk/include \ - -o /tmp/phase1_api_check /tmp/phase1_api_check.c \ - /home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so -ldl - -sudo /tmp/phase1_api_check -``` - -期望: -- 输出包含: - - `[OK] pread/pwrite-hole` - - `[OK] read/write-seq` - - `[PASS] phase1_api_check` - ---- - -## 3) 旧 POSIX 路径回归(LD_PRELOAD) - -```bash -cd /home/lian/share/10.1-spdk/zvfs -make -C test -j - -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_basic /zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_lseek /zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_two_files /zvfs -``` - -期望: -- `test_basic`、`test_lseek`、`test_two_files` 通过。 -- 无崩溃、无明显数据错乱。 - -若出现一次性 `zvfs_mount failed`,建议先确保没有残留测试进程,再重跑一次验证命令。 - ---- - -## 4) 验证完成后回填 - -请把验证结果回填到 `plan/plan.md` 的 phase1 区域: -- `### 阶段验收` -- `### 正确性验证方案` - -若任何一项失败,请附上: -1. 命令; -2. 错误输出; -3. 是否可稳定复现。 diff --git a/plan/phase2_validation.md b/plan/phase2_validation.md deleted file mode 100644 index 0e8271e..0000000 --- a/plan/phase2_validation.md +++ /dev/null @@ -1,82 +0,0 @@ -# Phase 2 验证方案(用户执行) - -目标:验证 phase2 新增 hook 与 POSIX 语义(openat/pread/pwrite/fsync/ftruncate/stat/rename 等)。 - -## 1) 构建 - -```bash -cd /home/lian/share/10.1-spdk/zvfs -make -C zvfs -j -make -C test -j -``` - -## 2) 关键回归(LD_PRELOAD) - -```bash -cd /home/lian/share/10.1-spdk/zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_basic /zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_lseek /zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_two_files /zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs -``` - -期望: -- 以上 4 个测试全部 PASSED。 -- `test_phase2_posix` 会覆盖: - - `mkdir/openat/close/rmdir` - - `pread/pwrite` + 稀疏洞校验 - - `fsync/fdatasync` - - `ftruncate` - - `fstat/stat/access` - - `rename` - - `O_EXCL` 与只读 fd 上 `write` 的 errno - -## 3) db_bench 最小验证 - -```bash -cd /home/lian/share/10.1-spdk/zvfs -sudo env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so \ - /home/lian/env/rocksdb-test/db_bench \ - --benchmarks=fillseq \ - --db=/zvfs/rdb_phase2 \ - --num=200000 \ - --value_size=128 \ - --threads=1 \ - --compression_type=none \ - --stats_interval_seconds=5 - - -sudo /home/lian/env/rocksdb-test/db_bench \ - --benchmarks=fillseq \ - --db=/tmp/rdb_native \ - --num=200000 \ - --value_size=128 \ - --threads=1 \ - --compression_type=none \ - --stats_interval_seconds=5 -``` - -期望: -- 不出现 `Function not implemented` -- 不出现 `Bad file descriptor` -- 不出现 `While mkdir if missing` 相关 `ENOENT` - -## 4) 失败场景建议(可选) - -用 `strace` 辅助确认关键 syscall 已被接管: - -```bash -sudo strace -f -qq -o /tmp/phase2_check.strace \ - -e trace=%file,%desc,fsync,fdatasync,ftruncate,pread64,pwrite64,rename,fcntl \ - env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs -``` - -看点: -- `openat("/zvfs/...")` 不再因目录缺失直接失败 -- `rename`/`ftruncate`/`pread64`/`fdatasync` 调用链完整 - -## 5) 回填要求 - -执行后请把结果回填到 `plan/plan.md` 的 phase2 区域: -- `### 阶段验收` -- `### 正确性验证方案` diff --git a/plan/plan.md b/plan/plan.md index 281e69a..3789e7a 100644 --- a/plan/plan.md +++ b/plan/plan.md @@ -1,249 +1,216 @@ -# ZVFS -> RocksDB (LD_PRELOAD) 分阶段改造计划(可重入) +# ZVFS 分阶段改造计划(可重入,用户验收版) -本文档是给后续编码代理(我)使用的执行计划。目标是:在不破坏现有功能的前提下,逐步把 `zvfs.c + zvfs_hook.c` 改造成可支撑 RocksDB 的 LD_PRELOAD 存储层。 +> 目标:把当前实现改造成可并发扩展、高性能且语义完整的架构。 +> 约束:我无法使用 root,所有阶段验收由你执行。 + +## 通用约定(所有阶段) + +- 建议先记录基线:`git rev-parse --short HEAD`。 +- 每阶段都保持“可编译 + 可回归”。 +- 每阶段完成后打一个里程碑 tag(例如 `phase1-done`),中断后可从最近 tag 继续。 +- 验收命令默认: + +```bash +make -C zvfs -j4 +make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +``` + +## 已落地变更(2026-03-03) + +1. **stale blob 自愈修复(已完成)** + - `open(O_CREAT)` 遇到元数据引用失效 blob 时自动重建并回写元数据。 + - `unlink/close(rename 覆盖)` 删除失效 blob 时容忍 `ENOENT/EINVAL`,避免误报 `EIO`。 + +2. **小块写合并(已完成)** + - hook 层新增 per-fd writeback buffer(默认 128KB),连续小写先合并再 `pwrite`。 + - 在 `read/pread/lseek/fsync/fdatasync/close/ftruncate/fallocate/unlink/rename/sync_file_range` 前补齐 flush,保证可见性。 + +3. **当前观察** + - 小块写已提升,但小块读仍偏低;读优化作为后续阶段重点。 --- -## 0. 使用规则(可重入协议) +## Phase 0:基线与护栏 -每次开始新一轮开发时,严格按以下顺序执行: +### 要做的事情 +1. 固化当前行为基线:功能、性能、CPU 占用。 +2. 在代码中加入轻量统计框架(计数器/延迟桶/开关),不改变行为。 +3. 增加最小并发回归入口(并行跑现有测试)。 -1. 读取本文件,定位“当前阶段”和“未完成任务”。 -2. 先做“状态核对”: - - 代码是否已存在同名结构/函数; - - 测试是否已覆盖该阶段验收项; - - 若已完成则跳过,不重复改动(幂等)。 -3. 只推进一个阶段内的最小闭环,不跨阶段大改。 -4. 每完成一个任务,立即更新本文件: - - 勾选完成项; - - 记录关键变更文件; - - 记录剩余风险; - - 记录本阶段“正确性验证”执行结果。 -5. 若发现与计划冲突的现实约束(SPDK限制/接口差异/语义冲突),先在“变更记录”追加说明,再调整后续子任务,不直接删原计划。 -6. 非代码文档统一写入 `plan/` 目录(用户约束)。 +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs +``` + +### 通过标准 +- 功能测试通过。 +- 有一份可复用的“基线性能记录”(IOPS/BW/延迟)。 + +### 可重入说明 +- 仅增量加观测代码,可重复执行,不影响后续阶段。 --- -## 1. 总目标与约束 +## Phase 1:全局运行时与并发安全 -### 1.1 总目标 -- 支持 RocksDB 关键 I/O 路径(优先 db_bench 可运行并稳定)。 -- 提供正确 POSIX 语义(至少覆盖 RocksDB 依赖子集)。 -- 从单线程串行模型演进到可并行数据面。 -- 保证崩溃后一致性(元数据持久化可恢复)。 +### 要做的事情 +1. 引入 `zvfs_runtime_t`,统一管理 mount/init 状态与全局资源。 +2. 用 `pthread_once + mount mutex` 保护初始化/挂载过程。 +3. 给 inode/path/fd/dirs 操作补齐锁(rwlock + 细粒度 mutex)。 +4. 保持接口不变:`open/read/write/...` 行为兼容。 -### 1.2 当前主要问题(已确认) -- 单线程 + busy wait + 全局串行,队列深度近似 1。 -- hook 覆盖不足,缺少 pread/pwrite/fsync/ftruncate/openat/stat 等。 -- I/O 参数耦合在 `zvfs_file_t`,不利于 pread/pwrite 和并发。 -- 全局状态无并发保护(`g_fs/fd_table/dirents/open_count` 等)。 -- open/write/close/unlink errno 与 POSIX 语义不完整。 -- 元数据依赖宿主文本文件,容量小、非原子、不可恢复。 -- 配置路径硬编码,部署迁移性差。 +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +for i in $(seq 1 8); do + env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_dual_open_same_file /zvfs & +done +wait +``` -### 1.3 强制要求(新增) -- 每个阶段都必须定义“正确性验证方案”,且在阶段结束前执行并记录结果。 -- 未完成正确性验证,不得将该阶段标记为完成。 +### 通过标准 +- 无崩溃/死锁。 +- 并发场景不出现随机 EBADF/ENOENT/元数据错乱。 + +### 可重入说明 +- 锁与 runtime 框架可独立提交;若中断,重新进入本阶段不会破坏状态。 --- -## 2. 阶段总览 +## Phase 2:Worker 化 IO 通路(替换单 global_thread) -- 阶段0:基线与兼容清单(先知道 RocksDB 真实需要什么) -- 阶段1:架构解耦(file vs io_req) -- 阶段2:补齐关键 hook 与 POSIX 语义 -- 阶段3:并发模型升级(控制面/数据面分离) -- 阶段4:元数据持久化重构(super blob / 日志) -- 阶段5:性能与稳定性验收 +### 要做的事情 +1. 实现 worker 池(默认 N:M,支持配置 1:1)。 +2. 每 worker 持有独立 `spdk_thread + io_channel`。 +3. read/write/pread/pwrite 路径改为“提交到绑定 worker 执行”。 +4. 保留同步 POSIX 语义,但去掉全局单线程瓶颈。 + +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +for i in $(seq 1 4); do + env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs & +done +wait +``` + +### 通过标准 +- 功能与 Phase 1 一致。 +- 并发压测吞吐明显高于基线(目标 >= 1.5x,先达成趋势)。 + +### 可重入说明 +- worker 与旧路径可通过编译开关共存,出现问题可快速切回旧路径继续调试。 --- -## 3. 详细阶段计划 +## Phase 3:完成等待机制与批处理 -## 阶段0:基线与兼容清单 +### 要做的事情 +1. 用“提交队列 + 完成通知”替换纯 busy-poll `waiter`。 +2. 增加批量 poll 与背压(队列满、超时、错误传播)。 +3. 补齐延迟与队列深度指标,定位长尾。 +4. 引入读路径流水线(允许并发 in-flight read),把有效 QD 从 1 提升到可配置值。 -### 目标 -- 建立“RocksDB syscall 最小集合”和“当前实现缺口表”。 +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs +``` -### 任务 -- [x] 用 `strace` 或等效方式采集 `db_bench` syscall(含失败分支)。 -- [x] 形成兼容矩阵:必须实现 / 可降级透传 / 暂不支持。 -- [x] 固化第一批验收用例(最小 db_bench 参数 + 现有单测集合)。 +### 通过标准 +- 在同等负载下 CPU 空转显著下降。 +- P99 延迟较 Phase 2 收敛(无明显长尾恶化)。 -### 交付物 -- [x] `plan/rocksdb_syscall_matrix.md` -- [x] `plan/baseline_commands.md` - -### 阶段验收 -- [x] 能明确列出阶段2必须完成的 hook 列表。 - -### 正确性验证方案 -- [x] 验证矩阵中的每个“必须实现 syscall”都有最小复现样例(命令或小程序)。 -- [x] 基线命令可重复执行,输出包含 syscall 统计与错误码分布。 -- [x] 验证结果记录到 `plan/baseline_commands.md`(含日期和环境信息)。 +### 可重入说明 +- 队列与等待层可单独演进;可先只替换 read,再替换 write。 --- -## 阶段1:架构解耦(关键) +## Phase 4:页缓存与写回合并 -### 目标 -- 把 I/O 请求参数从 `zvfs_file_t` 中拆出,建立请求对象,先打通 pread/pwrite 内核路径。 +### 要做的事情 +1. 引入 per-inode 4KB 页缓存(dirty/clean 状态)。 +2. 小写走 cache + 延迟刷盘,大写/顺序写支持直写或批量写。 +3. 引入 flush 策略:阈值、定时、fsync 强制。 +4. 缩减 `resize + sync_md` 频率(chunk 预分配)。 +5. 读性能专项: + - 增加顺序读 readahead(如 128KB~1MB 窗口自适应)。 + - 对齐读支持“直接读到用户缓冲”快路径,减少一次 memcpy。 + - 引入 clean page cache(读热点复用,避免重复 blob read)。 -### 任务 -- [x] 新增 `zvfs_io_req`(字段至少:op/buf/len/offset/flags/result/errno/finished)。 -- [x] 重构 `zvfs_read/zvfs_write` 为基于 `zvfs_io_req` 的通用入口。 -- [x] 实现内部 `zvfs_pread_internal/zvfs_pwrite_internal`,不改 hook 先可调用。 -- [x] 移除 `zvfs_file_t` 中仅一次请求有效的临时字段(或标记弃用)。 +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_noaligned_perf /zvfs +``` -### 交付物 -- [x] `zvfs/zvfs.h` 新结构与接口 -- [x] `zvfs/zvfs.c` 读写路径重构完成 -- [x] `plan/phase1_validation.md`(用户侧验证步骤) +### 通过标准 +- 功能语义不回退(truncate/sparse/rename/fstat 通过)。 +- 小块随机写吞吐继续提升,写放大降低。 -### 阶段验收 -- [x] 现有 read/write/lseek 测试全通过。 -- [x] 新增 pread/pwrite 单测通过(可先直连接口,不经 hook)。 - -### 正确性验证方案 -- [x] 新旧接口结果一致性校验:同一输入下 `read/write` 与 `pread/pwrite` 数据一致。 -- [x] 边界测试:offset=0、EOF、跨 page、非对齐、空写入。 -- [x] 失败路径测试:非法 fd/参数时返回值与 `errno` 符合预期。 +### 可重入说明 +- cache 可先只支持 write-through,再切 write-back;两步都可单独验收。 --- -## 阶段2:补齐 hook 与 POSIX 语义 +## Phase 5:元数据日志化与 fsync 语义闭环 -### 目标 -- 满足 RocksDB 最小兼容 API 集,保证语义与 errno 正确。 +### 要做的事情 +1. `meta_save/load` 从文本快照升级为 WAL + checkpoint(带 CRC/版本)。 +2. 明确并实现 `fdatasync/fsync` 语义: + - fdatasync 保证数据持久化; + - fsync 额外保证必要元数据持久化。 +3. 补齐崩溃恢复流程(checkpoint + replay)。 -### 首批必须实现 -- [x] `pread/pwrite`(及 `pread64/pwrite64` 视平台符号而定) -- [x] `open/open64/openat` -- [x] `fsync/fdatasync` -- [x] `ftruncate` -- [x] `fstat/stat/lstat`(至少满足 RocksDB 元数据查询) -- [x] `rename`(原子替换语义) +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_phase2_posix /zvfs +# 建议补充一次“异常退出后重启读取”的恢复验证(手工执行) +``` -### 语义修复 -- [x] `open` 支持 `O_TRUNC/O_APPEND/O_EXCL` -- [x] 权限检查(`O_RDONLY/O_WRONLY/O_RDWR`) -- [x] 返回值与 `errno` 映射统一(失败路径不可吞错) +### 通过标准 +- 重启后目录项与文件大小不丢失、不错乱。 +- 数据库关键路径(fsync/fdatasync)语义满足预期。 -### 交付物 -- [x] `zvfs/zvfs_hook.c`(phase2 hook 覆盖与语义修复) -- [x] `test/test_phase2_posix.c`(phase2 POSIX 回归用例) -- [x] `plan/phase2_validation.md`(用户侧验证步骤) - -### 阶段验收 -- [ ] db_bench 基础 workload 可跑通(单线程先行)。 -- [ ] 不支持的 syscall 必须明确透传且行为可解释。 - -### 正确性验证方案 -- [ ] 每个新增 hook 至少 1 个正例 + 1 个反例(权限/参数/不存在文件)。 -- [ ] `open` 语义验证:`O_TRUNC/O_APPEND/O_EXCL` 行为与本地文件系统对齐。 -- [ ] `errno` 对照验证:对关键失败场景做预期值断言。 -- [ ] 跑最小 `db_bench`,确认无 “Function not implemented/Bad file descriptor” 类错误。 +### 可重入说明 +- WAL 与 checkpoint 支持并存迁移;可先双写验证,再切主读路径。 --- -## 阶段3:并发模型升级 +## Phase 6:性能收敛与上线门槛 -### 目标 -- 保持 hook 层阻塞语义,但底层可并行提交处理。 +### 要做的事情 +1. 清理临时开关,保留必要调优参数。 +2. 整理性能报告(与 Phase 0 基线对比)。 +3. 做最终回归矩阵(功能 + 并发 + 性能 + 恢复)。 -### 任务 -- [ ] 设计并实现 `控制面线程 + N个数据面worker`。 -- [ ] 每 worker 使用独立 io_channel。 -- [ ] 引入并发保护:fd_table、dirents、open_count、全局挂载状态。 -- [ ] 修复生命周期竞态(close/unlink 并发、延迟删除)。 +### 用户验收 +```bash +make -C zvfs -j4 && make -C test -j4 +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so make -C test run-test +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_perf /zvfs +env LD_PRELOAD=/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so ./test/bin/test_single_file_random_perf /zvfs +``` -### 阶段验收 -- [ ] 多线程压测下无崩溃/无明显数据错乱。 -- [ ] QD>1 场景吞吐显著高于阶段2。 +### 通过标准 +- 全量功能测试通过。 +- 多线程性能达到 `codexplan.md` 目标(或给出量化偏差与原因)。 -### 正确性验证方案 -- [ ] 多线程读写一致性校验(文件内容 hash 或区块比对)。 -- [ ] 并发场景稳定性:长时间压测无崩溃、无死锁、无句柄泄漏。 -- [ ] 竞态回归:`close/unlink`、双开同文件、并发 append 场景正确。 +### 可重入说明 +- 本阶段仅收敛与验收,不引入架构性变更;可反复执行直到指标稳定。 --- -## 阶段4:元数据持久化重构 +## 附:root 权限与运行建议 -### 目标 -- 去掉宿主文本元数据文件,转向 blobstore 内部可恢复元数据。 - -### 任务 -- [ ] 使用 super blob(或单独 metadata blob)管理目录与 inode-like 信息。 -- [ ] 建立日志或 copy-on-write 更新流程,支持崩溃恢复。 -- [ ] 对 `create/unlink/rename/truncate` 实现原子更新策略。 - -### 阶段验收 -- [ ] 强制中断后重启可恢复一致目录和文件大小信息。 -- [ ] 不再依赖固定绝对路径元数据文件。 - -### 正确性验证方案 -- [ ] 故障注入:在 create/write/rename/truncate 中间点中断后重启验证一致性。 -- [ ] 元数据回放验证:目录项数量、文件大小、blob_id 映射正确。 -- [ ] 对比验证:与中断前快照(或日志)比对差异可解释。 - ---- - -## 阶段5:性能与稳定性验收 - -### 目标 -- 形成“可用 + 可解释 + 可回归”的最终版本。 - -### 任务 -- [ ] buffer 管理优化(池化、减少拷贝、减少重复 preread)。 -- [ ] 完整回归:现有单测 + 新增 hook 语义测试 + db_bench 组合。 -- [ ] 输出性能报告(吞吐、延迟、CPU、错误率)。 - -### 阶段验收 -- [ ] 关键 workload 稳定运行,结果可复现。 - -### 正确性验证方案 -- [ ] 全量回归连续执行至少 3 轮,结果一致且无新增失败。 -- [ ] 性能结果包含波动范围(平均值与离散度),可复现实验命令。 -- [ ] 最终发布前执行一次“从空盘到回归完成”的冷启动验证流程。 - ---- - -## 4. 当前阶段状态 - -- 当前阶段:`阶段2` -- 阶段状态:`pending_user_validation` -- 本轮目标:用户按 `plan/phase2_validation.md` 执行 phase2 正确性验证 - ---- - -## 5. 每轮执行后必须更新的记录 - -## 5.1 变更记录(按时间追加) -- [x] 2026-03-02: 完成 phase0(db_bench syscall 基线 + 失败分支 + 兼容矩阵);文件:`plan/plan.md`、`plan/rocksdb_syscall_matrix.md`、`plan/baseline_commands.md`;风险:当前环境 SPDK 初始化失败(IOVA PA 不可用),需在 phase1 前确定运行环境策略 -- [x] 2026-03-02: 完成 phase1 代码改造(file/io_req 解耦 + 新增 pread/pwrite API);文件:`zvfs/zvfs.h`、`zvfs/zvfs.c`、`plan/plan.md`、`plan/phase1_validation.md`;风险:本轮未在 root+LD_PRELOAD 环境完成端到端验证 -- [x] 2026-03-02: 根据用户反馈修复 phase1 边界问题(preread 失败时将临时缓冲区清零,避免洞区读到脏数据);文件:`zvfs/zvfs.c`、`plan/phase1_validation.md`;风险:仍需用户侧端到端复测确认 -- [x] 2026-03-02: 根据用户复测继续修复 sparse hole 问题(offset>EOF 时清零本次覆盖页内 gap 区间);文件:`zvfs/zvfs.c`、`plan/plan.md`;风险:跨多页大洞写入语义仍需在 phase2 做专项覆盖 -- [x] 2026-03-02: 完成 phase2 代码改造(补齐 openat/pread/pwrite/fsync/ftruncate/stat/rename/fcntl/mkdir/rmdir 等 hook 与关键语义);文件:`zvfs/zvfs_hook.c`、`zvfs/zvfs.h`、`test/test_phase2_posix.c`、`test/Makefile`、`plan/phase2_validation.md`、`plan/plan.md`;风险:尚未在用户环境完成 db_bench 端到端验收 -- [x] 2026-03-02: 根据用户 db_bench 反馈修复目录打开语义(目录允许 O_RDONLY 打开,不再强制要求 O_DIRECTORY);文件:`zvfs/zvfs_hook.c`;风险:仍需用户复测 db_bench -- [x] 2026-03-02: 根据用户 db_bench 反馈补齐 fadvise/fallocate/sync_file_range hook,避免 pseudo-fd 被内核路径误判为 EBADF;文件:`zvfs/zvfs_hook.c`、`zvfs/zvfs.h`;风险:仍需用户复测 db_bench - -## 5.2 风险清单(持续维护) -- [ ] 线程模型改造可能引入 SPDK thread affinity 问题 -- [ ] rename/truncate 语义与 blobstore 能力映射复杂 -- [ ] LD_PRELOAD 多符号拦截顺序可能受 libc/应用实现影响 - -## 5.3 决策记录(ADR-lite) -- [ ] D-001: 为什么选择控制面/数据面分离 -- [ ] D-002: 为什么选择 super blob 元数据格式 -- [ ] D-003: 不支持 syscall 的透传策略与边界 - ---- - -## 6. 完成定义(DoD) - -满足以下条件才可标记“计划完成”: -- [ ] 阶段0~5全部验收项勾选完成 -- [ ] RocksDB db_bench 至少 3 类 workload 稳定通过 -- [ ] 关键 POSIX 语义测试通过并有失败用例说明 -- [ ] 文档包含部署方式、限制项、回归命令 +- 若 NVMe/SPDK 环境需要 root,请在你本机按现有流程执行验收。 +- 若希望无 root 回归,建议补一个 `Malloc` bdev 的 JSON 配置,并将 bdev 名改为可配置(环境变量优先)。 diff --git a/plan/rocksdb_syscall_matrix.md b/plan/rocksdb_syscall_matrix.md deleted file mode 100644 index 7eccf56..0000000 --- a/plan/rocksdb_syscall_matrix.md +++ /dev/null @@ -1,87 +0,0 @@ -# Phase 0 - RocksDB syscall 兼容矩阵 - -- 日期: 2026-03-02 -- 采样对象: `/home/lian/env/rocksdb-test/db_bench` -- 当前 LD_PRELOAD: `/home/lian/share/10.1-spdk/zvfs/zvfs/libzvfs.so` -- 说明: 本矩阵基于 phase0 的 `fillseq` 最小工作负载和失败分支采样;用于定义 phase2 必做 hook 范围。 - -## 1) 采样结论(关键) - -### 1.1 成功基线(不启用 LD_PRELOAD,`--db=/tmp/rdb_phase0_plain`) -关键 syscall 计数(来自 strace 汇总): - -| syscall | count | -|---|---:| -| openat | 91 | -| fcntl | 82 | -| read | 56 | -| fstat | 56 | -| getdents64 | 36 | -| stat | 14 | -| mkdir | 12 | -| unlink | 12 | -| access | 11 | -| fsync | 10 | -| fdatasync | 10 | -| rename | 9 | -| pread64 | 8 | -| ftruncate | 5 | -| fallocate | 5 | -| lseek | 1 | - -路径级(仅匹配 DB 路径)高频调用: -- `openat`, `mkdir`, `access`, `rename`, `unlink`, `rmdir` - -### 1.2 失败分支(启用 LD_PRELOAD,`--db=/zvfs/rdb_phase0_preload`) -观察到的关键失败: -- SPDK 初始化失败:`Cannot use IOVA as PA`,随后 `spdk_env_init` 失败。 -- RocksDB 目录创建路径使用 `openat + mkdir`,对 `/zvfs/rdb_phase0_preload` 返回 `ENOENT`。 -- 与 DB 路径相关错误码分布:`ENOENT x4`。 - -结论:仅 hook `open/read/write/close/unlink/lseek` 无法支撑 RocksDB;至少要覆盖目录/元数据/同步/随机读写相关 syscall。 - -## 2) 当前实现 vs RocksDB 需求 - -当前已实现 hook(`zvfs_hook.c`): -- `open`, `read`, `write`, `close`, `unlink`, `lseek` - -当前缺失但在 phase0 中已观测到(或由 RocksDB 常规路径强依赖): -- `openat/openat64`, `mkdir`, `rmdir` -- `pread64/pwrite64`(`pwrite` 在 fillseq 未显式出现,但 RocksDB 通常依赖) -- `fsync/fdatasync` -- `ftruncate/fallocate` -- `fcntl`(文件锁) -- `stat/fstat/lstat/newfstatat`(含 `statx` 兼容策略) -- `rename`(CURRENT / OPTIONS / MANIFEST 原子更新) -- `access/faccessat`, `unlinkat` - -## 3) Phase 2 必做清单(来自 phase0 证据) - -> 这是“必须实现或必须可正确透传”的最小集合。 - -| API/syscall | 证据来源 | 当前状态 | Phase2 目标 | -|---|---|---|---| -| open/open64 | 现有 hook + RocksDB 文件创建 | 部分支持 | 补齐 flag 语义(`O_TRUNC/O_APPEND/O_EXCL`) | -| openat/openat2 | phase0 strace 高频 | 缺失 | 必须支持 `/zvfs` 路径 | -| close | 已实现 | 支持 | 保持并修复错误传播 | -| read/write | 已实现 | 支持 | 保持 | -| pread64/pwrite64 | phase0 观测到 pread64 | 缺失 | 必须支持 offset I/O | -| fsync/fdatasync | phase0 高频 | 缺失 | 必须支持(至少语义正确) | -| ftruncate | phase0 观测到 | 缺失 | 必须支持 | -| fallocate | phase0 观测到 | 缺失 | 必须支持或明确降级策略 | -| fcntl(F_SETLK等) | phase0 高频 | 缺失 | 必须支持最小锁语义 | -| stat/fstat/lstat/newfstatat | phase0 高频 | 缺失 | 必须支持最小元数据语义 | -| mkdir/rmdir | phase0 路径级高频 | 缺失 | 必须支持 `/zvfs` 目录层 | -| rename | phase0 路径级高频 | 缺失 | 必须支持原子替换语义 | -| unlink/unlinkat | unlink 已有,unlinkat 缺失 | 部分支持 | 补齐 unlinkat | -| access/faccessat | phase0 路径级高频 | 缺失 | 必须支持或一致透传 | - -## 4) 可降级透传(phase2 可先不接管) - -- `getdents64`, `readlink`, `statfs/fstatfs` 可先透传(前提:不作用于 `/zvfs` 或语义可接受)。 -- 若作用于 `/zvfs` 路径,必须在 phase2 给出明确行为(支持或返回可解释错误)。 - -## 5) 风险与备注 - -1. 当前机器环境下 SPDK 初始化存在 IOVA/PA 限制,影响“真实 I/O 路径”验证,需要在 phase1 前先明确运行环境策略。 -2. 仅凭 `fillseq` 不能覆盖全部 syscall;phase2 开始前建议补 `readrandom/overwrite` 采样以确认 `pwrite64` 等调用比例。 diff --git a/plan/userplan.md b/plan/userplan.md new file mode 100644 index 0000000..a5e567c --- /dev/null +++ b/plan/userplan.md @@ -0,0 +1,108 @@ +### 架构目标 +- 通过 LD_PRELOAD hook POSIX 文件操作(open/read/write/pread/pwrite/close/fsync 等),将 MySQL 的数据文件 IO 重定向到 SPDK Blobstore。 +- 最大化性能:绕过内核、利用多核并发、低延迟、小块写合并。 +- 核心原则:**每个 pthread 拥有独立的 SPDK 执行上下文**,全局共享底层存储资源。 + +### 全局资源(进程级别,唯一一份) +- `zvfs_t *g_fs`:文件系统实例,包含: + - `struct spdk_blob_store *bs`:全局 Blobstore(通过 spdk_bs_load/init 创建)。 + - bdev(Nvme0n1 或 Malloc0,通过 JSON 配置加载)。 + - 全局元数据:dirents 数组(zvfs_dirent_t *[])、fd_table(zvfs_file_t *[])、openfd_count。 + - 保护全局元数据的锁:pthread_rwlock_t g_meta_lock(读多写少场景)。 +- 全局初始化标志:`bool g_mounted`、`bool g_env_inited`。 +- pthread_key_t 用于线程本地存储:`g_thread_local_key`(带 destructor)。 + +### 线程本地资源(每个 pthread 独占一份,通过 TLS 实现) +每个 pthread 拥有以下私有状态,存储在结构体 `thread_local_zvfs_t` 中: + +```c +typedef struct { + struct spdk_thread *thread; // 本线程专属的 SPDK thread + struct spdk_io_channel *channel; // 本线程专属的 IO channel(绑定到 g_fs->bs) + TAILQ_HEAD(, io_ctx) pending_queue; // 本线程的 pending IO 队列,用于 batch poll + // 可选扩展: + // struct dma_buf_pool *dma_pool; // per-thread DMA buf 复用池 + // struct page_cache *local_cache; // 如果需要 per-thread cache +} thread_local_zvfs_t; +``` + +- **创建时机**:lazy(第一次 IO 时调用 `get_thread_local()`)。 +- **存储方式**:通过 `pthread_setspecific(g_thread_local_key, tl)` 绑定到当前 pthread。 +- **销毁时机**:pthread 退出时,TLS destructor 自动调用: + - spdk_bs_free_io_channel(channel) + - spdk_thread_exit + poll until exited + spdk_thread_destroy + +### 核心函数:get_thread_local() +```c +thread_local_zvfs_t *get_thread_local(void) { + // 确保 key 已创建(只执行一次) + pthread_once(&g_key_once, init_thread_key); + + thread_local_zvfs_t *tl = pthread_getspecific(g_thread_local_key); + if (tl == NULL) { + tl = calloc(1, sizeof(*tl)); + tl->thread = spdk_thread_create("zvfs_worker", NULL); + tl->channel = spdk_bs_alloc_io_channel(g_fs->bs); + TAILQ_INIT(&tl->pending_queue); + pthread_setspecific(g_thread_local_key, tl); + } + return tl; +} +``` + +### 工作流程(每个 pthread 独立执行) +1. **线程首次进入 IO 操作** + - 调用 `get_thread_local()` → 创建并绑定 thread + channel。 + - 如果 !g_mounted → 调用 zvfs_ensure_mounted()(使用当前 thread 进行 poll 完成 mount)。 + +2. **元数据操作(open/unlink/mkdir/rmdir/rename 等)** + - 加读锁(g_meta_lock)检查/修改全局 dirents、dirs、fd_table。 + - 创建/查找 zvfs_file_t,调用 zvfs_create/zvfs_open(使用当前 thread 同步等待)。 + - 分配伪 fd,记录到全局 fd_table。 + - 释放锁。 + +3. **读操作(read/pread)** + - 获取当前 tl = get_thread_local()。 + - spdk_set_thread(tl->thread)。 + - 如果小读 + cache hit → 直接 memcpy 返回。 + - 否则:创建 io_ctx,加入 tl->pending_queue。 + - 调用 spdk_blob_io_read(..., tl->channel, ...)。 + - 执行 batch_poll(tl, my_ctx): + - while (!my_ctx->done) spdk_thread_poll(tl->thread, 0, 0); + - 从 dma_buf 拷贝到用户 buf。 + +4. **写操作(write/pwrite)** + - 获取 tl。 + - spdk_set_thread(tl->thread)。 + - 如果小写 → patch per-file page cache(dirty),标记 dirty,返回(延迟写)。 + - 如果 cache 满或大写 → flush dirty pages(batch spdk_blob_io_writev,用 tl->channel)。 + - 创建 io_ctx → 加入 pending_queue → submit write → batch_poll。 + +5. **fsync** + - flush per-file dirty cache(batch writev + spdk_blob_sync_md)。 + - 使用当前 tl->thread poll 等待完成。 + +6. **close** + - fsync(flush cache)。 + - zvfs_close(用当前 tl->thread 同步)。 + - 释放 fd(加锁更新全局 fd_table)。 + +### 性能关键机制 +- **独立 poll**:每个 pthread 用自己的 spdk_thread 独立 poll,无跨线程消息。 +- **batch poll**:一个 poll 循环可完成多个 pending IO,提升有效 QD。 +- **page cache**:per-file 4K dirty pages(hashmap),合并小写,减少 write amplification。 +- **channel per-thread**:避免全局 channel 争用,每个线程独立提交 IO。 +- **最小全局锁**:只在元数据修改时短时加锁(rwlock),IO 操作无锁。 + +### 资源所有权总结表 + +| 资源类型 | 所有权 | 数量 | 创建时机 | 销毁时机 | +|----------------------|--------------|------------|------------------------|------------------------------| +| bdev | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount | +| blobstore (bs) | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount | +| zvfs_t / g_fs | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount + free | +| dirents / fd_table | 全局 | 1 | meta_load | zvfs_umount + free | +| spdk_thread | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) | +| io_channel | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) | +| pending_queue | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出 | +| page cache | per-file | per open fd| open 时 lazy | close 时 flush + free | diff --git a/test/test_single_file_perf.c b/test/test_single_file_perf.c index 8258d62..eceae4a 100755 --- a/test/test_single_file_perf.c +++ b/test/test_single_file_perf.c @@ -3,6 +3,7 @@ static int test_single_file_perf(const char *path) { size_t io_size = 128 * 1024; + // size_t io_size = 4096; size_t max_size = 2ULL * 1024 * 1024 * 1024; size_t max_count = max_size / io_size; int test_sec = 10; diff --git a/zvfs/zvfs.h b/zvfs/zvfs.h index 66f5d76..4b5e8ac 100755 --- a/zvfs/zvfs.h +++ b/zvfs/zvfs.h @@ -68,10 +68,17 @@ typedef struct zvfs_file_s { int flags; // O_RDONLY / O_RDWR / O_CREAT 等 int pseudo_fd; - /* 临时DMA缓冲区(可选:每个file一个,避免每次malloc) */ + /* 临时DMA缓冲区(可选:每个file一个,避免每次malloc) */ void *dma_buf; uint64_t dma_buf_size; + /* Small-write coalescing buffer in hook layer. */ + uint8_t *wb_buf; + uint64_t wb_base; + size_t wb_len; + size_t wb_cap; + bool wb_valid; + int op_errno; bool finished; } zvfs_file_t; diff --git a/zvfs/zvfs.old/Makefile b/zvfs/zvfs.old/Makefile deleted file mode 100755 index f609ed0..0000000 --- a/zvfs/zvfs.old/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (C) 2017 Intel Corporation -# All rights reserved. -# - -SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../spdk) -include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk - -APP = zvfs - -C_SRCS := zvfs.c - -SPDK_LIB_LIST = $(ALL_MODULES_LIST) event event_bdev - -include $(SPDK_ROOT_DIR)/mk/spdk.app.mk \ No newline at end of file diff --git a/zvfs/zvfs.old/zvfs.c b/zvfs/zvfs.old/zvfs.c deleted file mode 100755 index a415fa1..0000000 --- a/zvfs/zvfs.old/zvfs.c +++ /dev/null @@ -1,442 +0,0 @@ - - -#include - -#include -#include -#include -#include -#include - - - -#define BUFFER_SIZE 512 - - -#define FILENAME_LENGTH 128 - -typedef struct zvfs_s { - - struct spdk_blob_store *blobstore; - uint64_t unit_size; - uint64_t free_cluster_count; - uint64_t num_cluster; - - struct spdk_io_channel *channel; - - bool finished; -} zvfs_t; - -typedef struct zvfs_operation_s { - int (*mount)(struct zvfs_s *fs); - int (*umount)(struct zvfs_s *fs); -} zvfs_operation_t; - -typedef struct zvfs_file_s { - - uint8_t filename[FILENAME_LENGTH]; - - spdk_blob_id blob_id; - struct spdk_blob *blob; - zvfs_t *fs; - - uint8_t *write_buffer; - uint8_t *read_buffer; - - bool finished; - -} zvfs_file_t; - -typedef struct zvfs_file_operation_s { - - int (*open)(struct zvfs_file_s *file); - int (*read)(struct zvfs_file_s *file); - int (*write)(struct zvfs_file_s *file); - int (*close)(struct zvfs_file_s *file); - -} zvfs_file_operation_t; - - - -struct spdk_thread *global_thread = NULL; -static const int WAITER_MAX_TIME = 100000; - -// mount -void zvfs_do_mount(void *arg); -void zvfs_spdk_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx); -void zvfs_spdk_bs_init_cb(void *arg, struct spdk_blob_store *bs, int bserrno); - -// open -void zvfs_do_create(void *arg); -void zvfs_spdk_bs_create_blob_cb(void *arg, spdk_blob_id blobid, int bserrno); -void zvfs_spdk_bs_open_blob_create_cb(void *arg, struct spdk_blob *blb, int bserrno); -void zvfs_spdk_blob_resize_cb(void *arg, int bserrno); -void zvfs_spdk_blob_sync_cb(void *arg, int bserrno); - -// read -void zvfs_do_read(void *arg); -void zvfs_spdk_blob_read_cb(void *arg, int bserrno); - -// write -void zvfs_do_write(void *arg); -void zvfs_spdk_blob_write_cb(void *arg, int bserrno); - - -// close -void zvfs_do_close(void *arg); -void zvfs_spdk_blob_close_cb(void *arg, int bserrno); -void zvfs_spdk_blob_delete_cb(void *arg, int bserrno); - -// waiter -bool waiter(struct spdk_thread *thread, spdk_msg_fn start_fn, void *ctx, bool *finished); - -// setup -int zvfs_env_setup(void); -void zvfs_json_load_fn(void *arg); -void json_app_load_done(int rc, void *ctx); - -// unmount -void zvfs_do_umount(void *arg); -void zvfs_spdk_bs_unload_cb(void *arg, int bserrno); - -// mount -void zvfs_do_mount(void *arg) { - - zvfs_t *fs = (zvfs_t*)arg; - - struct spdk_bs_dev *bs_dev = NULL; - - int rc = spdk_bdev_create_bs_dev_ext("Malloc0", zvfs_spdk_bdev_event_cb, NULL, &bs_dev); - if (rc != 0) { - spdk_app_stop(0); - } - - spdk_bs_init(bs_dev, NULL, zvfs_spdk_bs_init_cb, fs); - - SPDK_NOTICELOG("zvfs_entry\n"); - -} - -void zvfs_spdk_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, - void *event_ctx) { - SPDK_NOTICELOG("zvfs_spdk_bdev_event_cb\n"); -} - -void zvfs_spdk_bs_init_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { - zvfs_t *fs = (zvfs_t*)arg; - - uint64_t io_unit_size = spdk_bs_get_io_unit_size(bs); - SPDK_NOTICELOG("io_unit_size : %"PRIu64"\n", io_unit_size); - - fs->unit_size = io_unit_size; - fs->blobstore = bs; - - fs->channel = spdk_bs_alloc_io_channel(fs->blobstore); - if (fs->channel == NULL) { - return ; - } - - fs->finished = true; - - SPDK_NOTICELOG("mount finished\n"); -} - - -// open -void zvfs_do_create(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - spdk_bs_create_blob(file->fs->blobstore, zvfs_spdk_bs_create_blob_cb, file); - -} - -void zvfs_spdk_bs_create_blob_cb(void *arg, spdk_blob_id blobid, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - file->blob_id = blobid; - SPDK_NOTICELOG("blobid : %"PRIu64"\n", blobid); - - spdk_bs_open_blob(file->fs->blobstore, blobid, zvfs_spdk_bs_open_blob_create_cb, file); -} - -void zvfs_spdk_bs_open_blob_create_cb(void *arg, struct spdk_blob *blb, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - file->blob = blb; - - uint64_t free_cluster = spdk_bs_free_cluster_count(file->fs->blobstore); // - SPDK_NOTICELOG("free_cluster : %"PRIu64"\n", free_cluster); - file->fs->free_cluster_count = free_cluster; - - spdk_blob_resize(blb, free_cluster, zvfs_spdk_blob_resize_cb, file); - -} - -void zvfs_spdk_blob_resize_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - uint64_t total = spdk_blob_get_num_clusters(file->blob); - file->fs->num_cluster = total; - - SPDK_NOTICELOG("resize blob :%"PRIu64"\n", total); - - spdk_blob_sync_md(file->blob, zvfs_spdk_blob_sync_cb, file); - -} - -void zvfs_spdk_blob_sync_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - file->write_buffer = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if (file->write_buffer == NULL) { - return ; - } - - file->read_buffer = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if (file->read_buffer == NULL) { - return ; - } - - - SPDK_NOTICELOG("open complete\n"); - - file->finished = true; -} - -// read -void zvfs_do_read(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - memset(file->read_buffer, 0x0, BUFFER_SIZE); - spdk_blob_io_read(file->blob, file->fs->channel, file->read_buffer, - 0, 1, zvfs_spdk_blob_read_cb, file); -} - -void zvfs_spdk_blob_read_cb(void *arg, int bserrno) { - - zvfs_file_t *file = (zvfs_file_t *)arg; - - SPDK_NOTICELOG("READ BUFFER : %s\n", file->read_buffer); - - file->finished = true; -} - - -// write -void zvfs_do_write(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_blob_io_write(file->blob, file->fs->channel, file->write_buffer, - 0, 1, zvfs_spdk_blob_write_cb, file); -} - -void zvfs_spdk_blob_write_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - SPDK_NOTICELOG("write complete\n"); - - file->finished = true; -} - -// close -void zvfs_do_close(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_blob_close(file->blob, zvfs_spdk_blob_close_cb, file); -} - -void zvfs_spdk_blob_close_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_bs_delete_blob(file->fs->blobstore, file->blob_id, zvfs_spdk_blob_delete_cb, file); -} - -void zvfs_spdk_blob_delete_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - spdk_free(file->write_buffer); - spdk_free(file->read_buffer); - - SPDK_NOTICELOG("close complete\n"); - file->finished = true; -} - -// unmount -void zvfs_do_umount(void *arg) { - - zvfs_t *fs = (zvfs_t *)arg; - - if (fs->blobstore) { - if (fs->channel) { - spdk_bs_free_io_channel(fs->channel); - } - spdk_bs_unload(fs->blobstore, zvfs_spdk_bs_unload_cb, fs); - } - -} - -void zvfs_spdk_bs_unload_cb(void *arg, int bserrno) { - - zvfs_t *fs = (zvfs_t *)arg; - fs->finished = true; -} - -// waiter -bool waiter(struct spdk_thread *thread, spdk_msg_fn start_fn, void *ctx, bool *finished) { - - spdk_thread_send_msg(thread, start_fn, ctx); - - int waiter_count = 0; - - do { - spdk_thread_poll(thread, 0, 0); - waiter_count ++; - } while(!(*finished) && waiter_count < WAITER_MAX_TIME); - - if (!(*finished) && waiter_count >= WAITER_MAX_TIME) { - return false; // timeout - } - - return true; -} - -// setup -// zvfs.json -static const char *json_file = "/home/lian/share/10.1-spdk/zvfs/zvfs.json"; - -int zvfs_env_setup(void) { - struct spdk_env_opts opts; - spdk_env_opts_init(&opts); - - if (0 != spdk_env_init(&opts)) { - return -1; - } - - spdk_log_set_print_level(SPDK_LOG_NOTICE); - spdk_log_set_level(SPDK_LOG_NOTICE); - spdk_log_open(NULL); - - spdk_thread_lib_init(NULL, 0); - global_thread = spdk_thread_create("global", NULL); - spdk_set_thread(global_thread); - - bool done = false; - - waiter(global_thread, zvfs_json_load_fn, &done, &done); - SPDK_NOTICELOG("json_app_load_done complete\n"); - - - return 0; - -} - -void zvfs_json_load_fn(void *arg) { - spdk_subsystem_init_from_json_config(json_file, SPDK_DEFAULT_RPC_ADDR, json_app_load_done, - arg, true); - -} - -void json_app_load_done(int rc, void *ctx) { - bool *done = ctx; - *done = true; - - SPDK_NOTICELOG("json_app_load_done\n"); -} - - -// filesystem -// load -static int zvfs_mount(struct zvfs_s *fs) { - - fs->finished = false; - - return waiter(global_thread, zvfs_do_mount, fs, &fs->finished); -} - - -// unload -static int zvfs_umount(struct zvfs_s *fs) { - - fs->finished = false; - - return waiter(global_thread, zvfs_do_umount, fs, &fs->finished); -} - - -// file -// open -static int zvfs_create(struct zvfs_file_s *file) { - - file->finished = false; - - return waiter(global_thread, zvfs_do_create, file, &file->finished); - -} - - -// read -static int zvfs_read(struct zvfs_file_s *file, uint8_t *buffer, size_t count) { - - file->finished = false; - waiter(global_thread, zvfs_do_read, file, &file->finished); - - int len = strlen(file->read_buffer); - memcpy(buffer, file->read_buffer, len); // - - return len; -} - - -// write -static int zvfs_write(struct zvfs_file_s *file, const uint8_t *buffer, size_t count) { - - file->finished = false; - - memcpy(file->write_buffer, buffer, count); // count / 512 - - return waiter(global_thread, zvfs_do_write, file, &file->finished); -} - -// close -static int zvfs_close(struct zvfs_file_s *file) { - - file->finished = false; - - return waiter(global_thread, zvfs_do_close, file, &file->finished); -} - - -int main(int argc, char *argv[]) { - - if (zvfs_env_setup()) { - return -1; - } - - SPDK_NOTICELOG("zvfs_env_setup success\n"); - - SPDK_NOTICELOG("\n\n zvfs mount start \n\n"); - zvfs_t *fs = calloc(1, sizeof(zvfs_t)); - zvfs_mount(fs); - - SPDK_NOTICELOG("\n\n zvfs open start \n\n"); - zvfs_file_t *file = calloc(1, sizeof(zvfs_file_t)); - file->fs = fs; - zvfs_create(file); - - - SPDK_NOTICELOG("\n\n zvfs write start \n\n"); - char *buffer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - zvfs_write(file, buffer, strlen(buffer)); - - - SPDK_NOTICELOG("\n\n zvfs read start \n\n"); - char rbuffer[BUFFER_SIZE] = {0}; - zvfs_read(file, rbuffer, BUFFER_SIZE); - SPDK_NOTICELOG("READ BUFFER: %s\n", rbuffer); - - SPDK_NOTICELOG("\n\n zvfs close start \n\n"); - zvfs_close(file); - - free(file); - - SPDK_NOTICELOG("\n\n zvfs umount start \n\n"); - zvfs_umount(fs); - free(fs); - -} - - diff --git a/zvfs/zvfs.old/zvfs.json b/zvfs/zvfs.old/zvfs.json deleted file mode 100755 index 9f788d7..0000000 --- a/zvfs/zvfs.old/zvfs.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "subsystems": [ - { - "subsystem": "bdev", - "config": [ - { - "method": "bdev_malloc_create", - "params": { - "name": "Malloc0", - "num_blocks": 655360, - "block_size": 512 - } - } - ] - } - ] -} diff --git a/zvfs/zvfs.old/zvfs.old.c b/zvfs/zvfs.old/zvfs.old.c deleted file mode 100755 index 9fadc41..0000000 --- a/zvfs/zvfs.old/zvfs.old.c +++ /dev/null @@ -1,176 +0,0 @@ - - -#include "spdk/event.h" -#include "spdk/blob.h" -#include "spdk/bdev.h" -#include "spdk/blob_bdev.h" - -#define BUFFER_SIZE 512 - -typedef struct zv1ianfs_ctx_s { - struct spdk_blob_store *blobstore; - uint64_t unit_size; - spdk_blob_id blob_id; - struct spdk_blob *blob; - uint64_t free_cluster_count; - uint64_t num_cluster; - uint8_t *write_buffer; - uint8_t *read_buffer; - struct spdk_io_channel *channel; -} zv1ianfs_ctx_t; - -void zv1ianfs_spdk_bs_unload_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_delete_blob_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_blob_close_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_blob_io_read_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_blob_io_write_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_blob_sync_cb(void *cb_arg, int bserrno); -void zv1ianfs_spdk_blob_resize(void *cb_arg, int bserrno); -void zv1ianfs_spdk_bs_open_blob_cb(void *cb_arg, struct spdk_blob *blb, int bserrno); -void zv1ianfs_spdk_bs_create_blob_cb(void *cb_arg, spdk_blob_id blobid, int bserrno); -void zv1ianfs_spdk_bs_init_cb(void *cb_arg, struct spdk_blob_store *bs, int bserrno); -void zv1ianfs_spdk_bdev_event_cb (enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx); -void zv1ianfs_do_mount(void *ctx); - -void zv1ianfs_spdk_bs_unload_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - spdk_app_stop(0); - - spdk_free(ctx->write_buffer); - spdk_free(ctx->read_buffer); - free(ctx); -} - -void zv1ianfs_spdk_delete_blob_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - if(ctx->blobstore) { - if(ctx->channel) { - spdk_bs_free_io_channel(ctx->channel); - } - spdk_bs_unload(ctx->blobstore, zv1ianfs_spdk_bs_unload_cb, ctx); - } -} - -void zv1ianfs_spdk_blob_close_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - spdk_bs_delete_blob(ctx->blobstore, ctx->blob_id, zv1ianfs_spdk_delete_blob_cb, ctx); -} - -void zv1ianfs_spdk_blob_io_read_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - SPDK_NOTICELOG("READ BUFFER : %s\n", ctx->read_buffer); - - spdk_blob_close(ctx->blob, zv1ianfs_spdk_blob_close_cb ,ctx); -} - -void zv1ianfs_spdk_blob_io_write_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - SPDK_NOTICELOG("WRITE COMPLETE\n"); - - uint8_t *rbuffer = spdk_malloc(512, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if(rbuffer == NULL){ - spdk_app_stop(-1); - return ; - } - memset(rbuffer, 0x0, BUFFER_SIZE); - ctx->read_buffer = rbuffer; - - spdk_blob_io_read(ctx->blob, ctx->channel, rbuffer, 0, 1, zv1ianfs_spdk_blob_io_read_cb, ctx); -} - -void zv1ianfs_spdk_blob_sync_cb(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - uint8_t *wbuffer = spdk_malloc(512, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if(wbuffer == NULL){ - spdk_app_stop(-1); - return ; - } - memset(wbuffer, 'A', BUFFER_SIZE); - *(wbuffer+BUFFER_SIZE-1) = '\0'; - ctx->write_buffer = wbuffer; - - ctx->channel = spdk_bs_alloc_io_channel(ctx->blobstore); - if(ctx->channel == NULL){ - spdk_app_stop(-1); - } - - spdk_blob_io_write(ctx->blob, ctx->channel, wbuffer, 0, 1, zv1ianfs_spdk_blob_io_write_cb, ctx); -} - -void zv1ianfs_spdk_blob_resize(void *cb_arg, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - - uint64_t total = spdk_blob_get_num_clusters(ctx->blob); - ctx->num_cluster = total; - - SPDK_NOTICELOG("resize blob : %"PRIu64"\n", total); - - spdk_blob_sync_md(ctx->blob, zv1ianfs_spdk_blob_sync_cb, ctx); -} - -void zv1ianfs_spdk_bs_open_blob_cb(void *cb_arg, struct spdk_blob *blb, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - ctx->blob = blb; - - uint64_t free_cluster = spdk_bs_free_cluster_count(ctx->blobstore); - ctx->free_cluster_count = free_cluster; - - SPDK_NOTICELOG("free_cluster : %"PRIu64"\n", free_cluster); - spdk_blob_resize(blb, free_cluster, zv1ianfs_spdk_blob_resize, ctx); -} - -void zv1ianfs_spdk_bs_create_blob_cb(void *cb_arg, spdk_blob_id blobid, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - ctx->blob_id = blobid; - - SPDK_NOTICELOG("blobid : %"PRIu64"\n", blobid); - spdk_bs_open_blob(ctx->blobstore, blobid, zv1ianfs_spdk_bs_open_blob_cb, ctx); -} - -void zv1ianfs_spdk_bs_init_cb(void *cb_arg, struct spdk_blob_store *bs, int bserrno){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)cb_arg; - ctx->blobstore = bs; - - uint64_t io_unit_size = spdk_bs_get_io_unit_size(bs); - ctx->unit_size = io_unit_size; - - SPDK_NOTICELOG("io_unit_size: %"PRIu64"\n", io_unit_size); - spdk_bs_create_blob(bs, zv1ianfs_spdk_bs_create_blob_cb, ctx); -} - -void zv1ianfs_spdk_bdev_event_cb (enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx){ - SPDK_NOTICELOG("zv1ianfs_spdk_bdev_event_cb called\n"); -} - -void zv1ianfs_do_mount(void *arg){ - zv1ianfs_ctx_t *ctx = (zv1ianfs_ctx_t *)arg; - - struct spdk_bs_dev *bs_dev = NULL; - - int rc = spdk_bdev_create_bs_dev_ext("Malloc0", zv1ianfs_spdk_bdev_event_cb, NULL, &bs_dev); - if(rc != 0){ - SPDK_NOTICELOG("错误: 无法创建 bs_dev, 错误码 = %d\n", rc); - spdk_app_stop(-1); - return; - } - - spdk_bs_init(bs_dev, NULL, zv1ianfs_spdk_bs_init_cb, ctx); - - SPDK_NOTICELOG("zv1ianfs_entry called\n"); -} - - -int main(int argc, char *argv[]){ - - struct spdk_app_opts opts = {}; - - spdk_app_opts_init(&opts, sizeof(opts)); - opts.name = "zv1ianfs"; - opts.json_config_file = argv[1]; - - zv1ianfs_ctx_t *ctx = calloc(1, sizeof(zv1ianfs_ctx_t)); - - spdk_app_start(&opts, zv1ianfs_do_mount, ctx); - - SPDK_NOTICELOG("hello spdk\n"); -} \ No newline at end of file diff --git a/zvfs/zvfs_hook.c b/zvfs/zvfs_hook.c index 2fc211d..9af7023 100644 --- a/zvfs/zvfs_hook.c +++ b/zvfs/zvfs_hook.c @@ -43,6 +43,7 @@ static const char *META_FILE = "/home/lian/share/10.1-spdk/zvfs/zvfs_meta.txt"; #define ZVFS_MAX_DIRS 1024 #define ZVFS_PATH_PREFIX "/zvfs" +#define ZVFS_WB_CAP (128 * 1024) typedef struct { bool used; @@ -462,6 +463,67 @@ static bool can_write(const zvfs_file_t *file) return mode != O_RDONLY; } +static int ensure_writeback_buf(zvfs_file_t *file, size_t need) +{ + uint8_t *p; + size_t cap = ZVFS_WB_CAP; + + if (!file) { + return -1; + } + if (file->wb_buf && file->wb_cap >= need) { + return 0; + } + while (cap < need) { + cap <<= 1; + } + p = realloc(file->wb_buf, cap); + if (!p) { + return -1; + } + file->wb_buf = p; + file->wb_cap = cap; + return 0; +} + +static int flush_file_wb(zvfs_file_t *file) +{ + int rc; + + if (!file || !file->wb_valid || file->wb_len == 0) { + return 0; + } + rc = zvfs_pwrite(file, file->wb_buf, file->wb_len, file->wb_base); + if (rc != (int)file->wb_len) { + if (rc >= 0) { + file->op_errno = -EIO; + } + return -1; + } + file->wb_valid = false; + file->wb_len = 0; + return 0; +} + +static int flush_dirent_wb(zvfs_dirent_t *dirent) +{ + int i; + + if (!g_fs || !dirent) { + return 0; + } + for (i = 0; i < ZVFS_MAX_FD; i++) { + zvfs_file_t *f = g_fs->fd_table[i]; + if (!f || f->dirent != dirent) { + continue; + } + if (flush_file_wb(f) != 0) { + return -1; + } + } + return 0; +} + /* ------------------------------------------------------------------ */ /* 目录项/FD 辅助 */ /* ------------------------------------------------------------------ */ @@ -980,6 +1042,15 @@ static void fill_stat64(struct stat64 *st, mode_t mode, off64_t size, uint64_t i st->st_ctime = now; } +static bool is_stale_blob_op_errno(int op_errno) +{ + int e = op_errno; + if (e < 0) { + e = -e; + } + return e == ENOENT || e == EINVAL; +} + /* ------------------------------------------------------------------ */ /* open helpers */ /* ------------------------------------------------------------------ */ @@ -991,6 +1062,7 @@ static int open_zvfs_file(const char *path, int flags) zvfs_dirent_t *dirent; zvfs_file_t *file; bool created = false; + bool stale_repaired = false; int ok; int fd; @@ -1070,10 +1142,25 @@ static int open_zvfs_file(const char *path, int flags) } else { file->blob_id = dirent->blob_id; ok = zvfs_open(file); + if (!ok && (flags & O_CREAT) && is_stale_blob_op_errno(file->op_errno)) { + /* Metadata may refer to a blob that no longer exists in Blobstore. */ + dirent->blob_id = 0; + dirent->file_size = 0; + dirent->allocated_clusters = 0; + file->blob_id = 0; + file->op_errno = 0; + file->finished = false; + ok = zvfs_create(file); + if (ok) { + dirent->blob_id = file->blob_id; + stale_repaired = true; + } + } } if (!ok) { + int op_errno = file->op_errno; free(file); - errno = EIO; + errno = is_stale_blob_op_errno(op_errno) ? ENOENT : EIO; return -1; } @@ -1092,7 +1179,7 @@ static int open_zvfs_file(const char *path, int flags) return -1; } dirent->open_count++; - if (created || (flags & O_TRUNC)) { + if (created || stale_repaired || (flags & O_TRUNC)) { (void)meta_save(g_fs); } if (debug_path_enabled(norm)) { @@ -1498,6 +1585,10 @@ ssize_t read(int fd, void *buf, size_t count) errno = EBADF; return -1; } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } rc = zvfs_read(file, (uint8_t *)buf, count); if (rc < 0) { errno = file->op_errno ? -file->op_errno : EIO; @@ -1512,6 +1603,7 @@ ssize_t write(int fd, const void *buf, size_t count) { zvfs_file_t *file; int rc; + uint64_t off; if (!is_zvfs_fd(fd)) { return real_write_fn ? real_write_fn(fd, buf, count) : -1; @@ -1524,6 +1616,40 @@ ssize_t write(int fd, const void *buf, size_t count) if (file->flags & O_APPEND) { file->current_offset = file->dirent ? file->dirent->file_size : file->current_offset; } + if (count == 0) { + return 0; + } + + off = file->current_offset; + if (count <= ZVFS_WB_CAP) { + if (!file->wb_valid || + off != file->wb_base + file->wb_len || + file->wb_len + count > file->wb_cap) { + if (flush_file_wb(file) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } + if (ensure_writeback_buf(file, count) != 0) { + errno = ENOMEM; + return -1; + } + file->wb_valid = true; + file->wb_base = off; + file->wb_len = 0; + } + memcpy(file->wb_buf + file->wb_len, buf, count); + file->wb_len += count; + file->current_offset += count; + if (file->dirent && file->current_offset > file->dirent->file_size) { + file->dirent->file_size = file->current_offset; + } + return (ssize_t)count; + } + + if (flush_file_wb(file) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } rc = zvfs_write(file, (const uint8_t *)buf, count); if (rc < 0) { errno = file->op_errno ? -file->op_errno : EIO; @@ -1553,6 +1679,10 @@ ssize_t pread(int fd, void *buf, size_t count, off_t offset) errno = EBADF; return -1; } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } rc = zvfs_pread(file, (uint8_t *)buf, count, (uint64_t)offset); if (rc < 0) { errno = file->op_errno ? -file->op_errno : EIO; @@ -1580,6 +1710,10 @@ ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) errno = EBADF; return -1; } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } rc = zvfs_pwrite(file, (const uint8_t *)buf, count, (uint64_t)offset); if (rc < 0) { errno = file->op_errno ? -file->op_errno : EIO; @@ -1611,6 +1745,10 @@ off_t lseek(int fd, off_t offset, int whence) errno = EBADF; return -1; } + if (flush_file_wb(file) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } file_size = file->dirent ? file->dirent->file_size : 0; switch (whence) { case SEEK_SET: @@ -1669,6 +1807,10 @@ int close(int fd) log_enabled = true; } + if (flush_file_wb(file) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } if (!zvfs_close(file)) { errno = file->op_errno ? -file->op_errno : EIO; return -1; @@ -1678,7 +1820,7 @@ int close(int fd) if (dirent) { dirent->open_count--; if (dirent->open_count == 0 && !dirent->is_valid) { - if (!zvfs_delete(file)) { + if (!zvfs_delete(file) && !is_stale_blob_op_errno(file->op_errno)) { errno = file->op_errno ? -file->op_errno : EIO; return -1; } @@ -1686,6 +1828,9 @@ int close(int fd) (void)meta_save(g_fs); } } + free(file->wb_buf); + file->wb_buf = NULL; + file->wb_cap = 0; free(file); if (log_enabled) { debug_log("close fd=%d path=%s", fd, log_path); @@ -1717,13 +1862,17 @@ int unlink(const char *name) d->is_valid = false; return 0; } + if (flush_dirent_wb(d) != 0) { + errno = EIO; + return -1; + } - { + if (d->blob_id != 0) { zvfs_file_t tmp = {0}; tmp.fs = g_fs; tmp.dirent = d; tmp.blob_id = d->blob_id; - if (!zvfs_delete(&tmp)) { + if (!zvfs_delete(&tmp) && !is_stale_blob_op_errno(tmp.op_errno)) { errno = tmp.op_errno ? -tmp.op_errno : EIO; return -1; } @@ -1757,7 +1906,19 @@ int unlinkat(int dirfd, const char *path, int flags) int fsync(int fd) { - if (is_zvfs_fd(fd) || is_zvfs_dirfd(fd)) { + if (is_zvfs_fd(fd)) { + zvfs_file_t *file = fd_lookup(fd); + if (!file) { + errno = EBADF; + return -1; + } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } + return 0; + } + if (is_zvfs_dirfd(fd)) { return 0; } return real_fsync_fn ? real_fsync_fn(fd) : -1; @@ -1765,7 +1926,19 @@ int fsync(int fd) int fdatasync(int fd) { - if (is_zvfs_fd(fd) || is_zvfs_dirfd(fd)) { + if (is_zvfs_fd(fd)) { + zvfs_file_t *file = fd_lookup(fd); + if (!file) { + errno = EBADF; + return -1; + } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } + return 0; + } + if (is_zvfs_dirfd(fd)) { return 0; } return real_fdatasync_fn ? real_fdatasync_fn(fd) : -1; @@ -1786,6 +1959,10 @@ int ftruncate(int fd, off_t length) errno = EBADF; return -1; } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } if ((uint64_t)length > file->dirent->file_size && length > 0) { uint8_t zero = 0; @@ -1821,6 +1998,10 @@ int fallocate(int fd, int mode, off_t offset, off_t len) errno = EBADF; return -1; } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } /* Minimal support: mode=0 or KEEP_SIZE only. */ keep_size = (mode & FALLOC_FL_KEEP_SIZE) != 0; @@ -1868,7 +2049,19 @@ int sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags) (void)offset; (void)nbytes; (void)flags; - if (is_zvfs_fd(fd) || is_zvfs_dirfd(fd)) { + if (is_zvfs_fd(fd)) { + zvfs_file_t *file = fd_lookup(fd); + if (!file) { + errno = EBADF; + return -1; + } + if (flush_dirent_wb(file->dirent) != 0) { + errno = file->op_errno ? -file->op_errno : EIO; + return -1; + } + return 0; + } + if (is_zvfs_dirfd(fd)) { return 0; } return real_sync_file_range_fn ? real_sync_file_range_fn(fd, offset, nbytes, flags) : 0; @@ -1971,18 +2164,26 @@ int rename(const char *oldpath, const char *newpath) errno = ENOENT; return -1; } + if (flush_dirent_wb(src) != 0) { + errno = EIO; + return -1; + } dst = dirent_find(new_norm); if (dst) { + if (flush_dirent_wb(dst) != 0) { + errno = EIO; + return -1; + } if (dst->open_count > 0) { errno = EBUSY; return -1; } - { + if (dst->blob_id != 0) { zvfs_file_t tmp = {0}; tmp.fs = g_fs; tmp.dirent = dst; tmp.blob_id = dst->blob_id; - if (!zvfs_delete(&tmp)) { + if (!zvfs_delete(&tmp) && !is_stale_blob_op_errno(tmp.op_errno)) { errno = tmp.op_errno ? -tmp.op_errno : EIO; return -1; }