Files
zvfs/README.md

219 lines
6.4 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ZVFS
ZVFS 是一个基于 `SPDK Blobstore` 的轻量级用户态文件系统原型,
通过 `LD_PRELOAD` 拦截常见 POSIX 文件 API`/zvfs` 路径下的文件 I/O 转换为 Blob I/O。
目标是让上层应用尽量少改动地复用阻塞式文件接口,同时接近 SPDK 在低队列深度QD≈1场景的性能上限。
## 1. 项目结构
```text
zvfs/
├── src/
│ ├── hook/ # POSIX API hook 层open/read/write/...
│ ├── fs/ # inode/path/fd 运行时元数据管理
│ ├── spdk_engine/ # SPDK Blobstore 封装
│ ├── common/ # 对齐与缓冲区工具函数
│ ├── config.h # 默认配置JSON、bdev、xattr key 等)
│ └── Makefile # 产出 libzvfs.so
├── tests/
│ ├── hook/ # hook API 语义测试
│ ├── ioengine_test/ # Blob 引擎单元测试
│ └── Makefile
├── scripts/ # db_bench/hook 测试辅助脚本
├── spdk/ # SPDK 子模块
└── README.md
```
## 2. 核心架构
### 2.1 分层
当前实现:
```text
App (open/read/write/fstat/...)
-> LD_PRELOAD Hook (src/hook)
-> ZVFS Runtime Metadata (src/fs)
-> SPDK Engine (src/spdk_engine)
-> SPDK Blobstore
-> bdev (Malloc/NVMe)
```
目标架构Daemon + IPC
```text
App (multi-process, e.g. PostgreSQL)
-> LD_PRELOAD Hook Client
-> IPC (Unix Domain Socket)
-> zvfs daemon
-> metadata manager
-> SPDK worker threads
-> SPDK Blobstore / bdev
```
### 2.2 目标架构简版HOOK 层 + daemon 层)
- `HOOK 层`
- 拦截 `/zvfs` 路径的 POSIX API 并同步发起 IPC 请求。
- 维护本地最小状态(如 `fd -> remote_handle_id`)。
- 对非 `/zvfs` 路径继续透传到 `real_*` syscallPOSIX passthrough
- `daemon 层`
- 独占 SPDK 资源(`spdk_env/blobstore/spdk_thread`)。
- 统一处理元数据与并发控制path/inode/handle
- 接收 IPC 请求并执行实际 I/O返回 POSIX 风格结果与 errno。
### 2.3 元数据与数据映射
- 文件数据:存储在 SPDK blob 中。
- 文件到 blob 的映射:写入真实文件的 `xattr`key: `user.zvfs.blob_id`)。
- 运行时维护三张表:
- `inode_table``blob_id -> inode`
- `path_cache``path -> inode`
- `fd_table``fd -> open_file`
### 2.4 当前实现的 I/O 路径要点
- `blob_read/blob_write` 统一走按 `io_unit_size` 对齐的 DMA 缓冲。
- 非对齐写会触发读改写RMW先读对齐块再覆盖局部写回。
- `readv/writev` 在 hook 层会做聚合,减少多次 I/O 提交。
- `fsync/fdatasync` 对 zvfs fd 调用 `blob_sync_md``sync_file_range` 在 zvfs 路径直接返回成功。
## 3. 构建
> 下面命令以仓库根目录为 `/home/lian/try/zvfs` 为例。
### 3.1 初始化并构建 SPDK
```bash
git submodule update --init --recursive
cd spdk
./scripts/pkgdep.sh
./configure --with-shared
make -j"$(nproc)"
```
### 3.2 构建 ZVFS 与测试
```bash
cd /home/lian/try/zvfs
make -j"$(nproc)"
make test -j"$(nproc)"
```
产物:
- `src/libzvfs.so`
- `tests/bin/hook_api_test`
- `tests/bin/ioengine_single_blob_test`
- `tests/bin/ioengine_multi_blob_test`
- `tests/bin/ioengine_same_blob_mt_test`
## 4. 运行与验证
### 4.1 Hook API 语义测试
```bash
mkdir -p /zvfs
cd /home/lian/try/zvfs
LD_PRELOAD=$PWD/src/libzvfs.so ZVFS_TEST_ROOT=/zvfs ./tests/bin/hook_api_test
```
覆盖点包括:
- `open/openat/rename/unlink`
- `read/write/pread/pwrite/readv/writev/pwritev`
- `fstat/lseek/ftruncate`
- `fcntl/ioctl(FIONREAD)`
- `fsync/fdatasync`
### 4.2 SPDK 引擎测试
```bash
cd /home/lian/try/zvfs
SPDK_BDEV_NAME=Malloc0 ./tests/bin/ioengine_single_blob_test
SPDK_BDEV_NAME=Malloc0 ./tests/bin/ioengine_multi_blob_test
SPDK_BDEV_NAME=Malloc0 ./tests/bin/ioengine_same_blob_mt_test
```
## 5. 关键环境变量
- `SPDK_BDEV_NAME`:选择后端 bdev默认 `Malloc0`)。
- `ZVFS_BDEV``zvfs_ensure_init` 使用的 bdev 名称(未设置时使用 `config.h` 默认值)。
- `SPDK_JSON_CONFIG`:覆盖默认 SPDK JSON 配置路径。
## 6. 性能说明(仅保留趋势)
`README` 历史压测数据来自旧版本,不能直接当作当前版本结论;但可作为设计趋势参考:
- 目标工作负载为阻塞 API近似 `QD=1`
- 旧数据下ZVFS 在 `QD=1` 时约达到 `spdk_nvme_perf``90%~95%`
- 4K`95 MiB/s` vs `100 MiB/s`
- 128K`1662 MiB/s` vs `1843 MiB/s`
- 相对同机 `O_DIRECT` 路径,旧数据写带宽约有 `2.2x~2.3x` 提升。
- 非对齐写存在 RMW吞吐明显下降旧数据常见接近对齐写的一半
如果需要用于对外汇报,请重新在当前 commit 与固定硬件环境下复测。
## 7. 当前限制
- 仅拦截 `/zvfs` 路径。
- `mmap` 对 zvfs fd 当前返回 `ENOTSUP`(建议上层关闭 mmap 读写)。
- `dup/dup2/dup3` 对 zvfs fd 当前返回 `ENOTSUP`
- `rename``/zvfs` 与非 `/zvfs` 路径返回 `EXDEV`
- `fallocate(FALLOC_FL_PUNCH_HOLE)` 未实现。
## 8. 后续建议
- 补齐 mmap 路径mmap table + 脏页回写)。
- 完善多线程/高并发下的语义与压测基线。
- 增加版本化 benchmark 报告,避免 README 中历史数据失真。
## 9. Blob Store 血泪教训
### Owner Thread 绑定
blobstore内部负责并发控制让所有metadata操作都在一个线程上执行回调固定绑定给创建blobstore的线程。所以多线程模型下不是send给谁谁就能poll到回调的。
正确架构:
```
metadata thread
spdk_bs_load()
resize
delete
sync_md
worker thread
blob_io_read
blob_io_write
```
### spdk_for_each_channel() Barrier
某些 metadata 操作非常慢:
```
resize
delete
unload
snapshot
```
这些操作内部会调用spdk_for_each_channel()
语义:在所有 io_channel 所属线程执行 callback
类似
```c
for each channel:
send_msg(channel->thread)
```
#### 问题1持有 Channel 的 Thread 不 poll
如果所属线程不poll就会卡住。
#### 问题2线程退出 Channel 没有释放
永远卡住
### IO 操作的回调行为与 metadata 操作不同
spdk_blob_io_read / spdk_blob_io_write 的回调,是通过传入的 io_channel 投递的,回调回到分配该 channel 的 thread。
### 超时任务
设置超时就免不了超时后回调成功执行,超时后回调仍会触发,存在 UAF 风险