小块写缓存优化

This commit is contained in:
2026-03-03 14:24:17 +00:00
parent c33a694bd8
commit 975afaf3f0
14 changed files with 690 additions and 1298 deletions

View File

@@ -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 采样。

194
plan/codexplan.md Normal file
View File

@@ -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 Planeworker**
- 处理 read/pread/write/pwrite/fsync 的数据 IO。
- 每个 worker 持有:`spdk_thread + io_channel + submission_queue + completion_queue`
- **Persistence Plane元数据持久化**
- 元数据 WALappend-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 提升 >= 2x4~16 线程场景)
- P99 延迟下降 >= 30%
- CPU busy-poll 占比显著下降(可通过 perf/top 观测)
- `test_single_file_perf``test_single_file_random_perf` 在同配置下持续稳定,无明显长尾抖动。

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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. 是否可稳定复现。

View File

@@ -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 区域:
- `### 阶段验收`
- `### 正确性验证方案`

View File

@@ -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 2Worker 化 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: 完成 phase0db_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 名改为可配置(环境变量优先)。

View File

@@ -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` 不能覆盖全部 syscallphase2 开始前建议补 `readrandom/overwrite` 采样以确认 `pwrite64` 等调用比例。

108
plan/userplan.md Normal file
View File

@@ -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 创建)。
- bdevNvme0n1 或 Malloc0通过 JSON 配置加载)。
- 全局元数据dirents 数组zvfs_dirent_t *[]、fd_tablezvfs_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 cachedirty标记 dirty返回延迟写
- 如果 cache 满或大写 → flush dirty pagesbatch spdk_blob_io_writev用 tl->channel
- 创建 io_ctx → 加入 pending_queue → submit write → batch_poll。
5. **fsync**
- flush per-file dirty cachebatch writev + spdk_blob_sync_md
- 使用当前 tl->thread poll 等待完成。
6. **close**
- fsyncflush 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 pageshashmap合并小写减少 write amplification。
- **channel per-thread**:避免全局 channel 争用,每个线程独立提交 IO。
- **最小全局锁**只在元数据修改时短时加锁rwlockIO 操作无锁。
### 资源所有权总结表
| 资源类型 | 所有权 | 数量 | 创建时机 | 销毁时机 |
|----------------------|--------------|------------|------------------------|------------------------------|
| 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 |

View File

@@ -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;

View File

@@ -72,6 +72,13 @@ typedef struct zvfs_file_s {
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;

View File

@@ -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

View File

@@ -1,442 +0,0 @@
#include <stdio.h>
#include <spdk/event.h>
#include <spdk/blob.h>
#include <spdk/bdev.h>
#include <spdk/blob_bdev.h>
#include <spdk/env.h>
#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);
}

View File

@@ -1,17 +0,0 @@
{
"subsystems": [
{
"subsystem": "bdev",
"config": [
{
"method": "bdev_malloc_create",
"params": {
"name": "Malloc0",
"num_blocks": 655360,
"block_size": 512
}
}
]
}
]
}

View File

@@ -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");
}

View File

@@ -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;
}