介绍
块设备是支持以固定大小的块读取和写入数据的存储设备。这些块通常为512或4096字节。设备可以是软件中的逻辑构造,或者对应于诸如NVMe SSD的物理设备。
块设备层包含单个通用库lib/bdev,以及实现各种类型的块设备的许多可选模块(作为单独的库)。通用库的公共头文件是bdev.h,它是与任何类型的块设备交互所需的全部API。
下面将介绍如何使用该API与bdev进行交互。有关实现bdev模块的指南,请参阅编写自定义块设备模块。
除了为所有块设备提供通用抽象之外,bdev层还提供了许多有用的功能:
- 响应队列满或内存不足的情况自动排队I / O请求
- 支持热移除,即使在I / O流量发生时也是如此。
- I / O统计信息,如带宽和延迟
- 设备重置支持,和I / O超时跟踪
基本原语
bdev API的用户与许多基本对象进行交互。
struct spdk_bdev,本指南将其称为bdev,表示通用块设备。struct spdk_bdev_desc,此前称为描述符,表示给定块设备的句柄。描述符用于建立和跟踪使用底层块设备的权限,非常类似于UNIX系统上的文件描述符。对块设备的请求是异步的,由spdk_bdev_io对象表示。请求必须在关联的I / O channel上提交。消息传递和并发中描述了I / O channel的动机和设计。
Bdev可以是分层的,这样一些bdev通过将请求路由到其他bdev来服务I / O. 这可用于实现缓存,RAID,逻辑卷管理等。的BDEV该路线I / O到其他的BDEV通常被称为虚拟的BDEV,或vbdevs的简称。
初始化库
bdev层依赖于头文件include/spdk/thread.h抽象的通用消息传递基础结构。有关完整说明,请参阅消息传递和并发。最重要的是,只能通过调用spdk_allocate_thread()从已经分配了SPDK的线程调用bdev库。
从分配的线程中,可以通过调用spdk_bdev_initialize()来初始化bdev库,这是一个异步操作。在调用完成回调之前,不能调用其他bdev库函数。同样,要拆除bdev库,请调用spdk_bdev_finish()。
发现块设备
所有块设备都有一个简单的字符串名称。在任何时候,都可以通过调用spdk_bdev_get_by_name()来获取指向设备对象的指针,或者可以使用spdk_bdev_first()和spdk_bdev_next()及其变体来迭代整个bdev集。
一些块设备也可以给出别名,也是字符串名称。别名的行为类似于符号链接 - 它们可以与实名互换使用以查找块设备。
准备使用块设备
为了将I / O请求发送到块设备,必须首先通过调用spdk_bdev_open()来打开它。这将返回一个描述符。多个用户可能同时打开bdev,并且用户之间的读写协调必须由bdev层之外的某些更高级别的机制来处理。如果虚拟bdev模块声明了bdev,则打开具有写入权限的bdev可能会失败。虚拟bdev模块实现RAID或逻辑卷管理之类的逻辑,并将其I / O转发到较低级别的bdev,因此它们将这些较低级别的bdev标记为声称可防止外部用户发出写入。
打开块设备时,可以提供可选的回调和上下文,如果删除了为块设备提供服务的底层存储,则将调用该回调和上下文。例如,当NVMe SSD热插拔时,将在物理NVMe SSD支持的bdev的每个打开描述符上调用remove回调。回调可以被认为是关闭打开描述符的请求,因此可以释放其他内存。当存在开放描述符时,不能拆除bdev,因此强烈建议提供回调。
当用户完成描述符时,他们可以通过调用spdk_bdev_close()来释放它。
描述符可以同时传递给多个线程并从中使用。但是,对于每个线程,必须通过调用spdk_bdev_get_io_channel()获得单独的I / O channel。这将分配必要的每线程资源,以便在不接受锁定的情况下向bdev提交I / O请求。要释放channel,请调用spdk_put_io_channel()。在销毁所有相关 channel之前,不能关闭描述符。
SPDK 的I/O 路径采用无锁化机制。当多个thread操作同意SPDK 用户态block device (bdev) 时,SPDK会提供一个I/O channel的概念 (即thread和device的一个mapping关系)。不同的thread 操作同一个device应该拥有不同的I/O channel,每个I/O channel在I/O路径上使用自己独立的资源就可以避免资源竞争,从而去除锁的机制。详见SPDK进程间的高效通信。
发送I / O
一旦一个描述符和一个信道已经获得,I / O可以通过调用各种I / O功能提交诸如发送spdk_bdev_read() 。这些调用都将回调作为参数,稍后将使用spdk_bdev_io对象的句柄调用该参数。响应完成,用户必须调用spdk_bdev_free_io()来释放资源。在此回调中,用户还可以使用函数spdk_bdev_io_get_nvme_status()和spdk_bdev_io_get_scsi_status()以他们选择的格式获取错误信息。
通过调用spdk_bdev_read()或spdk_bdev_write()等函数来执行I / O提交。这些函数将一个指向内存区域的指针或一个描述将被传输到块设备的内存的分散集合列表作为参数。必须通过spdk_dma_malloc()或其变体分配此内存。有关内存必须来自特殊分配池的完整说明,请参阅用户空间驱动程序的内存管理。在可能的情况下,内存中的数据将使用直接内存访问直接传输到块设备。这意味着它不会被复制。
所有I / O提交功能都是异步和非阻塞的。它们不会因任何原因阻塞或停止线程。但是,I / O提交功能可能会以两种方式之一失败。首先,它们可能会立即失败并返回错误代码。在这种情况下,将不会调用提供的回调。其次,它们可能异步失败。在这种情况下,关联的spdk_bdev_io将传递给回调,它将报告错误信息。
某些I / O请求类型是可选的,给定的bdev可能不支持。要查询bdev以获取其支持的I / O请求类型,请调用spdk_bdev_io_type_supported()。
重置块设备
为了处理意外的故障情况,bdev库提供了一种通过调用spdk_bdev_reset()来执行设备重置的机制。这会将消息传递给bdev存在I / O channel的每个其他线程,暂停它,然后将重置请求转发到底层bdev模块并等待完成。完成后,I / O channel将恢复,重置将完成。bdev模块中的特定行为是特定于模块的。例如,NVMe设备将删除所有队列对,执行NVMe重置,然后重新创建队列对并继续。最重要的是,无论设备类型如何,块设备的所有未完成的I / O都将在重置完成之前完成。
从RPC到Bdev
# ./scripts/rpc.py -h
例如:./scripts/rpc.py -p 5261 construct_nvme_bdev -b "nvme0" -t "PCIe" -a "0000.04:00:0"
scripts/rpc.py
if __name__ == "__main__":
p = subparsers.add_parser('construct_nvme_bdev',
help='Add bdev with nvme backend')
p.add_argument('-b', '--name', help="Name of the bdev", required=True)
p.add_argument('-t', '--trtype',
help='NVMe-oF target trtype: e.g., rdma, pcie', required=True)
p.add_argument('-a', '--traddr',
help='NVMe-oF target address: e.g., an ip address or BDF', required=True)
p.add_argument('-f', '--adrfam',
help='NVMe-oF target adrfam: e.g., ipv4, ipv6, ib, fc, intra_host')
p.add_argument('-s', '--trsvcid',
help='NVMe-oF target trsvcid: e.g., a port number')
p.add_argument('-n', '--subnqn', help='NVMe-oF target subnqn')
p.set_defaults(func=construct_nvme_bdev)
scripts/rpc/bdev.py
def construct_nvme_bdev(args):
params = {'name': args.name,
'trtype': args.trtype,
'traddr': args.traddr}
if args.adrfam:
params['adrfam'] = args.adrfam
if args.trsvcid:
params['trsvcid'] = args.trsvcid
if args.subnqn:
params['subnqn'] = args.subnqn
args.client.call('construct_nvme_bdev', params)
Souce Insight
---- construct_nvme_bdev Matches (13 in 8 files) ----
Bdev.py (c:\workspace\spdk20180208\scripts\rpc):def construct_nvme_bdev(args):
Bdev.py (c:\workspace\spdk20180208\scripts\rpc): args.client.call('construct_nvme_bdev', params)
Bdev_nvme_rpc.c (c:\workspace\spdk20180208\lib\bdev\nvme):SPDK_RPC_REGISTER("construct_nvme_bdev", spdk_rpc_construct_nvme_bdev)
bdev_nvme_rpc.c
SPDK_RPC_REGISTER("construct_nvme_bdev", spdk_rpc_construct_nvme_bdev)
static void
spdk_rpc_construct_nvme_bdev(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params)
{ ......
}