【SPDK】三、IO流程代码解析

在分析SPDK数据面代码之前,需要我们对qemu中实现的IO环以及virtio前后端驱动的实现有所了解(后续我计划出专门的博文来介绍qemu)。这里我们仍以SPDK前端配置vhost-blk,后端对接NVMe SSD为例(有关NVMe驱动涉及较多规范细节,这里也不作过于深入的讨论,感兴趣的读者可以结合NVMe规范展开阅读)进行分析。

总流程

  前文在分析SPDK IO栈时已经大致分析了IO处理的调用层次,在此我们进一步打开内部实现细节,更细致地分析一下IO处理流程:

  首先,从虚拟机视角来说,它看到的是一个virtio-blk-pci设备,该pci设备内部包含一条virtio总线,其上又连接了virtio-blk设备。qemu在对虚拟机用户呈现这个virtio-blk-pci设备时,采用的具体设备类型是vhost-user-blk-pci(这是virtio-blk-pci设备的一种后端实现方式。另外两种是:vhost-blk-pci,由内核实现后端;普通virtio-blk-pci,由qemu实现后端处理),这样便可与用户态的SPDK vhost进程建立连接。SPDK vhost进程内部对于虚拟机所见的virtio-blk-pci设备也有一个对象来表示它,这就是spdk_vhost_blk_dev。该对象指向一个bdev对象和一个io channel对象,bdev对象代表真正的后端块存储(这里对应NVMe SSD上的一个namespace),io channel代表当前线程访问存储的独立通道(对应NVMe SSD的一个Queue Pair)。这两个对象在驱动层会进一步扩展新的成员变量,用来表示驱动层可见的一些详细信息。

  其次,当虚拟机往IO环中放入IO请求后,便立刻被vhost进程中的某个reactor线程轮循到该请求(轮循过种中执行函数为vdev_worker)。reactor线程取出请求后,会将其映成一个任务(spdk_vhost_blk_task)。对于读写请求,会进一步走到bdev层,将任务封状成一个bdev_io对象(类似内核的bio)。bdev_io继续往驱动层递交,它会扩展为适配具体驱动的io对象,例如针对NVMe驱动,bdev_io将扩展成nvme_bdev_io对象。NVMe驱动会根据nvme_bdev_io对象中的请求内容在当前reactor线程对应的QueuePair中生成一个新的请求项,并通知NVMe控制器有新的请求产生。

  最后,当物理NVMe控制器完成IO请求后,会往QueuePair中添加IO响应。该响应信息也会很快被reactor线程轮循到(轮循执行函数为bdev_nvme_poll)。reactor取出响应后,根据其id找到对应的nvme_bdev_io,进一步关联到对应的bdev_io,再调用bdev_io中的记录的回调函数。vhost-blk下发请求时注册的回调函数为blk_request_complete_cb,回调参数为当前的spdk_vhost_blk_task对象。在blk_request_complete_cb中会往虚拟机IO环中放入IO响应,并通过虚拟中断通知虚拟机IO完成。

IO请求下发流程代码解析

  vhost进程通过vdev_worker函数以轮循方式处理虚拟机下发的IO请求,调用栈如下:

vdev_worker()

\-process_vq()

|-spdk_vhost_vq_avail_ring_get()

\-process_blk_request()

|-blk_iovs_setup()

\-spdk_bdev_readv()/spdk_bdev_writev()

\-spdk_bdev_io_submit()

\-bdev->fn_table->submit_request()

  下面我们先来分析一下vhost-blk层的具体代码实现:

spdk/lib/vhost/vhost-blk.c:

/* reactor线程会采用轮循方式周期性地调用vdev_worker函数来处理虚拟机下发的请求 */

static int

vdev_worker(void *arg)

{

/* arg在注册轮循函数时指定,代表当前操作的vhost-blk对象 */

struct spdk_vhost_blk_dev *bvdev = arg;

uint16_t q_idx;

/* vhost-blk对象bvdev中含有一个抽象的spdk_vhost_dev对象,其内部记录所有vhost_dev类别对象

均含有的公共内容,max_queues代表当前vhost_dev对象共有多少个IO环,virtqueue[]数组记录了

所有的IO环信息 */

for (q_idx = 0; q_idx < bvdev->vdev.max_queues; q_idx++) {

/* 根据IO环的个数,依次处理每个环中的请求 */

process_vq(bvdev, &bvdev->vdev.virtqueue[q_idx]);

}

...

}

/* 处理IO环中的所有请求 */

static void

process_vq(struct spdk_vhost_blk_dev *bvdev, struct spdk_vhost_virtqueue *vq)

{

struct spdk_vhost_blk_task *task;

int rc;

uint16_t reqs[32];

uint16_t reqs_cnt, i;

/* 先给出一些关于IO环的知识:

(1) 简单来说,每个IO环分成descriptor数组、avail数组和used数组三个部分,数组元素个数均为环的最大请求个数。

(2) descriptor数组元素代表一段虚拟机内存,每个IO请求至少包含三段,请求头部段、数据段(至少一个)和响应段。

请求头部包含请求类型(读或写)、访问偏移,数据段代表实际的数据存放位置,响应段记录请求处理结果。一般来说,

每个IO请求在descriptor中至少要占据三个元素;不过当配置了indirect特性后,一个IO请求只占用一项,只不过

该项指向的内存段又是一个descriptor数组,该数组元素个数为IO请求实际所需内存段。

(3) avail数组用来记录已下发的IO请求,数组元素内容为IO请求在descriptor数组中的下标,该下标可作为请求的id。

(4) used数组用来记录已完成的IO响应,数组元素内容同样为IO在descritpror数组中的下标。

*/

/* 从IO环的avail数组中中取出一批请求,将请求id放入reqs数组中;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的示例代码,用于初始化STM32F4的多个IO口。本例子初始化了GPIOA的2,3,4个输出IO口,将它们设置为推挽输出,输出高电平。 ```c #include "stm32f4xx.h" int main(void) { // 1. 配置GPIOA端口时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 2. 配置GPIOA的2,3,4IO口为输出模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. 设置GPIOA的2,3,4IO口输出高电平 GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4); while(1) { // do something } } ``` 上述代码中,首先通过RCC_AHB1PeriphClockCmd()函数使能GPIOA端口时钟。然后,使用GPIO_InitTypeDef结构体配置GPIOA的2,3,4IO口为推挽输出模式,输出速度为50MHz,无上下拉电阻。最后,使用GPIO_SetBits()函数将GPIOA的2,3,4IO口设置为高电平。 需要注意的是,上述代码中的GPIO_Pin_2、GPIO_Pin_3、GPIO_Pin_4是宏定义,分别代表GPIOA的2、3、4IO口。在实际开发中,可以根据需要进行修改。 此外,还需要在main函数之前定义SystemInit()函数,用于初始化系统时钟,具体内容可以参考STM32的启动文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值