前言
在kernel 6.0的内核版本中,引入了ublk driver(Userspace Block Device Driver)[1],以io_uring的方式实现,旨在提供用户空间块设备逻辑。SPDK也基于此,提供了ublk target用来对接ublk driver实现高效的bdev块设备服务。本文将对此进行介绍并提供相应的使用说明。
io_uring 机制
在介绍 ublk模块之前,先简单回顾一下io_uring机制。kernel为了支持异步读写,提升CPU的利用率,引入了aio异步机制。相对于libaio,在5.1版本的kernel中引入了更为高效的io_uring机制。顾名思义,io_uring的核心就是user和ring,用户通过io_uring_setup()将返回一个fd,并通过这个fd进行mmap与kernel共享一块内存。这部分内存区域分为三个部分,分别是SQ(submission queue),CQ(completion queue)和SQE(submission queue entries)数组,实际的请求放在SQE的数组中,SQ和CQ是典型的 RingBuffer结构,有 head,tail 两个成员。用户提交任务的做法是找到空闲的SQE进行初始化,并根据任务请求进行设置,然后将SQE的索引放入到SQ中,完成设置SQE以后,更新SQ的tail,表示向RingBuffer中插入一个或多个请求。当进行系统调用,陷入到内核以后,kernel拿到对应的请求,更新SQ head并执行对应的操作。通过RingBuffer,可以实现批量的IO请求,减少系统调用的次数。此外,io_uring支持polling模式,SQ线程会捕捉来自SQ的请求。当完成操作以后,用户可以遍历CQ中head到tail的区间批量收割已完成的请求并进行处理。
图1. io_uring 机制
UBLK 架构
完整的ublk服务涉及三个部分:ublk driver、ublk server以及workload。在kernel中,ublk driver提供了ublk-control节点用于管理和控制整个ublk设备框架。位于用户态的ublk server从提交UBLK_CMD_ADD_DEV命令开始,通过io_uring向ublk-control节点提交控制命令,创建控制面设备ublkc*,主要声明了队列大小、块大小等配置信息。然后ublk server打开新增的设备ublkc*,将设备上的区域映射到它对应的用户态地址空间。在这些设置完成以后,ublk server提交UBLK_CMD_START_DEV命令,创建一个对系统其他进程可见的块设备/dev/ublkb*。此时,workload应用就可以根据需要对ublkb*提交相应的IO请求。
图2. ublk框架
SPDK ublk target
SPDK中提供了ublk target模块用于充当ublk server角色如图2所示,它结合了SPDK bdev子系统,可以将任意用户态bdev以ublkb*内核块设备的形式进行封装管理,并提供相应块服务。
SPDK ublk target configuration
正如前文所述,SPDK ublk target依赖kernel中的ublk driver和io_uring相关文件。因此,需要将kernel升级到6.0以后,并通过modprobe ublk_drv命令加载kernel中的ublk driver模块。同时,在SPDK中执行configure操作时加上对应的配置参数‘./configure --with-ublk --with-uring=/usr’,这将会自动校验ublk_cmd.h头文件以及liburing的安装路径。
Example for ublk target
ublk target的创建需要借助spdk_target,在启动了spdk_target以后,可利用rpc命令ublk_create_target()进行创建。当前ublk target支持用户选用自定义的core,这一参数在创建时指定,需要注意的是,允许ublk target选用的core必须是被spdk_target所使用的core的子集。每一个被ublk target占用的core会分别创建一个ublk spdk_thread,同时每个ublk spdk_thread上也会创建一个poller用于处理ublk IO请求。当ublk target完成创建以后,则可以通过rpc命令基于bdev添加对应的ublk设备。以malloc bdev为例,下述命令完整地展示了添加ublk设备的流程。
1. ./build/bin/spdk_tgt -m 0xf &
2. // 指定ublk target使用后两个core,默认情况下使用spdk_target所有core
3. ./scripts/rpc.py ublk_create_target -m [2,3]
4. // 创建后端bdev,以malloc bdev为例
5. ./scripts/rpc.py bdev_malloc_create -b Malloc0 128 512
6. // 添加ublk设备,queue num = 4,depth = 512,返回ublk设备的id
7. ./scripts/rpc.py ublk_start_disk Malloc0 0 -q 4 -d 512
ublk设备添加过程
上述命令在host上创建了一个ublk设备,可以在/dev/ublkb0中看到。此时,可以通过fio对其性能进行测试,也可以基于该块设备创建文件系统,并通过mount的形式挂载到其他地方给用户使用,部分使用场景可参考[2]。需要说明的是,出于性能和负载均衡的考虑,SPDK所实现的模型中,每一个ublk设备的queue会被均匀地分散到ublk target所使用的core上,如下图所示。
图3 SPDK ublk target框架
SPDK ublk target还支持对ublk设备的其他操作,包含查询、删除操作,同时也支持对ublk target本身的删除,这些操作可参考下述命令。
1. // 删除ublk设备
2. ./scripts/rpc.py ublk_stop_disk 0
3. // 查询ublk设备,默认情况查询所有ublk设备,可通过-n指定ublk id
4. ./scripts/rpc.py ublk_get_disks
5. // 删除ublk target
6. ./scripts/rpc.py ublk_destroy_target
ublk相关rpc命令
需要注意的是,当前SPDK只支持创建一个ublk target,不能重复创建,但允许用户在删除了ublk target以后重新分配core,创建新的ublk target。
总结
本文介绍了kernel中ublk模块,并简述了SPDK ublk target模块对ublk的支持,详细说明了ublk target的使用方法。SPDK ublk target 可以在Cloud Native Block Storage Solution中为主机端提供抽象块设备,同时SPDK也在共同完善ublk的一些新增特性,如USER COPY,热升级,Shared CQ等。需要注意的是受到ublk本身的限制,当前SPDK ublk target并不支持framework_set_scheduler()的方式动态调整各个用于IO poller的ublk spdk_thread。如果需要重新分配负载,一个更为推荐的做法是调用ublk_destroy_target()再根据需要重新创建ublk target,更为详细的文档也可以参考[3]。
参考文档
[1] https://docs.kernel.org/block/ublk.html
[3] https://spdk.io/doc/ublk.html
转载须知
推荐阅读
使用Intel DLB技术加速IPsec大象流的处理性能
助力边缘计算与5G应用“扬帆”--- 英特尔处理器产品网络特性介绍
点点“赞”和“在看”,给我充点儿电吧~