暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SPDK bdev详解

360云计算 2021-11-12
3242

01

简介

在之前的文章《SPDK简述和概览》中我们描述了SPDK应用编程框架的部分内容,基于文中提到的线程模型,SPDK实现了各类子系统、应用服务在调用spdk_app_start方法启动时,除了会按模型初始化线程外,还会对注册的各个子系统进行初始化。
 


如上图是SPDK支持的子系统以及子系统间的依赖关系,在SPDK框架中bdev也是作为一个子系统存在,用于提供通用的用户态块存储抽象,和内核的通用块层类似,它会屏蔽底层模块(Module)具体的实现,对外提供统一的接口,当然,对底层模块要求也是需要实现对应的API。


02

SPDK BDEV模型


2.1 g_bdev_mgr


 

如图,SPDK使用了一个全局结构g_bdev_mgr对内部得块设备进行管理,并将其注册到全局设备,服务启动调用方法spdk_bdev_initialize进行bdev子系统初始化时,会对g_bdev_mgr的全局资源进行分配;

bdev_io_pool: spdk_bdev_io结构得全局缓冲池,默认缓存池大小64k,每个线程局部缓存256个,当对bdev进行操作时,SPDK需要将io封装成spdk_bdev_io结构,就会从线程缓冲区或全局缓冲区中获取;
  •  buf_small_pool: 小于8k数据的全局数据缓冲池,当数据大小小于8k时,会从该缓存池中获取buf,默认池大小为8191
  • buf_large_pool: 小于64k数据的全局数据缓冲池,当数据大小小于64k时,会从该缓冲池获取buf,默认池大小为1023
  • zero_buffer: 使用该缓冲区对设备进行填充补零操作;
  • bdev_modules: 保存bdev模块的链式结构,如下图,是SPDK目前支持的模块;



  • bdevs: 保存bdev的链式结构,包含子系统所有的块设备;

  • bdev_names: 保存bdev名称的红黑树结构,方便使用名称对bdev快速检索;

2.2 spdk_bdev_mgmt_channel

spdk_bdev_mgmt_channel是对应上面一节提到的io_device g_bdev_mgr的io_channel,用于维护线程的局部资源:

 

  • per_thread_cache: 每个thread对接一个io_device只有唯一的io_channel,对应到该结构,代表了每个thread具有自己本地独立的spdk_bdev_io缓存池,初始化时会从全局结构bdev_io_pool中获取默认256个缓存单元;当进行IO时,会先该本地缓存池中获取,为空时,再从全局缓存池中获取;

  • need_buf_small: 将等待small buf的io进行排队等候;

  •  need_buf_large: 将等待large buf的io进行排队等候;

  • shared_resources: 如果bdev使用了相同的后端设备,会使用同一个shared_resource,不同的后端设备对应不同shared_resource;

  •  io_wait_queue: 将等待io元数据得io进行排队等候;

 

2.3 bdev子系统结构



在SPDK内部,reactor对应了绑定在某个CPU核上的一个线程,每个reactor上会创建多个thread,类似于协程,会不断的被调度。另外,SPDK将所有的io设备(io_device)保存在全局变量g_io_devices中,为了方便对io设备进行查询,这里也使用了红黑树的结构(之前使用链表结构)进行管理,io设备不仅仅是对底层磁盘的映射抽象,也包含一些对线程资源敏感的逻辑管理结构,如上文提到的g_bdev_mgr就是一个io设备,但是它并不对应底层磁盘。thread到io设备之间使用io_channel进行关联,每个thread对应同一个io设备有唯一的io_channel,io_channel存储了thread对io设备操作所需的信息,多个thread可以使用自己独有的io_channel使用同一个io_device,不管thread是同属一个reactor还是不同reactor都一样。



如上图,bdev子系统会注册三类io设备:

  • g_bdev_mgr设备包括了bdev子系统全局的缓存池资源,对应得io_channel(spdk_bdev_mgmt_channel)会对thread局部资源进行管理。

  • SPDK中得每个bdev都会注册成io设备。

  • 另外还有部分io设备会在模块中注册,对应io_channel会保存实际设备操作结构信息,对设备进行真正得IO。


2.2节提到了spdk_bdev_mgmt_channel结构,上图可以很清楚的看到它和各类io_channel以及`g_bdev_mgr`的关系,本身进行了线程局部资源的缓存,并且使用shared_resource将spdk_bdev_channel和底层模块的io_channel进行关联,使得同一thread内能够让多个bdev使用相同的操作信息共用底层设备。


