Files
zvfs/README.md

7.9 KiB
Raw Blame History

usage

SPDK

  1. blob_store: blob仓库管理多个blob对象。
  2. blob: 存储对象,逻辑上连续,物理上不一定连续。相当于文件。
  3. cluster: 分配单元,一个 blob 可以由多个 cluster 构成,扩容即分配新的 cluster。相当于文件系统的block group。
  4. page: IO单元一个 cluster 等于多个 page。

文件系统

架构设计

| 应用程序
    |   (POSIX API: open/read/write/close)
| LD_PRELOAD  拦截层
    |   (简单路径判断和转发到zvfs)
| zvfs 文件系统层
    |   (blob 操作)
| SPDK Blobstore
| 块设备 (Malloc0)

磁盘布局

BlobStore:
|—— Super Blob元数据使用SPDK的Super Blob锚定
    |——超级块
    |——目录项/目录日志
|—— Blob 1 (文件A...)
|—— Blob 2 (文件B...)
|—— Blob N (文件C...)

数据结构

Super Blob元数据

[超级块]
- magic_number: 0x5A563146 (ZV1F)
- version: 1

[目录项]
- filename[256]: 文件名
- blob_id: 对应的数据blob ID
- file_size: 文件实际大小字节
- allocated_clusters: 已分配的cluster数量
- is_valid: 标记是否有效用于删除
/* 目录项(内存中的目录) */
typedef struct {
    char        filename[256];
    spdk_blob_id blob_id;
    uint64_t    file_size;          // 文件逻辑大小(字节)
    uint32_t    allocated_clusters; // 已分配的cluster数量
    bool        is_valid;           // false 表示已删除
    int32_t     open_count;         // 打开的文件句柄数量
} zvfs_dirent_t;

/* 文件系统全局结构 */
typedef struct zvfs {
    struct spdk_blob_store  *bs;
    struct spdk_io_channel  *channel;
    struct spdk_blob        *super_blob;    // 承载目录日志的blob
    uint64_t                io_unit_size;   // page大小单位字节

    /* 目录 */
    zvfs_dirent_t           *dirents;       // 目录项数组 #define ZVFS_MAX_FILES 1024
    uint32_t                dirent_count;   // 当前有效项数

    /* 伪FD表 */
    struct zvfs_file        *fd_table[ZVFS_MAX_FD]; // // e.g., #define ZVFS_MAX_FD 64
    int                     fd_base;                // 伪FD起始值如10000
	int 					openfd_count;

    /* 元数据 */
    uint32_t                magic;                  // 0x5A563146 (ZV1F)
    uint32_t                version;                // 1
} zvfs_t;

/* 打开的文件句柄 */
typedef struct zvfs_file {
    zvfs_t                  *fs;
    struct spdk_blob        *blob;
    zvfs_dirent_t           *dirent;        // 指回目录项 file_size/allocated_clusters
    
    uint64_t                current_offset; // 当前读写位置
    int                     flags;          // O_RDONLY / O_RDWR / O_CREAT 等
    int                     pseudo_fd;

    /* 临时DMA缓冲区可选每个file一个避免每次malloc */
    void                    *dma_buf;
    uint64_t                dma_buf_size;
} zvfs_file_t;

工作流程

mount

hook POSIX API没有很好的调用时机单线程目前采用懒加载。

1. [创建块设备]
    - spdk_bdev_create_bs_dev_ext
2. [初始化文件系统]
    - spdk_bs_init 或者 spdk_bs_load已有数据时
    - spdk_bs_get_io_unit_size 获取io单元大小(page)
    - spdk_bs_alloc_io_channel 分配blobstore的读写入口
3. [读取元数据]
    - spdk_bs_get_super_blob 获取 Super Blob ID
    - spdk_bs_open_blob 打开 Super Blob
    - 读取超级块校验 magic
    - 读取目录项数组加载到内存 dirents
4. [创建zvfs_t结构体]
    - 创建 zvfs_t 结构体
    - 填充 bs/channel/super_blob/dirents 等字段

open

O_RDONLY / O_RDWR
1. [文件名查找]
    - 遍历 dirents匹配 filename  is_valid=true
    - 找不到返回 -ENOENT
2. [打开blob]
    - spdk_bs_open_blob(dirent->blob_id)
    - dirent->open_count++
    - fs->openfd_count++
