Files
zvfs/plan/userplan.md
2026-03-03 14:24:17 +00:00

5.8 KiB
Raw Blame History

架构目标

  • 通过 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_mountedbool g_env_inited
  • pthread_key_t 用于线程本地存储:g_thread_local_key(带 destructor

线程本地资源(每个 pthread 独占一份,通过 TLS 实现)

每个 pthread 拥有以下私有状态,存储在结构体 thread_local_zvfs_t 中:

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()

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