zvfs: testcase重写
This commit is contained in:
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
@@ -1,6 +1,9 @@
|
|||||||
*.o
|
*.o
|
||||||
*.d
|
*.d
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
|
test/bin
|
||||||
|
|
||||||
zvfs/func_test
|
zvfs/func_test
|
||||||
zvfs_meta.txt
|
zvfs_meta.txt
|
||||||
zvfs/zvfs_meta.txt
|
zvfs/zvfs_meta.txt
|
||||||
0
.gitmodules
vendored
Normal file → Executable file
0
.gitmodules
vendored
Normal file → Executable file
11
Makefile
Normal file → Executable file
11
Makefile
Normal file → Executable file
@@ -1,9 +1,16 @@
|
|||||||
.PHONY: all clean zvfs
|
.PHONY: all clean zvfs test run-test
|
||||||
|
|
||||||
all: zvfs
|
all: zvfs
|
||||||
|
|
||||||
zvfs:
|
zvfs:
|
||||||
$(MAKE) -C zvfs
|
$(MAKE) -C zvfs
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(MAKE) -C test
|
||||||
|
|
||||||
|
run-test:
|
||||||
|
$(MAKE) -C test run-test
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) -C zvfs clean
|
$(MAKE) -C zvfs clean
|
||||||
|
$(MAKE) -C test clean
|
||||||
|
|||||||
3
README.md
Normal file → Executable file
3
README.md
Normal file → Executable file
@@ -3,7 +3,8 @@
|
|||||||
```shell
|
```shell
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
cd /home/lian/share/10.1-spdk/spdk
|
cd spdk
|
||||||
|
./scripts/pkgdep.sh
|
||||||
./configure --with-shared
|
./configure --with-shared
|
||||||
make -j
|
make -j
|
||||||
|
|
||||||
|
|||||||
346
plan.md
Normal file
346
plan.md
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
# ZVFS -> RocksDB LD_PRELOAD 可重入改造计划(给 Codex 执行)
|
||||||
|
|
||||||
|
> 目标:这是一份“可中断、可恢复、可分段提交”的实施计划。
|
||||||
|
> 原则:每阶段都能独立编译、独立验证、独立回滚,不要求一次性改完。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 执行约束(必须遵守)
|
||||||
|
|
||||||
|
1. **一次只做一个阶段**,每阶段结束后必须通过该阶段验收再进入下一阶段。
|
||||||
|
2. **不跨阶段混改**:例如阶段 1 不改 hook 覆盖,阶段 3 不重构线程模型。
|
||||||
|
3. **每阶段必须留下恢复锚点**:
|
||||||
|
- 文档状态更新(本文件“阶段状态”区)
|
||||||
|
- 代码中保留临时兼容开关(若有)
|
||||||
|
- 能独立提交 commit(建议)
|
||||||
|
4. **失败可回退**:阶段内失败仅回退本阶段改动,不影响已完成阶段。
|
||||||
|
5. **默认保持旧行为可用**,新增能力通过新路径启用,逐步替换旧路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 阶段状态(可重入)
|
||||||
|
|
||||||
|
- [ ] Phase 1: IO 请求结构体落地(单线程兼容模式)
|
||||||
|
- [ ] Phase 2: Worker + 多线程执行模型落地
|
||||||
|
- [ ] Phase 3: 偏移型 IO 完整化(pread/pwrite 语义)
|
||||||
|
- [ ] Phase 4: RocksDB 核心 hook 补齐(P0)
|
||||||
|
- [ ] Phase 5: 目录/锁/截断接口补齐(P1)
|
||||||
|
- [ ] Phase 6: 元数据一致性与崩溃恢复增强
|
||||||
|
- [ ] Phase 7: 性能增强与观测
|
||||||
|
|
||||||
|
> 重入方式:中断后先看此状态区 + 对应阶段“完成定义”,从未完成阶段继续。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 基线与分支策略
|
||||||
|
|
||||||
|
### 2.1 基线要求
|
||||||
|
- 当前 `libzvfs.so` 可正常构建。
|
||||||
|
- 当前 `func_test` 可运行。
|
||||||
|
- 已确认 `/zvfs` 路径仍由现有 hook 接管。
|
||||||
|
|
||||||
|
### 2.2 分支与提交建议
|
||||||
|
- 每阶段 1 个分支或 1 个 commit:
|
||||||
|
- `phase1-req-struct`
|
||||||
|
- `phase2-worker-thread`
|
||||||
|
- `phase3-offset-io`
|
||||||
|
- ...
|
||||||
|
- commit message 模板:
|
||||||
|
- `phaseX: <what> + <compat mode/DoD>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 分阶段实施
|
||||||
|
|
||||||
|
## Phase 1: IO 请求结构体落地(不改线程模型)
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
先把“每次系统调用复用 file 临时字段”的问题解开;引入可扩展请求对象,为后续多线程做准备。
|
||||||
|
**本阶段仍允许单线程执行路径**(内部可继续使用现有 waiter)。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs.h`
|
||||||
|
- `zvfs/zvfs.c`
|
||||||
|
- 尽量不改 `zvfs/zvfs_hook.c` 的接口覆盖面(只做必要适配)
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 新增 `zvfs_req_t`(最小字段):
|
||||||
|
- `op`, `file`, `buf`, `count`, `offset`, `ret`, `op_errno`, `done`
|
||||||
|
- 可选:`need_copy_back`, `actual_io_count`
|
||||||
|
2. 给现有读写流程加“请求入参”版本:
|
||||||
|
- 新增 `zvfs_read_req(req)` / `zvfs_write_req(req)` 内部函数
|
||||||
|
- 旧 API `zvfs_read/zvfs_write` 变成薄包装(构造 req 后调用)
|
||||||
|
3. 去除对 `file->io_count/write_staging_buf/finished` 的强依赖:
|
||||||
|
- 保留字段可兼容,但主流程改为优先使用 req 字段
|
||||||
|
4. 错误返回统一:
|
||||||
|
- 内部负 errno -> 外部返回值 + errno 映射规则固定
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- 编译通过。
|
||||||
|
- 现有 `func_test` 全通过。
|
||||||
|
- 无并发改造前提下,行为与旧版一致(回归结果一致)。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- 代码里出现 `zvfs_req_t` 且 `zvfs_read/zvfs_write` 已封装请求对象。
|
||||||
|
- 若中断,优先检查旧路径是否仍可用,再继续替换剩余 call path。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Worker + 多线程执行模型
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
将“调用线程主动 poll SPDK”改为“专用 worker 线程 poll + 请求队列”。
|
||||||
|
建立线程安全基础,不追求本阶段 hook 覆盖齐全。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs.h`
|
||||||
|
- `zvfs/zvfs.c`
|
||||||
|
- `zvfs/zvfs_hook.c`(仅初始化、提交流程相关)
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 新增 `zvfs_worker_t`:
|
||||||
|
- `pthread_t tid`
|
||||||
|
- 请求队列(先用 mutex + list + cond,后续可换 MPSC)
|
||||||
|
- `running/stopping` 状态
|
||||||
|
2. 新增生命周期函数:
|
||||||
|
- `zvfs_worker_start()`
|
||||||
|
- `zvfs_worker_stop()`
|
||||||
|
- 在 mount/init 时启动,在 atexit/unmount 时停止
|
||||||
|
3. 新增提交流程:
|
||||||
|
- `zvfs_submit_and_wait(req)`:调用线程阻塞等待完成
|
||||||
|
- worker 线程执行 SPDK 异步链并回填 req
|
||||||
|
4. 替换 `waiter()` 主路径:
|
||||||
|
- 保留 waiter 仅作为 fallback(编译开关控制)
|
||||||
|
5. 锁与并发保护(最小闭环):
|
||||||
|
- 全局状态锁:`g_fs/g_mounted/fd_map`
|
||||||
|
- 文件级锁:`offset` 更新、close 与 io 竞争
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- `db_bench --threads=4` 不崩溃、不死锁(先小规模)。
|
||||||
|
- 旧单线程 case 仍通过。
|
||||||
|
- 调用线程不再直接 `spdk_thread_poll`。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- worker 生命周期已接管主流程(可在日志中看到 worker 启动/停止)。
|
||||||
|
- 若中断,优先确保 stop 路径可达,避免进程退出挂死。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 偏移型 IO 完整化(pread/pwrite 语义)
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
补齐 RocksDB 高频调用语义:`pread/pwrite` 不改文件偏移。
|
||||||
|
把 `read/write` 变成 `pread/pwrite + offset 管理` 的包装。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs.h`
|
||||||
|
- `zvfs/zvfs.c`
|
||||||
|
- `zvfs/zvfs_hook.c`
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 新增核心接口:
|
||||||
|
- `zvfs_pread(file, buf, count, offset)`
|
||||||
|
- `zvfs_pwrite(file, buf, count, offset)`
|
||||||
|
2. 改造 read/write:
|
||||||
|
- `read`:在 file lock 下读取并推进 `current_offset`
|
||||||
|
- `write`:在 file lock 下决定 offset(含 O_APPEND)
|
||||||
|
3. 明确并发语义:
|
||||||
|
- 同一 fd 并发 pread/pwrite 允许并行(不同 offset)
|
||||||
|
- 同一 fd 的 read/write 偏移操作受 file lock 串行
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- `pread/pwrite` 回归用例通过。
|
||||||
|
- 同一 fd 的并发 `pread` 不互相污染 offset。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- hook 层可区分 `read/write` 与 `pread/pwrite` 路径。
|
||||||
|
- 若中断,先保证 `read/write` 仍有正确 fallback。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: RocksDB 核心 hook 补齐(P0)
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
先覆盖 RocksDB “能跑起来”最关键接口。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs_hook.c`
|
||||||
|
- 必要时 `zvfs/zvfs.h` 新增声明
|
||||||
|
|
||||||
|
### 任务清单(按优先级)
|
||||||
|
1. 打开类:
|
||||||
|
- `open/open64/openat/openat64/__open_2/__open64_2`
|
||||||
|
2. IO 类:
|
||||||
|
- `pread/pread64/pwrite/pwrite64`
|
||||||
|
3. 元数据类:
|
||||||
|
- `fstat/stat/lstat/fstatat/access`
|
||||||
|
4. 持久化类:
|
||||||
|
- `fsync/fdatasync`
|
||||||
|
5. 文件变更类:
|
||||||
|
- `rename/renameat/renameat2/unlink/unlinkat`
|
||||||
|
|
||||||
|
### 实现要求
|
||||||
|
- 非 `/zvfs` 路径必须透传 real libc。
|
||||||
|
- 所有失败分支必须设置 errno。
|
||||||
|
- 统一 dlsym 初始化:`pthread_once`,避免递归。
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- RocksDB 基础 workload(单线程)可跑通:
|
||||||
|
- `fillseq`, `fillrandom`, `readrandom`
|
||||||
|
- `strace -f` 观察 `/zvfs` 关键 syscall 已被接管。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- 每新增一组 hook 独立提交,失败时可只回退该组。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: 目录/锁/截断接口补齐(P1)
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
补齐 RocksDB 稳定运行需要的辅助接口。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs_hook.c`
|
||||||
|
- `zvfs/zvfs.c`(必要后端支撑)
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 截断:
|
||||||
|
- `ftruncate/truncate`
|
||||||
|
2. 锁:
|
||||||
|
- `fcntl` 最少支持 `F_SETLK/F_SETLKW/F_UNLCK/F_GETLK`
|
||||||
|
3. 目录:
|
||||||
|
- `mkdir/rmdir/opendir/readdir/closedir`
|
||||||
|
4. 容量提示(可选但建议):
|
||||||
|
- `fallocate/posix_fallocate`(不支持时明确 `EOPNOTSUPP`)
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- RocksDB 多线程基础 workload 可稳定运行(`--threads=4/8`)。
|
||||||
|
- LOCK 文件语义满足单进程多线程正确性。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- `fcntl` 与目录接口可拆成两个子阶段提交。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: 元数据一致性与崩溃恢复增强
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
解决 `zvfs_meta.txt` 全量覆盖、崩溃窗口、恢复不确定问题。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs_hook.c`(meta load/save)
|
||||||
|
- `zvfs/zvfs.c`(关键操作强制 flush 时机)
|
||||||
|
- 文档与测试脚本
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 元数据写入策略:
|
||||||
|
- 从“每次全量覆盖”改为“批量 + 关键点强制刷盘”
|
||||||
|
2. 元数据文件格式增强:
|
||||||
|
- 增加版本号 + CRC
|
||||||
|
3. 恢复路径:
|
||||||
|
- 加载失败时回退到最近完整版本(若实现双文件/快照)
|
||||||
|
4. 关键操作一致性点:
|
||||||
|
- `rename/unlink/truncate/fsync` 后策略明确
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- 人工崩溃注入后(kill -9)可重新打开 DB。
|
||||||
|
- 元数据损坏能被检测并报错,不默默产生脏状态。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- 先做“格式版本化 + 校验”,再做“批量策略”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: 性能增强与观测
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
在正确性稳定后提性能,确保收益可量化。
|
||||||
|
|
||||||
|
### 改动范围
|
||||||
|
- `zvfs/zvfs.c`
|
||||||
|
- 性能脚本与统计输出
|
||||||
|
|
||||||
|
### 任务清单
|
||||||
|
1. 请求对象池 / DMA buffer 复用
|
||||||
|
2. 减少全局锁竞争(热点路径细化锁)
|
||||||
|
3. 元数据 debounce 与批量刷盘参数化
|
||||||
|
4. 增加指标:
|
||||||
|
- op 次数、失败率、平均/尾延迟、队列深度
|
||||||
|
|
||||||
|
### 完成定义(DoD)
|
||||||
|
- 与旧版本对比,多线程 `db_bench` 吞吐显著提升(目标 >1.5x,按实测调整)。
|
||||||
|
- 无新增数据一致性回归。
|
||||||
|
|
||||||
|
### 重入点
|
||||||
|
- 每个优化项独立开关,可单独启停做 A/B。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 阶段间依赖关系(严格)
|
||||||
|
|
||||||
|
1. 必须先完成 Phase 1 再做 Phase 2。
|
||||||
|
2. 必须先完成 Phase 2 再做 Phase 3/4。
|
||||||
|
3. Phase 4(核心 hook)完成后再做 Phase 5(辅助 hook)。
|
||||||
|
4. Phase 6/7 必须基于 Phase 5 稳定分支。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 验收矩阵(执行时打勾)
|
||||||
|
|
||||||
|
### Phase 1
|
||||||
|
- [ ] 编译通过
|
||||||
|
- [ ] `func_test` 通过
|
||||||
|
- [ ] 请求对象已接管 read/write 内部主流程
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
- [ ] worker 模型接管
|
||||||
|
- [ ] 多线程 smoke 测试通过
|
||||||
|
- [ ] 无死锁/退出挂死
|
||||||
|
|
||||||
|
### Phase 3
|
||||||
|
- [ ] `pread/pwrite` 语义正确
|
||||||
|
- [ ] offset 并发污染问题消失
|
||||||
|
|
||||||
|
### Phase 4
|
||||||
|
- [ ] P0 hook 集合已实现
|
||||||
|
- [ ] RocksDB 单线程 workload 跑通
|
||||||
|
|
||||||
|
### Phase 5
|
||||||
|
- [ ] P1 hook 集合已实现
|
||||||
|
- [ ] RocksDB 多线程 workload 稳定
|
||||||
|
|
||||||
|
### Phase 6
|
||||||
|
- [ ] 元数据校验与恢复策略生效
|
||||||
|
- [ ] 崩溃恢复测试通过
|
||||||
|
|
||||||
|
### Phase 7
|
||||||
|
- [ ] 关键指标可观测
|
||||||
|
- [ ] 性能目标达成或给出瓶颈结论
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 风险与应对(执行版)
|
||||||
|
|
||||||
|
1. **接口补齐不完整**
|
||||||
|
- 应对:每阶段跑 `strace -f`,统计 `/zvfs` 相关未接管 syscall。
|
||||||
|
2. **并发死锁**
|
||||||
|
- 应对:固定锁顺序;debug 模式打印锁获取日志。
|
||||||
|
3. **行为回归**
|
||||||
|
- 应对:保留兼容开关(old path)直到该阶段验收通过。
|
||||||
|
4. **性能退化**
|
||||||
|
- 应对:优化项独立开关,逐项 A/B,不一次性合并。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 给 Codex 的执行模板(每阶段复用)
|
||||||
|
|
||||||
|
1. 阅读本阶段“目标/任务/DoD/重入点”
|
||||||
|
2. 只改本阶段文件与最小依赖
|
||||||
|
3. 本地编译 + 对应测试
|
||||||
|
4. 更新“阶段状态”勾选
|
||||||
|
5. 输出:
|
||||||
|
- 改了什么
|
||||||
|
- 测了什么
|
||||||
|
- 还剩什么
|
||||||
|
6. 等待用户确认再进入下一阶段
|
||||||
|
|
||||||
42
test/Makefile
Executable file
42
test/Makefile
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
CC ?= gcc
|
||||||
|
CFLAGS ?= -O2 -Wall -Wextra -std=gnu11
|
||||||
|
|
||||||
|
SRCS := $(sort $(wildcard test_*.c))
|
||||||
|
BIN_DIR ?= bin
|
||||||
|
BIN_NAMES := $(SRCS:.c=)
|
||||||
|
BINS := $(addprefix $(BIN_DIR)/,$(BIN_NAMES))
|
||||||
|
RUN_DIR ?= /tmp/zvfs-test
|
||||||
|
RUN_BINS ?= test_basic test_lseek test_dual_open_same_file test_two_files \
|
||||||
|
test_single_file_perf test_single_file_random_perf \
|
||||||
|
test_single_file_random_noaligned_perf test_write_file test_read_delete_file
|
||||||
|
|
||||||
|
.PHONY: all clean list run-test
|
||||||
|
|
||||||
|
all: $(BINS)
|
||||||
|
|
||||||
|
$(BIN_DIR):
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
$(BIN_DIR)/%: %.c test_utils.h | $(BIN_DIR)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
list:
|
||||||
|
@printf "%s\n" $(BINS)
|
||||||
|
|
||||||
|
run-test: all
|
||||||
|
@mkdir -p $(RUN_DIR)
|
||||||
|
@pass=0; fail=0; \
|
||||||
|
for t in $(RUN_BINS); do \
|
||||||
|
printf "\n[RUN] %s\n" "$$t"; \
|
||||||
|
if ./$(BIN_DIR)/$$t $(RUN_DIR); then \
|
||||||
|
pass=$$((pass + 1)); \
|
||||||
|
else \
|
||||||
|
fail=$$((fail + 1)); \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
printf "\n=== run-test summary: PASS=%d FAIL=%d ===\n" $$pass $$fail; \
|
||||||
|
test $$fail -eq 0
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) $(BINS)
|
||||||
|
-rmdir $(BIN_DIR)
|
||||||
51
test/test_basic.c
Executable file
51
test/test_basic.c
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_basic(const char *path)
|
||||||
|
{
|
||||||
|
printf("\n=== test_basic ===\n");
|
||||||
|
|
||||||
|
printf("open: %s\n", path);
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||||
|
if (fd < 0) { perror("open"); return 1; }
|
||||||
|
|
||||||
|
const char *msg = "ABCDEFGHIJKL";
|
||||||
|
ssize_t w = write(fd, msg, strlen(msg));
|
||||||
|
if (w < 0) { perror("write"); return 2; }
|
||||||
|
printf("write: %zd\n", w);
|
||||||
|
|
||||||
|
const char *msg2 = "MNOPQRSTUVWXYZ";
|
||||||
|
ssize_t w2 = write(fd, msg2, strlen(msg2));
|
||||||
|
if (w2 < 0) { perror("write"); return 2; }
|
||||||
|
printf("write: %zd\n", w2);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY);
|
||||||
|
if (fd < 0) { perror("open R"); return 3; }
|
||||||
|
|
||||||
|
char buf[10];
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
ssize_t r = read(fd, buf, sizeof(buf));
|
||||||
|
if (r < 0) { perror("read"); return 4; }
|
||||||
|
printf("read: %zd bytes: %.*s\n", r, (int)r, buf);
|
||||||
|
|
||||||
|
char buf2[512];
|
||||||
|
memset(buf2, 0, sizeof(buf2));
|
||||||
|
ssize_t r2 = read(fd, buf2, sizeof(buf2));
|
||||||
|
if (r2 < 0) { perror("read"); return 4; }
|
||||||
|
printf("read: %zd bytes: %.*s\n", r2, (int)r2, buf2);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (unlink(path) != 0) { perror("unlink"); return 5; }
|
||||||
|
printf("unlink: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_basic(path);
|
||||||
|
return report_result("test_basic", rc);
|
||||||
|
}
|
||||||
50
test/test_dual_open_same_file.c
Executable file
50
test/test_dual_open_same_file.c
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_dual_open_same_file(const char *path)
|
||||||
|
{
|
||||||
|
printf("\n=== test_dual_open_same_file ===\n");
|
||||||
|
|
||||||
|
int fd_init = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||||
|
if (fd_init < 0) { perror("open init"); return 1; }
|
||||||
|
const char *init = "0123456789";
|
||||||
|
if (write(fd_init, init, 10) != 10) { perror("write init"); return 2; }
|
||||||
|
close(fd_init);
|
||||||
|
|
||||||
|
int fd_w = open(path, O_WRONLY);
|
||||||
|
if (fd_w < 0) { perror("open W"); return 3; }
|
||||||
|
|
||||||
|
int fd_r = open(path, O_RDONLY);
|
||||||
|
if (fd_r < 0) { perror("open R"); return 4; }
|
||||||
|
|
||||||
|
printf("fd_w=%d fd_r=%d\n", fd_w, fd_r);
|
||||||
|
|
||||||
|
if (write(fd_w, "HELLO", 5) != 5) { perror("write"); return 5; }
|
||||||
|
printf("write via fd_w: HELLO (overwrite first 5 bytes)\n");
|
||||||
|
|
||||||
|
char buf[32] = {0};
|
||||||
|
lseek(fd_r, 0, SEEK_SET);
|
||||||
|
ssize_t r = read(fd_r, buf, sizeof(buf));
|
||||||
|
printf("read via fd_r: %zd bytes: %.*s (expect: HELLO56789)\n", r, (int)r, buf);
|
||||||
|
|
||||||
|
lseek(fd_w, 0, SEEK_END);
|
||||||
|
if (write(fd_w, "!!!", 3) != 3) { perror("write append"); return 6; }
|
||||||
|
printf("write append via fd_w: !!!\n");
|
||||||
|
|
||||||
|
lseek(fd_r, 10, SEEK_SET);
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
r = read(fd_r, buf, sizeof(buf));
|
||||||
|
printf("read appended via fd_r: %zd bytes: %.*s (expect: !!!)\n", r, (int)r, buf);
|
||||||
|
|
||||||
|
close(fd_w);
|
||||||
|
close(fd_r);
|
||||||
|
unlink(path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_dual_open_same_file(path);
|
||||||
|
return report_result("test_dual_open_same_file", rc);
|
||||||
|
}
|
||||||
55
test/test_lseek.c
Executable file
55
test/test_lseek.c
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_lseek(const char *path)
|
||||||
|
{
|
||||||
|
printf("\n=== test_lseek ===\n");
|
||||||
|
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||||
|
if (fd < 0) { perror("open"); return 1; }
|
||||||
|
|
||||||
|
const char *alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
if (write(fd, alpha, 26) != 26) { perror("write"); return 2; }
|
||||||
|
printf("write 26 bytes: %s\n", alpha);
|
||||||
|
|
||||||
|
off_t pos = lseek(fd, 0, SEEK_SET);
|
||||||
|
printf("lseek SEEK_SET 0 -> %ld\n", (long)pos);
|
||||||
|
char buf[32] = {0};
|
||||||
|
ssize_t r = read(fd, buf, 5);
|
||||||
|
printf("read 5 bytes: %.*s (expect: ABCDE)\n", (int)r, buf);
|
||||||
|
|
||||||
|
pos = lseek(fd, 3, SEEK_CUR);
|
||||||
|
printf("lseek SEEK_CUR +3 -> %ld\n", (long)pos);
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
r = read(fd, buf, 5);
|
||||||
|
printf("read 5 bytes: %.*s (expect: IJKLM)\n", (int)r, buf);
|
||||||
|
|
||||||
|
pos = lseek(fd, -5, SEEK_END);
|
||||||
|
printf("lseek SEEK_END -5 -> %ld\n", (long)pos);
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
r = read(fd, buf, 10);
|
||||||
|
printf("read %zd bytes: %.*s (expect: VWXYZ)\n", r, (int)r, buf);
|
||||||
|
|
||||||
|
pos = lseek(fd, 30, SEEK_SET);
|
||||||
|
printf("lseek SEEK_SET 30 -> %ld\n", (long)pos);
|
||||||
|
if (write(fd, "!", 1) != 1) { perror("write hole"); return 3; }
|
||||||
|
|
||||||
|
lseek(fd, 26, SEEK_SET);
|
||||||
|
memset(buf, 0xAA, sizeof(buf));
|
||||||
|
r = read(fd, buf, 5);
|
||||||
|
printf("read hole+1: %zd bytes, hole[0]=%02X hole[1]=%02X hole[2]=%02X "
|
||||||
|
"hole[3]=%02X last='%c' (expect: 00 00 00 00 '!')\n",
|
||||||
|
r, (unsigned char)buf[0], (unsigned char)buf[1],
|
||||||
|
(unsigned char)buf[2], (unsigned char)buf[3], buf[4]);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
unlink(path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_lseek(path);
|
||||||
|
return report_result("test_lseek", rc);
|
||||||
|
}
|
||||||
31
test/test_read_delete_file.c
Executable file
31
test/test_read_delete_file.c
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_read_delete_file(const char *path)
|
||||||
|
{
|
||||||
|
printf("\n=== test_read_delete_file ===\n");
|
||||||
|
|
||||||
|
int fd = open(path, O_RDONLY);
|
||||||
|
if (fd < 0) { perror("open"); return 1; }
|
||||||
|
printf("open: %s fd=%d\n", path, fd);
|
||||||
|
|
||||||
|
char buf[256] = {0};
|
||||||
|
ssize_t r = read(fd, buf, sizeof(buf));
|
||||||
|
if (r < 0) { perror("read"); close(fd); return 2; }
|
||||||
|
printf("read: %zd bytes: %.*s\n", r, (int)r, buf);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
printf("close: ok\n");
|
||||||
|
|
||||||
|
if (unlink(path) != 0) { perror("unlink"); return 3; }
|
||||||
|
printf("unlink: ok\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_read_delete_file(path);
|
||||||
|
return report_result("test_read_delete_file", rc);
|
||||||
|
}
|
||||||
92
test/test_single_file_perf.c
Executable file
92
test/test_single_file_perf.c
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_single_file_perf(const char *path)
|
||||||
|
{
|
||||||
|
size_t io_size = 128 * 1024;
|
||||||
|
size_t max_size = 2ULL * 1024 * 1024 * 1024;
|
||||||
|
size_t max_count = max_size / io_size;
|
||||||
|
int test_sec = 10;
|
||||||
|
int direct = 0;
|
||||||
|
|
||||||
|
printf("\n=== test_single_file_perf ===\n");
|
||||||
|
printf("Path : %s\n", path);
|
||||||
|
printf("IO size : %zu KB\n", io_size / 1024);
|
||||||
|
printf("Max file: %zu MB\n", max_size / 1024 / 1024);
|
||||||
|
printf("Duration: %d sec\n", test_sec);
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
char *buf = aligned_alloc(4096, io_size);
|
||||||
|
if (!buf) { perror("aligned_alloc"); return 1; }
|
||||||
|
memset(buf, 'A', io_size);
|
||||||
|
|
||||||
|
struct timespec t1, t2, now;
|
||||||
|
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
||||||
|
if (fd < 0) { perror("open write"); free(buf); return 1; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
size_t wcount = 0;
|
||||||
|
size_t wpos = 0;
|
||||||
|
do {
|
||||||
|
if (wpos >= max_count) {
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
wpos = 0;
|
||||||
|
}
|
||||||
|
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
||||||
|
perror("write");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
wcount++;
|
||||||
|
wpos++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double wsec = time_diff_sec(t1, t2);
|
||||||
|
double wmb = (double)(wcount * io_size) / (1024.0 * 1024.0);
|
||||||
|
printf("\nWRITE:\n");
|
||||||
|
printf(" total : %.1f MB\n", wmb);
|
||||||
|
printf(" time : %.3f sec\n", wsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | direct);
|
||||||
|
if (fd < 0) { perror("open read"); free(buf); return 3; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
size_t rcount = 0;
|
||||||
|
do {
|
||||||
|
ssize_t r = read(fd, buf, io_size);
|
||||||
|
if (r <= 0) {
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rcount++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double rsec = time_diff_sec(t1, t2);
|
||||||
|
double rmb = (double)(rcount * io_size) / (1024.0 * 1024.0);
|
||||||
|
printf("\nREAD:\n");
|
||||||
|
printf(" total : %.1f MB\n", rmb);
|
||||||
|
printf(" time : %.3f sec\n", rsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_single_file_perf(path);
|
||||||
|
return report_result("test_single_file_perf", rc);
|
||||||
|
}
|
||||||
116
test/test_single_file_random_noaligned_perf.c
Executable file
116
test/test_single_file_random_noaligned_perf.c
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_single_file_random_noaligned_perf(const char *path)
|
||||||
|
{
|
||||||
|
size_t io_size = 128 * 1024;
|
||||||
|
size_t max_size = 2ULL * 1024 * 1024 * 1024;
|
||||||
|
int test_sec = 10;
|
||||||
|
int direct = 0;
|
||||||
|
|
||||||
|
printf("\n=== test_single_file_random_noaligned_perf ===\n");
|
||||||
|
printf("Path : %s\n", path);
|
||||||
|
printf("IO size : %zu KB\n", io_size / 1024);
|
||||||
|
printf("Range : %zu MB\n", max_size / 1024 / 1024);
|
||||||
|
printf("Duration: %d sec\n", test_sec);
|
||||||
|
|
||||||
|
srand(0x1234);
|
||||||
|
|
||||||
|
char *buf = aligned_alloc(4096, io_size);
|
||||||
|
if (!buf) { perror("aligned_alloc"); return 1; }
|
||||||
|
memset(buf, 'A', io_size);
|
||||||
|
|
||||||
|
struct timespec t1, t2, now;
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
||||||
|
if (fd < 0) { perror("open rand write"); free(buf); return 2; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
|
||||||
|
size_t wcount = 0;
|
||||||
|
do {
|
||||||
|
off_t offset = (off_t)(rand() % (max_size - io_size));
|
||||||
|
|
||||||
|
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||||
|
perror("lseek rand write");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
||||||
|
perror("rand write");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcount++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double wsec = time_diff_sec(t1, t2);
|
||||||
|
double wmb = (double)wcount * io_size / 1024 / 1024;
|
||||||
|
|
||||||
|
printf("\nRANDOM WRITE:\n");
|
||||||
|
printf(" total : %.1f MB\n", wmb);
|
||||||
|
printf(" time : %.3f sec\n", wsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | direct);
|
||||||
|
if (fd < 0) { perror("open rand read"); free(buf); return 5; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
|
||||||
|
size_t rcount = 0;
|
||||||
|
do {
|
||||||
|
off_t offset = (off_t)(rand() % (max_size - io_size));
|
||||||
|
|
||||||
|
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||||
|
perror("lseek rand read");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t r = read(fd, buf, io_size);
|
||||||
|
if (r < 0) {
|
||||||
|
perror("rand read");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcount++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double rsec = time_diff_sec(t1, t2);
|
||||||
|
double rmb = (double)rcount * io_size / 1024 / 1024;
|
||||||
|
|
||||||
|
printf("\nRANDOM READ:\n");
|
||||||
|
printf(" total : %.1f MB\n", rmb);
|
||||||
|
printf(" time : %.3f sec\n", rsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_single_file_random_noaligned_perf(path);
|
||||||
|
return report_result("test_single_file_random_noaligned_perf", rc);
|
||||||
|
}
|
||||||
119
test/test_single_file_random_perf.c
Executable file
119
test/test_single_file_random_perf.c
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_single_file_random_perf(const char *path)
|
||||||
|
{
|
||||||
|
size_t io_size = 128 * 1024;
|
||||||
|
size_t max_size = 2ULL * 1024 * 1024 * 1024;
|
||||||
|
size_t max_count = max_size / io_size;
|
||||||
|
int test_sec = 10;
|
||||||
|
int direct = 0;
|
||||||
|
|
||||||
|
printf("\n=== test_single_file_random_perf ===\n");
|
||||||
|
printf("Path : %s\n", path);
|
||||||
|
printf("IO size : %zu KB\n", io_size / 1024);
|
||||||
|
printf("Range : %zu MB\n", max_size / 1024 / 1024);
|
||||||
|
printf("Duration: %d sec\n", test_sec);
|
||||||
|
|
||||||
|
srand(0x1234);
|
||||||
|
|
||||||
|
char *buf = aligned_alloc(4096, io_size);
|
||||||
|
if (!buf) { perror("aligned_alloc"); return 1; }
|
||||||
|
memset(buf, 'A', io_size);
|
||||||
|
|
||||||
|
struct timespec t1, t2, now;
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
||||||
|
if (fd < 0) { perror("open rand write"); free(buf); return 2; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
|
||||||
|
size_t wcount = 0;
|
||||||
|
do {
|
||||||
|
size_t blk = rand() % max_count;
|
||||||
|
off_t offset = (off_t)blk * io_size;
|
||||||
|
|
||||||
|
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||||
|
perror("lseek rand write");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
||||||
|
perror("rand write");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcount++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double wsec = time_diff_sec(t1, t2);
|
||||||
|
double wmb = (double)wcount * io_size / 1024 / 1024;
|
||||||
|
|
||||||
|
printf("\nRANDOM WRITE:\n");
|
||||||
|
printf(" total : %.1f MB\n", wmb);
|
||||||
|
printf(" time : %.3f sec\n", wsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | direct);
|
||||||
|
if (fd < 0) { perror("open rand read"); free(buf); return 5; }
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
|
||||||
|
size_t rcount = 0;
|
||||||
|
do {
|
||||||
|
size_t blk = rand() % max_count;
|
||||||
|
off_t offset = (off_t)blk * io_size;
|
||||||
|
|
||||||
|
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||||
|
perror("lseek rand read");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t r = read(fd, buf, io_size);
|
||||||
|
if (r < 0) {
|
||||||
|
perror("rand read");
|
||||||
|
close(fd);
|
||||||
|
free(buf);
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcount++;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
} while (time_diff_sec(t1, now) < test_sec);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t2);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
double rsec = time_diff_sec(t1, t2);
|
||||||
|
double rmb = (double)rcount * io_size / 1024 / 1024;
|
||||||
|
|
||||||
|
printf("\nRANDOM READ:\n");
|
||||||
|
printf(" total : %.1f MB\n", rmb);
|
||||||
|
printf(" time : %.3f sec\n", rsec);
|
||||||
|
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
||||||
|
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_single_file_random_perf(path);
|
||||||
|
return report_result("test_single_file_random_perf", rc);
|
||||||
|
}
|
||||||
78
test/test_two_files.c
Executable file
78
test/test_two_files.c
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_two_files(const char *path_a, const char *path_b)
|
||||||
|
{
|
||||||
|
printf("\n=== test_two_files ===\n");
|
||||||
|
|
||||||
|
int fd_a = open(path_a, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||||
|
if (fd_a < 0) { perror("open A"); return 1; }
|
||||||
|
|
||||||
|
int fd_b = open(path_b, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||||
|
if (fd_b < 0) { perror("open B"); return 2; }
|
||||||
|
|
||||||
|
printf("fd_a=%d fd_b=%d\n", fd_a, fd_b);
|
||||||
|
|
||||||
|
const char *data_a = "File-A: Hello World!";
|
||||||
|
const char *data_b = "File-B: Goodbye World!";
|
||||||
|
if (write(fd_a, data_a, strlen(data_a)) < 0) { perror("write A"); return 3; }
|
||||||
|
if (write(fd_b, data_b, strlen(data_b)) < 0) { perror("write B"); return 4; }
|
||||||
|
printf("write A: %s\n", data_a);
|
||||||
|
printf("write B: %s\n", data_b);
|
||||||
|
|
||||||
|
lseek(fd_a, 0, SEEK_SET);
|
||||||
|
lseek(fd_b, 0, SEEK_SET);
|
||||||
|
|
||||||
|
char buf_a[64] = {0};
|
||||||
|
char buf_b[64] = {0};
|
||||||
|
ssize_t r_a = read(fd_a, buf_a, sizeof(buf_a));
|
||||||
|
ssize_t r_b = read(fd_b, buf_b, sizeof(buf_b));
|
||||||
|
|
||||||
|
printf("read A: %zd bytes: %.*s\n", r_a, (int)r_a, buf_a);
|
||||||
|
printf("read B: %zd bytes: %.*s\n", r_b, (int)r_b, buf_b);
|
||||||
|
|
||||||
|
int ok = 1;
|
||||||
|
if (strncmp(buf_a, data_a, strlen(data_a)) != 0) {
|
||||||
|
printf("FAIL: A content mismatch!\n");
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if (strncmp(buf_b, data_b, strlen(data_b)) != 0) {
|
||||||
|
printf("FAIL: B content mismatch!\n");
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
printf("PASS: both files read back correctly\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
lseek(fd_a, 0, SEEK_END);
|
||||||
|
if (write(fd_a, "[A-TAIL]", 8) != 8) { perror("append A"); return 5; }
|
||||||
|
|
||||||
|
lseek(fd_b, 8, SEEK_SET);
|
||||||
|
if (write(fd_b, "Hi! ", 7) != 7) { perror("overwrite B"); return 6; }
|
||||||
|
|
||||||
|
lseek(fd_a, 0, SEEK_SET);
|
||||||
|
lseek(fd_b, 0, SEEK_SET);
|
||||||
|
memset(buf_a, 0, sizeof(buf_a));
|
||||||
|
memset(buf_b, 0, sizeof(buf_b));
|
||||||
|
r_a = read(fd_a, buf_a, sizeof(buf_a));
|
||||||
|
r_b = read(fd_b, buf_b, sizeof(buf_b));
|
||||||
|
printf("after cross-write:\n");
|
||||||
|
printf(" A: %.*s\n", (int)r_a, buf_a);
|
||||||
|
printf(" B: %.*s\n", (int)r_b, buf_b);
|
||||||
|
|
||||||
|
close(fd_a);
|
||||||
|
close(fd_b);
|
||||||
|
unlink(path_a);
|
||||||
|
unlink(path_b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path_a[PATH_MAX];
|
||||||
|
char path_b[PATH_MAX];
|
||||||
|
const char *dir = argc >= 2 ? argv[1] : NULL;
|
||||||
|
make_path(path_a, sizeof(path_a), dir, "file_a.dat");
|
||||||
|
make_path(path_b, sizeof(path_b), dir, "file_b.dat");
|
||||||
|
int rc = test_two_files(path_a, path_b);
|
||||||
|
return report_result("test_two_files", rc);
|
||||||
|
}
|
||||||
40
test/test_utils.h
Executable file
40
test/test_utils.h
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef TEST_UTILS_H
|
||||||
|
#define TEST_UTILS_H
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef PATH_MAX
|
||||||
|
#define PATH_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline double time_diff_sec(struct timespec a, struct timespec b)
|
||||||
|
{
|
||||||
|
return (b.tv_sec - a.tv_sec) + (b.tv_nsec - a.tv_nsec) / 1000000000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void make_path(char *out, size_t out_sz, const char *dir, const char *name)
|
||||||
|
{
|
||||||
|
if (dir && dir[0] != 0) {
|
||||||
|
snprintf(out, out_sz, "%s/%s", dir, name);
|
||||||
|
} else {
|
||||||
|
snprintf(out, out_sz, "/tmp/%s", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int report_result(const char *name, int rc)
|
||||||
|
{
|
||||||
|
printf("\n=== %s %s ===\n", name, rc == 0 ? "PASSED" : "FAILED");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
27
test/test_write_file.c
Executable file
27
test/test_write_file.c
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
static int test_write_file(const char *path)
|
||||||
|
{
|
||||||
|
printf("\n=== test_write_file ===\n");
|
||||||
|
|
||||||
|
int fd = open(path, O_CREAT | O_RDWR, 0644);
|
||||||
|
if (fd < 0) { perror("open"); return 1; }
|
||||||
|
printf("open: %s fd=%d\n", path, fd);
|
||||||
|
|
||||||
|
const char *msg = "Hello, zvfs!";
|
||||||
|
ssize_t w = write(fd, msg, strlen(msg));
|
||||||
|
if (w < 0) { perror("write"); close(fd); return 2; }
|
||||||
|
printf("write: %zd bytes: %s\n", w, msg);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
printf("close: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat");
|
||||||
|
int rc = test_write_file(path);
|
||||||
|
return report_result("test_write_file", rc);
|
||||||
|
}
|
||||||
644
zvfs/func_test.c
644
zvfs/func_test.c
@@ -1,644 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
static double time_diff_sec(struct timespec a, struct timespec b)
|
|
||||||
{
|
|
||||||
return (b.tv_sec - a.tv_sec) +
|
|
||||||
(b.tv_nsec - a.tv_nsec) / 1000000000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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; /* 最大 2GB,循环覆写 */
|
|
||||||
size_t max_count = max_size / io_size;
|
|
||||||
int test_sec = 10;
|
|
||||||
|
|
||||||
int direct = 0;
|
|
||||||
|
|
||||||
printf("\n=== test_single_file_perf ===\n");
|
|
||||||
printf("Path : %s\n", path);
|
|
||||||
printf("IO size : %zu KB\n", io_size / 1024);
|
|
||||||
printf("Max file: %zu MB\n", max_size / 1024 / 1024);
|
|
||||||
printf("Duration: %d sec\n", test_sec);
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
char *buf = aligned_alloc(4096, io_size);
|
|
||||||
if (!buf) { perror("aligned_alloc"); return 1; }
|
|
||||||
memset(buf, 'A', io_size);
|
|
||||||
|
|
||||||
struct timespec t1, t2, now;
|
|
||||||
|
|
||||||
/* ================= WRITE ================= */
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
|
||||||
if (fd < 0) { perror("open write"); free(buf); return 1; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
size_t wcount = 0;
|
|
||||||
size_t wpos = 0; /* 当前写位置(以块为单位) */
|
|
||||||
do {
|
|
||||||
/* 超过最大文件大小,seek 回头循环覆写 */
|
|
||||||
if (wpos >= max_count) {
|
|
||||||
lseek(fd, 0, SEEK_SET);
|
|
||||||
wpos = 0;
|
|
||||||
}
|
|
||||||
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
|
||||||
perror("write");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
wcount++;
|
|
||||||
wpos++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double wsec = time_diff_sec(t1, t2);
|
|
||||||
double wmb = (double)(wcount * io_size) / (1024.0 * 1024.0);
|
|
||||||
printf("\nWRITE:\n");
|
|
||||||
printf(" total : %.1f MB\n", wmb);
|
|
||||||
printf(" time : %.3f sec\n", wsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
|
||||||
|
|
||||||
/* ================= READ ================= */
|
|
||||||
fd = open(path, O_RDONLY | direct);
|
|
||||||
if (fd < 0) { perror("open read"); free(buf); return 3; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
size_t rcount = 0;
|
|
||||||
do {
|
|
||||||
ssize_t r = read(fd, buf, io_size);
|
|
||||||
if (r <= 0) {
|
|
||||||
lseek(fd, 0, SEEK_SET);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rcount++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double rsec = time_diff_sec(t1, t2);
|
|
||||||
double rmb = (double)(rcount * io_size) / (1024.0 * 1024.0);
|
|
||||||
printf("\nREAD:\n");
|
|
||||||
printf(" total : %.1f MB\n", rmb);
|
|
||||||
printf(" time : %.3f sec\n", rsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int test_single_file_random_perf(const char *path)
|
|
||||||
{
|
|
||||||
size_t io_size = 128 * 1024;
|
|
||||||
size_t max_size = 2ULL * 1024 * 1024 * 1024;
|
|
||||||
size_t max_count = max_size / io_size;
|
|
||||||
int test_sec = 10;
|
|
||||||
int direct = 0;
|
|
||||||
|
|
||||||
printf("\n=== test_single_file_random_perf ===\n");
|
|
||||||
printf("Path : %s\n", path);
|
|
||||||
printf("IO size : %zu KB\n", io_size / 1024);
|
|
||||||
printf("Range : %zu MB\n", max_size / 1024 / 1024);
|
|
||||||
printf("Duration: %d sec\n", test_sec);
|
|
||||||
|
|
||||||
srand(0x1234);
|
|
||||||
|
|
||||||
char *buf = aligned_alloc(4096, io_size);
|
|
||||||
if (!buf) { perror("aligned_alloc"); return 1; }
|
|
||||||
memset(buf, 'A', io_size);
|
|
||||||
|
|
||||||
struct timespec t1, t2, now;
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
|
|
||||||
/* ================= RANDOM WRITE ================= */
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
|
||||||
if (fd < 0) { perror("open rand write"); free(buf); return 2; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
|
|
||||||
size_t wcount = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
size_t blk = rand() % max_count;
|
|
||||||
off_t offset = (off_t)blk * io_size;
|
|
||||||
|
|
||||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
|
||||||
perror("lseek rand write");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
|
||||||
perror("rand write");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
wcount++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double wsec = time_diff_sec(t1, t2);
|
|
||||||
double wmb = (double)wcount * io_size / 1024 / 1024;
|
|
||||||
|
|
||||||
printf("\nRANDOM WRITE:\n");
|
|
||||||
printf(" total : %.1f MB\n", wmb);
|
|
||||||
printf(" time : %.3f sec\n", wsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
|
||||||
|
|
||||||
|
|
||||||
/* ================= RANDOM READ ================= */
|
|
||||||
fd = open(path, O_RDONLY | direct);
|
|
||||||
if (fd < 0) { perror("open rand read"); free(buf); return 5; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
|
|
||||||
size_t rcount = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
size_t blk = rand() % max_count;
|
|
||||||
off_t offset = (off_t)blk * io_size;
|
|
||||||
|
|
||||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
|
||||||
perror("lseek rand read");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t r = read(fd, buf, io_size);
|
|
||||||
if (r < 0) {
|
|
||||||
perror("rand read");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
rcount++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double rsec = time_diff_sec(t1, t2);
|
|
||||||
double rmb = (double)rcount * io_size / 1024 / 1024;
|
|
||||||
|
|
||||||
printf("\nRANDOM READ:\n");
|
|
||||||
printf(" total : %.1f MB\n", rmb);
|
|
||||||
printf(" time : %.3f sec\n", rsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int test_single_file_random_noaligned_perf(const char *path)
|
|
||||||
{
|
|
||||||
size_t io_size = 128 * 1024;
|
|
||||||
size_t max_size = 2ULL * 1024 * 1024 * 1024;
|
|
||||||
size_t max_count = max_size / io_size;
|
|
||||||
int test_sec = 10;
|
|
||||||
int direct = 0;
|
|
||||||
|
|
||||||
printf("\n=== test_single_file_random_perf ===\n");
|
|
||||||
printf("Path : %s\n", path);
|
|
||||||
printf("IO size : %zu KB\n", io_size / 1024);
|
|
||||||
printf("Range : %zu MB\n", max_size / 1024 / 1024);
|
|
||||||
printf("Duration: %d sec\n", test_sec);
|
|
||||||
|
|
||||||
srand(0x1234);
|
|
||||||
|
|
||||||
char *buf = aligned_alloc(4096, io_size);
|
|
||||||
if (!buf) { perror("aligned_alloc"); return 1; }
|
|
||||||
memset(buf, 'A', io_size);
|
|
||||||
|
|
||||||
struct timespec t1, t2, now;
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
|
|
||||||
/* ================= RANDOM WRITE ================= */
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR | direct, 0644);
|
|
||||||
if (fd < 0) { perror("open rand write"); free(buf); return 2; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
|
|
||||||
size_t wcount = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
off_t offset = (off_t)(rand() % (max_size - io_size));
|
|
||||||
|
|
||||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
|
||||||
perror("lseek rand write");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, buf, io_size) != (ssize_t)io_size) {
|
|
||||||
perror("rand write");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
wcount++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double wsec = time_diff_sec(t1, t2);
|
|
||||||
double wmb = (double)wcount * io_size / 1024 / 1024;
|
|
||||||
|
|
||||||
printf("\nRANDOM WRITE:\n");
|
|
||||||
printf(" total : %.1f MB\n", wmb);
|
|
||||||
printf(" time : %.3f sec\n", wsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", wcount / wsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", wmb / wsec);
|
|
||||||
|
|
||||||
|
|
||||||
/* ================= RANDOM READ ================= */
|
|
||||||
fd = open(path, O_RDONLY | direct);
|
|
||||||
if (fd < 0) { perror("open rand read"); free(buf); return 5; }
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
||||||
|
|
||||||
size_t rcount = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
off_t offset = (off_t)(rand() % (max_size - io_size));
|
|
||||||
|
|
||||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
|
||||||
perror("lseek rand read");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t r = read(fd, buf, io_size);
|
|
||||||
if (r < 0) {
|
|
||||||
perror("rand read");
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
rcount++;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
|
|
||||||
} while (time_diff_sec(t1, now) < test_sec);
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t2);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
double rsec = time_diff_sec(t1, t2);
|
|
||||||
double rmb = (double)rcount * io_size / 1024 / 1024;
|
|
||||||
|
|
||||||
printf("\nRANDOM READ:\n");
|
|
||||||
printf(" total : %.1f MB\n", rmb);
|
|
||||||
printf(" time : %.3f sec\n", rsec);
|
|
||||||
printf(" IOPS : %.0f ops/sec\n", rcount / rsec);
|
|
||||||
printf(" BW : %.2f MB/s\n", rmb / rsec);
|
|
||||||
|
|
||||||
unlink(path);
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Test 1: 原有基础测试 */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
static int test_basic(const char *path)
|
|
||||||
{
|
|
||||||
printf("\n=== test_basic ===\n");
|
|
||||||
|
|
||||||
printf("open: %s\n", path);
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
||||||
if (fd < 0) { perror("open"); return 1; }
|
|
||||||
|
|
||||||
const char *msg = "ABCDEFGHIJKL";
|
|
||||||
ssize_t w = write(fd, msg, strlen(msg));
|
|
||||||
if (w < 0) { perror("write"); return 2; }
|
|
||||||
printf("write: %zd\n", w);
|
|
||||||
|
|
||||||
const char *msg2 = "MNOPQRSTUVWXYZ";
|
|
||||||
ssize_t w2 = write(fd, msg2, strlen(msg2));
|
|
||||||
if (w2 < 0) { perror("write"); return 2; }
|
|
||||||
printf("write: %zd\n", w2);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
fd = open(path, O_RDONLY);
|
|
||||||
if (fd < 0) { perror("open R"); return 3; }
|
|
||||||
|
|
||||||
char buf[10];
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
ssize_t r = read(fd, buf, sizeof(buf));
|
|
||||||
if (r < 0) { perror("read"); return 4; }
|
|
||||||
printf("read: %zd bytes: %.*s\n", r, (int)r, buf);
|
|
||||||
|
|
||||||
char buf2[512];
|
|
||||||
memset(buf2, 0, sizeof(buf2));
|
|
||||||
ssize_t r2 = read(fd, buf2, sizeof(buf2));
|
|
||||||
if (r2 < 0) { perror("read"); return 4; }
|
|
||||||
printf("read: %zd bytes: %.*s\n", r2, (int)r2, buf2);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
if (unlink(path) != 0) { perror("unlink"); return 5; }
|
|
||||||
printf("unlink: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Test 2: lseek */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
static int test_lseek(const char *path)
|
|
||||||
{
|
|
||||||
printf("\n=== test_lseek ===\n");
|
|
||||||
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
||||||
if (fd < 0) { perror("open"); return 1; }
|
|
||||||
|
|
||||||
/* 写入 26 个字母 */
|
|
||||||
const char *alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
if (write(fd, alpha, 26) != 26) { perror("write"); return 2; }
|
|
||||||
printf("write 26 bytes: %s\n", alpha);
|
|
||||||
|
|
||||||
/* SEEK_SET: 跳到第 0 字节,读 5 个 */
|
|
||||||
off_t pos = lseek(fd, 0, SEEK_SET);
|
|
||||||
printf("lseek SEEK_SET 0 -> %ld\n", (long)pos);
|
|
||||||
char buf[32] = {0};
|
|
||||||
ssize_t r = read(fd, buf, 5);
|
|
||||||
printf("read 5 bytes: %.*s (expect: ABCDE)\n", (int)r, buf);
|
|
||||||
|
|
||||||
/* SEEK_CUR: 从当前位置(5)再向前跳 3 个,应读到第 8 字节 'I' */
|
|
||||||
pos = lseek(fd, 3, SEEK_CUR);
|
|
||||||
printf("lseek SEEK_CUR +3 -> %ld\n", (long)pos);
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
r = read(fd, buf, 5);
|
|
||||||
printf("read 5 bytes: %.*s (expect: IJKLM)\n", (int)r, buf);
|
|
||||||
|
|
||||||
/* SEEK_END: 从文件尾往回 5 字节,读到最后 5 个字母 */
|
|
||||||
pos = lseek(fd, -5, SEEK_END);
|
|
||||||
printf("lseek SEEK_END -5 -> %ld\n", (long)pos);
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
r = read(fd, buf, 10); /* 最多只剩 5 字节 */
|
|
||||||
printf("read %zd bytes: %.*s (expect: VWXYZ)\n", r, (int)r, buf);
|
|
||||||
|
|
||||||
/* SEEK_SET 超过文件末尾:写一个字节,制造"空洞" */
|
|
||||||
pos = lseek(fd, 30, SEEK_SET);
|
|
||||||
printf("lseek SEEK_SET 30 -> %ld\n", (long)pos);
|
|
||||||
if (write(fd, "!", 1) != 1) { perror("write hole"); return 3; }
|
|
||||||
|
|
||||||
/* 重新读回空洞区域,[26,29] 应为 0x00 */
|
|
||||||
lseek(fd, 26, SEEK_SET);
|
|
||||||
memset(buf, 0xAA, sizeof(buf));
|
|
||||||
r = read(fd, buf, 5);
|
|
||||||
printf("read hole+1: %zd bytes, hole[0]=%02X hole[1]=%02X hole[2]=%02X "
|
|
||||||
"hole[3]=%02X last='%c' (expect: 00 00 00 00 '!')\n",
|
|
||||||
r, (unsigned char)buf[0], (unsigned char)buf[1],
|
|
||||||
(unsigned char)buf[2], (unsigned char)buf[3], buf[4]);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
unlink(path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Test 3: 同一文件被两个 fd 同时打开(一读一写) */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
static int test_dual_open_same_file(const char *path)
|
|
||||||
{
|
|
||||||
printf("\n=== test_dual_open_same_file ===\n");
|
|
||||||
|
|
||||||
/* 先创建并写入初始内容 */
|
|
||||||
int fd_init = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
||||||
if (fd_init < 0) { perror("open init"); return 1; }
|
|
||||||
const char *init = "0123456789";
|
|
||||||
if (write(fd_init, init, 10) != 10) { perror("write init"); return 2; }
|
|
||||||
close(fd_init);
|
|
||||||
|
|
||||||
/* 用写句柄和读句柄分别打开同一文件 */
|
|
||||||
int fd_w = open(path, O_WRONLY);
|
|
||||||
if (fd_w < 0) { perror("open W"); return 3; }
|
|
||||||
|
|
||||||
int fd_r = open(path, O_RDONLY);
|
|
||||||
if (fd_r < 0) { perror("open R"); return 4; }
|
|
||||||
|
|
||||||
printf("fd_w=%d fd_r=%d\n", fd_w, fd_r);
|
|
||||||
|
|
||||||
/* 通过写句柄覆写前 5 字节 */
|
|
||||||
if (write(fd_w, "HELLO", 5) != 5) { perror("write"); return 5; }
|
|
||||||
printf("write via fd_w: HELLO (overwrite first 5 bytes)\n");
|
|
||||||
|
|
||||||
/* 通过读句柄从头读回全部内容 */
|
|
||||||
char buf[32] = {0};
|
|
||||||
lseek(fd_r, 0, SEEK_SET);
|
|
||||||
ssize_t r = read(fd_r, buf, sizeof(buf));
|
|
||||||
printf("read via fd_r: %zd bytes: %.*s (expect: HELLO56789)\n",
|
|
||||||
r, (int)r, buf);
|
|
||||||
|
|
||||||
/* 继续用写句柄追加 */
|
|
||||||
lseek(fd_w, 0, SEEK_END);
|
|
||||||
if (write(fd_w, "!!!", 3) != 3) { perror("write append"); return 6; }
|
|
||||||
printf("write append via fd_w: !!!\n");
|
|
||||||
|
|
||||||
/* 读句柄读追加部分 */
|
|
||||||
lseek(fd_r, 10, SEEK_SET);
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
r = read(fd_r, buf, sizeof(buf));
|
|
||||||
printf("read appended via fd_r: %zd bytes: %.*s (expect: !!!)\n",
|
|
||||||
r, (int)r, buf);
|
|
||||||
|
|
||||||
close(fd_w);
|
|
||||||
close(fd_r);
|
|
||||||
unlink(path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Test 4: 两个不同文件同时打开,分别读写 */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
static int test_two_files(const char *path_a, const char *path_b)
|
|
||||||
{
|
|
||||||
printf("\n=== test_two_files ===\n");
|
|
||||||
|
|
||||||
int fd_a = open(path_a, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
||||||
if (fd_a < 0) { perror("open A"); return 1; }
|
|
||||||
|
|
||||||
int fd_b = open(path_b, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
||||||
if (fd_b < 0) { perror("open B"); return 2; }
|
|
||||||
|
|
||||||
printf("fd_a=%d fd_b=%d\n", fd_a, fd_b);
|
|
||||||
|
|
||||||
/* 分别写入不同内容 */
|
|
||||||
const char *data_a = "File-A: Hello World!";
|
|
||||||
const char *data_b = "File-B: Goodbye World!";
|
|
||||||
if (write(fd_a, data_a, strlen(data_a)) < 0) { perror("write A"); return 3; }
|
|
||||||
if (write(fd_b, data_b, strlen(data_b)) < 0) { perror("write B"); return 4; }
|
|
||||||
printf("write A: %s\n", data_a);
|
|
||||||
printf("write B: %s\n", data_b);
|
|
||||||
|
|
||||||
/* 各自 seek 回头读取,验证内容互不干扰 */
|
|
||||||
lseek(fd_a, 0, SEEK_SET);
|
|
||||||
lseek(fd_b, 0, SEEK_SET);
|
|
||||||
|
|
||||||
char buf_a[64] = {0};
|
|
||||||
char buf_b[64] = {0};
|
|
||||||
ssize_t r_a = read(fd_a, buf_a, sizeof(buf_a));
|
|
||||||
ssize_t r_b = read(fd_b, buf_b, sizeof(buf_b));
|
|
||||||
|
|
||||||
printf("read A: %zd bytes: %.*s\n", r_a, (int)r_a, buf_a);
|
|
||||||
printf("read B: %zd bytes: %.*s\n", r_b, (int)r_b, buf_b);
|
|
||||||
|
|
||||||
int ok = 1;
|
|
||||||
if (strncmp(buf_a, data_a, strlen(data_a)) != 0) {
|
|
||||||
printf("FAIL: A content mismatch!\n"); ok = 0;
|
|
||||||
}
|
|
||||||
if (strncmp(buf_b, data_b, strlen(data_b)) != 0) {
|
|
||||||
printf("FAIL: B content mismatch!\n"); ok = 0;
|
|
||||||
}
|
|
||||||
if (ok) printf("PASS: both files read back correctly\n");
|
|
||||||
|
|
||||||
/* 交叉写:向 A 追加,向 B 中段覆写,再各自读回验证 */
|
|
||||||
lseek(fd_a, 0, SEEK_END);
|
|
||||||
write(fd_a, "[A-TAIL]", 8);
|
|
||||||
|
|
||||||
lseek(fd_b, 8, SEEK_SET); /* "File-B: " 之后 */
|
|
||||||
write(fd_b, "Hi! ", 7); /* 覆写 "Goodbye" */
|
|
||||||
|
|
||||||
lseek(fd_a, 0, SEEK_SET);
|
|
||||||
lseek(fd_b, 0, SEEK_SET);
|
|
||||||
memset(buf_a, 0, sizeof(buf_a));
|
|
||||||
memset(buf_b, 0, sizeof(buf_b));
|
|
||||||
r_a = read(fd_a, buf_a, sizeof(buf_a));
|
|
||||||
r_b = read(fd_b, buf_b, sizeof(buf_b));
|
|
||||||
printf("after cross-write:\n");
|
|
||||||
printf(" A: %.*s\n", (int)r_a, buf_a);
|
|
||||||
printf(" B: %.*s\n", (int)r_b, buf_b);
|
|
||||||
|
|
||||||
close(fd_a);
|
|
||||||
close(fd_b);
|
|
||||||
unlink(path_a);
|
|
||||||
unlink(path_b);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int test_write_file(const char *path)
|
|
||||||
{
|
|
||||||
printf("\n=== test_write_file ===\n");
|
|
||||||
|
|
||||||
int fd = open(path, O_CREAT | O_RDWR, 0644);
|
|
||||||
if (fd < 0) { perror("open"); return 1; }
|
|
||||||
printf("open: %s fd=%d\n", path, fd);
|
|
||||||
|
|
||||||
const char *msg = "Hello, zvfs!";
|
|
||||||
ssize_t w = write(fd, msg, strlen(msg));
|
|
||||||
if (w < 0) { perror("write"); close(fd); return 2; }
|
|
||||||
printf("write: %zd bytes: %s\n", w, msg);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
printf("close: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int test_read_delete_file(const char *path)
|
|
||||||
{
|
|
||||||
printf("\n=== test_read_delete_file ===\n");
|
|
||||||
|
|
||||||
int fd = open(path, O_RDONLY);
|
|
||||||
if (fd < 0) { perror("open"); return 1; }
|
|
||||||
printf("open: %s fd=%d\n", path, fd);
|
|
||||||
|
|
||||||
char buf[256] = {0};
|
|
||||||
ssize_t r = read(fd, buf, sizeof(buf));
|
|
||||||
if (r < 0) { perror("read"); close(fd); return 2; }
|
|
||||||
printf("read: %zd bytes: %.*s\n", r, (int)r, buf);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
printf("close: ok\n");
|
|
||||||
|
|
||||||
if (unlink(path) != 0) { perror("unlink"); return 3; }
|
|
||||||
printf("unlink: ok\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* main */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int rc = 0;
|
|
||||||
|
|
||||||
|
|
||||||
char path[256];
|
|
||||||
char path_a[256];
|
|
||||||
char path_b[256];
|
|
||||||
if(argc >= 2){
|
|
||||||
sprintf(path, "%s/file.dat", argv[1]);
|
|
||||||
sprintf(path_a, "%s/file_a.dat", argv[1]);
|
|
||||||
sprintf(path_b, "%s/file_a.dat", argv[1]);
|
|
||||||
}else {
|
|
||||||
sprintf(path, "/tmp/test.dat");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(argc == 3){
|
|
||||||
int choose = atoi(argv[2]);
|
|
||||||
if(choose == 0){
|
|
||||||
rc = test_write_file(path);
|
|
||||||
}else if(choose == 1){
|
|
||||||
rc = test_read_delete_file(path);
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("argv[0]: %s\n", argv[0]);
|
|
||||||
// printf("argv[1]: %s\n", argv[1]);
|
|
||||||
// printf("path_a: %s\n", path_a);
|
|
||||||
// printf("path_b: %s\n", path_b);
|
|
||||||
|
|
||||||
// rc |= test_basic(path);
|
|
||||||
// rc |= test_lseek(path);
|
|
||||||
// rc |= test_dual_open_same_file(path);
|
|
||||||
// rc |= test_two_files(path_a, path_b);
|
|
||||||
// rc |= test_single_file_perf(path);
|
|
||||||
// rc |= test_single_file_random_perf(path);
|
|
||||||
rc |= test_single_file_random_noaligned_perf(path);
|
|
||||||
|
|
||||||
|
|
||||||
printf("\n=== all tests %s ===\n", rc == 0 ? "PASSED" : "FAILED");
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
816
zvfs/zvfs.c
816
zvfs/zvfs.c
File diff suppressed because it is too large
Load Diff
5
zvfs/zvfs.h
Normal file → Executable file
5
zvfs/zvfs.h
Normal file → Executable file
@@ -48,6 +48,8 @@ typedef struct zvfs_s {
|
|||||||
uint32_t magic; // 0x5A563146 (ZV1F)
|
uint32_t magic; // 0x5A563146 (ZV1F)
|
||||||
uint32_t version; // 1
|
uint32_t version; // 1
|
||||||
|
|
||||||
|
bool bs_dev_owned;
|
||||||
|
int op_errno;
|
||||||
|
|
||||||
bool finished;
|
bool finished;
|
||||||
} zvfs_t;
|
} zvfs_t;
|
||||||
@@ -72,6 +74,7 @@ typedef struct zvfs_file_s {
|
|||||||
int aligned;
|
int aligned;
|
||||||
|
|
||||||
size_t io_count;
|
size_t io_count;
|
||||||
|
int op_errno;
|
||||||
bool finished;
|
bool finished;
|
||||||
} zvfs_file_t;
|
} zvfs_file_t;
|
||||||
|
|
||||||
@@ -95,4 +98,4 @@ int close(int fd);
|
|||||||
int unlink(const char *name);
|
int unlink(const char *name);
|
||||||
off_t lseek(int fd, off_t offset, int whence);
|
off_t lseek(int fd, off_t offset, int whence);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
0
zvfs/zvfs.json
Normal file → Executable file
0
zvfs/zvfs.json
Normal file → Executable file
0
zvfs/zvfs.old/zvfs.json
Normal file → Executable file
0
zvfs/zvfs.old/zvfs.json
Normal file → Executable file
0
zvfs/zvfs.old/zvfs.old.c
Normal file → Executable file
0
zvfs/zvfs.old/zvfs.old.c
Normal file → Executable file
0
zvfs/zvfs_hook.c
Normal file → Executable file
0
zvfs/zvfs_hook.c
Normal file → Executable file
Reference in New Issue
Block a user