### 架构目标 - 通过 LD_PRELOAD hook POSIX 文件操作(open/read/write/pread/pwrite/close/fsync 等),将 MySQL 的数据文件 IO 重定向到 SPDK Blobstore。 - 最大化性能:绕过内核、利用多核并发、低延迟、小块写合并。 - 核心原则:**每个 pthread 拥有独立的 SPDK 执行上下文**,全局共享底层存储资源。 ### 全局资源(进程级别,唯一一份) - `zvfs_t *g_fs`:文件系统实例,包含: - `struct spdk_blob_store *bs`:全局 Blobstore(通过 spdk_bs_load/init 创建)。 - bdev(Nvme0n1 或 Malloc0,通过 JSON 配置加载)。 - 全局元数据:dirents 数组(zvfs_dirent_t *[])、fd_table(zvfs_file_t *[])、openfd_count。 - 保护全局元数据的锁:pthread_rwlock_t g_meta_lock(读多写少场景)。 - 全局初始化标志:`bool g_mounted`、`bool g_env_inited`。 - pthread_key_t 用于线程本地存储:`g_thread_local_key`(带 destructor)。 ### 线程本地资源(每个 pthread 独占一份,通过 TLS 实现) 每个 pthread 拥有以下私有状态,存储在结构体 `thread_local_zvfs_t` 中: ```c typedef struct { struct spdk_thread *thread; // 本线程专属的 SPDK thread struct spdk_io_channel *channel; // 本线程专属的 IO channel(绑定到 g_fs->bs) TAILQ_HEAD(, io_ctx) pending_queue; // 本线程的 pending IO 队列,用于 batch poll // 可选扩展: // struct dma_buf_pool *dma_pool; // per-thread DMA buf 复用池 // struct page_cache *local_cache; // 如果需要 per-thread cache } thread_local_zvfs_t; ``` - **创建时机**:lazy(第一次 IO 时调用 `get_thread_local()`)。 - **存储方式**:通过 `pthread_setspecific(g_thread_local_key, tl)` 绑定到当前 pthread。 - **销毁时机**:pthread 退出时,TLS destructor 自动调用: - spdk_bs_free_io_channel(channel) - spdk_thread_exit + poll until exited + spdk_thread_destroy ### 核心函数:get_thread_local() ```c thread_local_zvfs_t *get_thread_local(void) { // 确保 key 已创建(只执行一次) pthread_once(&g_key_once, init_thread_key); thread_local_zvfs_t *tl = pthread_getspecific(g_thread_local_key); if (tl == NULL) { tl = calloc(1, sizeof(*tl)); tl->thread = spdk_thread_create("zvfs_worker", NULL); tl->channel = spdk_bs_alloc_io_channel(g_fs->bs); TAILQ_INIT(&tl->pending_queue); pthread_setspecific(g_thread_local_key, tl); } return tl; } ``` ### 工作流程(每个 pthread 独立执行) 1. **线程首次进入 IO 操作** - 调用 `get_thread_local()` → 创建并绑定 thread + channel。 - 如果 !g_mounted → 调用 zvfs_ensure_mounted()(使用当前 thread 进行 poll 完成 mount)。 2. **元数据操作(open/unlink/mkdir/rmdir/rename 等)** - 加读锁(g_meta_lock)检查/修改全局 dirents、dirs、fd_table。 - 创建/查找 zvfs_file_t,调用 zvfs_create/zvfs_open(使用当前 thread 同步等待)。 - 分配伪 fd,记录到全局 fd_table。 - 释放锁。 3. **读操作(read/pread)** - 获取当前 tl = get_thread_local()。 - spdk_set_thread(tl->thread)。 - 如果小读 + cache hit → 直接 memcpy 返回。 - 否则:创建 io_ctx,加入 tl->pending_queue。 - 调用 spdk_blob_io_read(..., tl->channel, ...)。 - 执行 batch_poll(tl, my_ctx): - while (!my_ctx->done) spdk_thread_poll(tl->thread, 0, 0); - 从 dma_buf 拷贝到用户 buf。 4. **写操作(write/pwrite)** - 获取 tl。 - spdk_set_thread(tl->thread)。 - 如果小写 → patch per-file page cache(dirty),标记 dirty,返回(延迟写)。 - 如果 cache 满或大写 → flush dirty pages(batch spdk_blob_io_writev,用 tl->channel)。 - 创建 io_ctx → 加入 pending_queue → submit write → batch_poll。 5. **fsync** - flush per-file dirty cache(batch writev + spdk_blob_sync_md)。 - 使用当前 tl->thread poll 等待完成。 6. **close** - fsync(flush cache)。 - zvfs_close(用当前 tl->thread 同步)。 - 释放 fd(加锁更新全局 fd_table)。 ### 性能关键机制 - **独立 poll**:每个 pthread 用自己的 spdk_thread 独立 poll,无跨线程消息。 - **batch poll**:一个 poll 循环可完成多个 pending IO,提升有效 QD。 - **page cache**:per-file 4K dirty pages(hashmap),合并小写,减少 write amplification。 - **channel per-thread**:避免全局 channel 争用,每个线程独立提交 IO。 - **最小全局锁**:只在元数据修改时短时加锁(rwlock),IO 操作无锁。 ### 资源所有权总结表 | 资源类型 | 所有权 | 数量 | 创建时机 | 销毁时机 | |----------------------|--------------|------------|------------------------|------------------------------| | bdev | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount | | blobstore (bs) | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount | | zvfs_t / g_fs | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount + free | | dirents / fd_table | 全局 | 1 | meta_load | zvfs_umount + free | | spdk_thread | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) | | io_channel | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) | | pending_queue | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出 | | page cache | per-file | per open fd| open 时 lazy | close 时 flush + free |