03

自定义模块

 

3.1 模块接口

 

启动服务之前,SPDK bdev子系统的各个模块会调用宏定义`
SPDK_BDEV_MODULE_REGISTER`添加到全局结构中,服务启动时,会逐一对各个模块进行初始化,当服务停止时,会对模块进行烦注册。如下,是模块需要实现接口:


struct spdk_bdev_module {
const char *name;
bool async_init;
bool async_fini;
bool async_fini_start;
int (*module_init)(void);
void (*init_complete)(void);
void (*fini_start)(void);
void (*module_fini)(void);
int (*config_json)(struct spdk_json_write_ctx *w);
int (*get_ctx_size)(void);
void (*examine_config)(struct spdk_bdev *bdev);
void (*examine_disk)(struct spdk_bdev *bdev);
}
复制



  • name: 模块名称;

  • async_init: 标记模块得初始化过程是否是异步的;

  • async_fini: 标记模块卸载过程是否是异步的;

  • async_fini_start: 标记模块开始卸载的过程是否是异步的;

  •  module_init: 模块初始化方法,必须实现;

  • init_complete: bdev子系统初始化完成后的模块回调方法;

  • fini_start: 模块开始反注册的回调方法;

  •  config_json: 用于获取模块的配置信息;

  • get_ctx_size:获取模块上下文元数据的大小;

  • examine_config:当使用配置文件启动系统服务时,可能添加了一些vbdev(虚拟bdev),当bdev注册时,需要该回调检查该bdev是否被vbdev依赖,存在依赖时需要自动创建vbdev;

  •  examine_disk:当bdev注册时,需要回调该方法检查bdev是否包含vbdev的元数据,存在则需要自动创建vbdev;

3.2 底层bdev(模块)接口

struct spdk_bdev_fn_table {
int (*destruct)(void *ctx);
void (*submit_request)(struct spdk_io_channel *ch, struct spdk_bdev_io *);
bool (*io_type_supported)(void *ctx, enum spdk_bdev_io_type);
struct spdk_io_channel *(*get_io_channel)(void *ctx);
int (*dump_info_json)(void *ctx, struct spdk_json_write_ctx *w);
void (*write_config_json)(struct spdk_bdev *bdev, struct spdk_json_write_ctx *w);
uint64_t (*get_spin_time)(struct spdk_io_channel *ch);
void *(*get_module_ctx)(void *ctx);
int (*get_memory_domains)(void *ctx, struct spdk_memory_domain **domains, int array_size);
}


复制
  • destruct: bdev设备关闭回调;

  • submit_request: 向后端设备提交IO请求的方法;

  • io_type_supported: 查看底层bdev支持的方法;

  • get_io_channel:获取底层模块的io_channel;

  • dump_info_json:获取驱动的json信息;

  • write_config_json:获取bdev的json信息;

  • get_spin_time:获取io_channel的统计信息,供vtune工具调试使用;

  • get_memory_domains:提供给上层vbdev获取底层设备所使用的内存域;



04

 io流程

当需要对bdev进行IO时,首先使用spdk_bdev_open_ext方法打开获取对应的设备描述符(spdk_bdev_desc),然后再使用spdk_bdev_get_io_channel方法获取bdev的io_channel,后续就可以使用设备描述符和io_channel对bdev进行io操作:
 
  •  a.从线程的本地缓存池获取IO结构;

  •  b.本地缓存不足时会从全局缓存池获取IO结构;

  •  c.对于某些IO,需要使用新的数据缓冲区时,按数据的大小从全局结构的small_pool和large_pool获取buffer,放入IO结构中;

  • d.从shared_resource中获取底层bdev的io_channel;

  • e.调用底层bdev的submit_request方法下发IO;


05

  总结

 
在SPDK的线程模型中,bdev子系统对用户态的块设备接口进行了统一的抽象,集成了很多底层实现的模块,并且很容易通过指定接口加入自己的模块,实现用户态的块设备接入、或则再通过SPDK的协议服务层通过iSCSI、NVMe-oF等暴露出去,都是很方便有效的。

 

06

  参考文档

 

  • SPDK 应用编程框架

  • SPDK block device 及其编程的简单介绍



文章转载自360云计算,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论