3. [分配文件句柄]
    - 创建 zvfs_file_tdirent 指针指向目录项
    - 分配伪FD写入 fd_table
5. [返回伪FD]
O_CREAT
1. [文件名查找]
    - 遍历 dirents filename 已存在且 is_valid=true返回 -EEXIST
    - 找一个 is_valid=false 的空槽位没有空槽则追加dirent_count < max_files
2. [创建blob]
    - spdk_bs_create_blob  得到 blob_id
    - spdk_bs_open_blob  得到 blob 句柄
    - spdk_blob_resize 初始分配空间
    - spdk_blob_sync_md 持久化 cluster 分配
3. [写目录]
    - 填充 filename/blob_id/file_size=0/is_valid=true
    - dirent->open_count = 1
4. [创建文件句柄]
    - 创建 zvfs_file_t
    - 分配伪FD写入 fd_table
5. [返回伪FD]

说明目录变更只写内存unmount 时统一持久化。

read

读写都以字节为单位offset / count 单位为字节;根据 io_unit_size 做对齐计算。

1. [参数]
    - fd
    - buffer
    - count
    - offset(隐含)
2. [边界检查]
    - 实际可读 = min(count, dirent->file_size - current_offset)
    - 实际可读为0则返回0
3. [计算Blob位置]
    - start_page = current_offset / io_unit_size
    - page_offset = current_offset % io_unit_size
    - num_pages = (page_offset + 实际可读 + io_unit_size - 1) / io_unit_size
4. [DMA读取]
    - 非对齐读(offset != 0 || count 不是整页)
        - 需要DMA临时缓冲区spdk_dma_zmalloc
        - spdk_blob_io_read(blob, channel, dma_buffer, start_page, num_pages, ...)
        -  dma_buffer + page_offset 拷贝到用户 buffer
    - 对齐
        - 仍使用DMA缓冲区执行读取再拷贝到用户buffer
5. [更新offset]
    - current_offset += 实际可读
6. [返回实际读取字节数]

说明SPDK需要DMA可用的内存应用提供的用户缓冲区通常不满足要求。即便对齐也不能直接提交给spdk_blob_io_*应使用DMA缓冲作为跳板未来通过注册内存池可优化直传。

write

1. [参数]
    - fd
    - buffer
    - count
    - offset(隐含)
2. [检查空间是否足够]
    - 需要大小 = current_offset + count
    - 若超过 allocated_clusters 对应容量
        - spdk_blob_resize 扩容
        - spdk_blob_sync_md
        - 更新 dirent->allocated_clusters
3. [计算写入位置]
    - start_page / page_offset / num_pages同read
4. [DMA写入]
    - 非对齐写(offset != 0 || count 不是整页)
        - 读取涉及的首尾page到DMA临时缓冲区
        - 修改对应位置的数据
        - 写回spdk_blob_io_write(blob, channel, dma_buffer, start_page, num_pages, ...)
    - 对齐
        - 仍通过DMA缓冲区提交写入
5. [更新状态]
    - current_offset += count
    - dirent->file_size = max(dirent->file_size, current_offset)
6. [返回写入字节数]

close

1. [关闭Blob]
    - spdk_blob_close(file->blob)
    - dirent->open_count--
    - fs->openfd_count++
    -  open_count == 0  is_valid == false已unlinkspdk_bs_delete_blob, 清空dirent
    -  openfd_count == 0  unmount
2. [释放缓冲区]
    - 释放 dma_buf
    - 清除 fd_table[pseudo_fd]
    - free(zvfs_file_t)
3. [返回0]
1. [查找目录项]
    - 遍历 dirents匹配 filename  is_valid=true
    - 找不到返回 -ENOENT
2. [标记删除]
    - dirent->is_valid = false
3. [判断是否立即删除]
    - open_count == 0spdk_bs_delete_blob清空该槽位
    - open_count > 0延迟最后一个 close 负责删除
4. [返回0]

unmount

1. [关闭channel]
    - spdk_bs_free_io_channel
2. [关闭BlobStore]
    - spdk_bs_unload
3. [释放FS]
    - free(fs)

其他方案

如果不使用LD_PRELOADhook可以使用FUSE。
FUSE是一种内核文件系统程序挂载在文件目录上对这个目录的访问会使用这个文件系统程序。
文件系统程序会将请求转发给应用层程序这里的应用层程序可以是SPDK。这样就不用管其他的操作。