欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > virtio介绍 (三)--spdk作为virtio后端处理nvme盘io的流程--上

virtio介绍 (三)--spdk作为virtio后端处理nvme盘io的流程--上

2025/6/6 18:34:12 来源:https://blog.csdn.net/qq_35950085/article/details/148371396  浏览:    关键词:virtio介绍 (三)--spdk作为virtio后端处理nvme盘io的流程--上

目录

一 简介

二 vhost-blk层

三 bdev层

四 lvol层

五 bdev_nvme层

六 硬件驱动层

七 完整取io调用栈流程


一 简介

         上节介绍了virito的基本原理,后面根据实际代码介绍virtio的流程。virtio后端代码相对于前端代码更简单,我们先以spdk中的virtio后端代码为例介绍下接口的流转过程。

        spdk作为virito后端,通常有三种形态的磁盘:blk,scsi,nvme。 取其中一种方式:前端GuestOS显示blk,后端使用nvme磁盘。介绍下io的完整流程,再介绍下io流程中主要组件的初始化部分。spdk版本为24.05.x

        spdk后端io流程大体可分为两部分:

  1. 将io从ring环中取出,提交给磁盘驱动
  2. io从驱动返回之后,将io返还给前端

二 vhost-blk层

        由于前端是blk盘,所以后端对应的通信协议代码要先处理从vhost-blk开始。vhost-blk层将io数据从virtio-ring中取出,该逻辑的入口是process_vq,先获取idx,然后根据idx获取到对应的io数据。

process_vq中简化代码如下:

static int process_vq(struct spdk_vhost_blk_session *bvsession, struct spdk_vhost_virtqueue *vq)
{struct spdk_vhost_session *vsession = &bvsession->vsession;uint16_t reqs[SPDK_VHOST_VQ_MAX_SUBMISSIONS];uint16_t reqs_cnt, i;reqs_cnt = vhost_vq_avail_ring_get(vq, reqs, SPDK_COUNTOF(reqs)); //一次性从ring环中取出可用idx总数,批量处理for (i = 0; i < reqs_cnt; i++) {process_blk_task(vq, reqs[i]);// io处理是以task形式提交,根据idx获取剩余的数量:desc地址等}return reqs_cnt;
}

idx获取到之后,根据idx的信息从ring环中取出io数据,封装到task(任务)中,其中desc中的数据放到iovs中,接下来以请求的方式将task提交。

-->process_blk_task|-->blk_iovs_split_queue_setup	取出io数据|	|-->vhost_vq_get_desc		取出desc ring环地址|	|-->vhost_vring_desc_to_iov	|		|-->vhost_vring_desc_payload_to_iov		将desc中的数据放到iov中|			|-->rte_vhost_va_from_guest_pa		将Guest中的物理地址转化为spdk中的虚拟地址|-->vhost_user_process_blk_request|-->virtio_blk_process_request	提交封装task,注册请求的回调接口vhost_user_blk_request_finish

根据iov中的类型判断当前io时什么操作:读、写、flush、ummap等。然后根据io类型决定使用bdev层中哪种分装。

int virtio_blk_process_request(struct spdk_vhost_dev *vdev, struct spdk_io_channel *ch, struct spdk_vhost_blk_task *task, virtio_blk_request_cb cb, void *cb_arg)
{task->cb = cb; // 注册回调vhost_user_blk_request_finish,用户task完成时的逻辑处理task->cb_arg = cb_arg;iov = &task->iovs[0];memcpy(&req, iov->iov_base, sizeof(req));iov = &task->iovs[task->iovcnt - 1];payload_len = task->payload_size;task->status = iov->iov_base;payload_len -= sizeof(req) + sizeof(*task->status);iovcnt = task->iovcnt - 2;type = req.type;switch (type) {case VIRTIO_BLK_T_IN:case VIRTIO_BLK_T_OUT:if (type == VIRTIO_BLK_T_IN) {task->used_len = payload_len + sizeof(*task->status);rc = spdk_bdev_readv(bvdev->bdev_desc, ch, &task->iovs[1], iovcnt, req.sector * 512, payload_len, blk_request_complete_cb, task);} else if (!bvdev->readonly) { // 写io需要保证磁盘不能只读task->used_len = sizeof(*task->status);rc = spdk_bdev_writev(bvdev->bdev_desc, ch, &task->iovs[1], iovcnt, req.sector * 512, payload_len, blk_request_complete_cb, task);} else {rc = -1;}break;case VIRTIO_BLK_T_DISCARD:case VIRTIO_BLK_T_WRITE_ZEROES:case VIRTIO_BLK_T_FLUSH:case VIRTIO_BLK_T_GET_ID:default:}return 0;
}

三 bdev层

        Bdev是"Block Device"的缩写,即块设备层,位于底层存储介质(如NVMe设备)和上层应用(如存储服务)之间的抽象层。

        bdev层无论哪种封装,最后都走到bdev_io_submit接口,bdev层最终的io提交在bdev_io_do_submit中进行。接口简化如下:

static inline void bdev_io_increment_outstanding(...)
{bdev_ch->io_outstanding++;			//通道提交数量+1shared_resource->io_outstanding++;	
}static inline void bdev_submit_request(...)
{bdev->fn_table->submit_request(ioch, bdev_io);	//根据注册到bdev通道的驱动来提交
}static inline void bdev_io_do_submit(...)
{bdev_io_increment_outstanding(bdev_ch, shared_resource);bdev_io->internal.in_submit_request = true;	 // 该通道正在进行提交bdev_submit_request(bdev, ch, bdev_io);bdev_io->internal.in_submit_request = false; // 该通过已提交完成
}

四 lvol层

        lvol 层(Logical Volume Layer)类似传统 LVM 的功能:在物理块设备 (bdev) 或 Blobstore 上创建灵活的、可动态调整大小的逻辑卷,并支持快照和克隆说人话就是:客户用不了这么大的磁盘,把一块完整的大nvme磁盘变成很多小blk盘。

        一个lvol 逻辑卷本质上就是一个 Blob,所以查看lvol层代码时你会发现,里面lvol开头的接口很少,并且lvol接口实际上都有blob层对应的接口。比如:

  •         lvol_write                 -->        spdk_blob_io_writev_ext
  •         lvol_read                 -->        spdk_blob_io_readv_ext 
  •         lvol_write_zeroes    -->        spdk_blob_io_write_zeroes
  •         lvol_unmap             -->         spdk_blob_io_unmap

注:如果lvol层没有对应的blob接口,那说明spdk不支持这种调用,比如flush。

         回到正题上,通过lvol层之后,最终还会走到bdev_io_do_submit接口,但是这次注册的接口是bdev_nvme_submit_request。 所以这里的逻辑是:nvme磁盘的驱动先通过bdev层注册到lvol层,lvol层再通过bdev层注册到blk层。        

这里列举下写io的在lvol层的调用栈,其他的(读,unmap也类似)

-->vbdev_lvol_submit_request -------------------------------bdev_lvol层--------↓-->lvol_write 	把bdev_io数据拆开-->spdk_blob_io_writev_ext		---------------------lib blob层--------↓-->blob_request_submit_rw_iov --> bs_sequence_writev_dev-->channel->dev->writev --> bdev_blob_writev-->spdk_bdev_writev_blocks			----------bdev层-------↓-->bdev_writev_blocks_with_md|-->bdev_io_init|-->_bdev_io_submit_ext -->bdev_io_submit --> _bdev_io_submit -->bdev_io_do_submit-->bdev->fn_table->submit_request  --> bdev_nvme_submit_request 

五 bdev_nvme层

        接下来到核心的地方了,spdk实现用户态驱动实际上指的就是这一层,bdev_nvme层将linux内核中关于nvme的驱动又实现了一次。

这里涉及到存储和pcie相关知识,没办法一下讲清楚,后面单独出一篇介绍这个地方,这里只列举下调用栈)。

-->bdev_nvme_submit_request --> _bdev_nvme_submit_request  ---bdev_nvme层------↓-->bdev_nvme_writev-->spdk_nvme_ns_cmd_writev_with_md|-->_nvme_ns_cmd_rw 建立一个请求|	-->nvme_allocate_request 从qpair的free链表中找到一个可用的|	-->_nvme_ns_cmd_setup_request 1.从上层下发的参数放到req中 2.拼装nvme的cmd命令,操作码 spdk_nvme_nvm_opcode|-->nvme_qpair_submit_request-->_nvme_qpair_submit_request-->nvme_transport_qpair_submit_request-->transport->ops.qpair_submit_request --> nvme_pcie_qpair_submit_request|-->pqpair->free_tr 中取出一个,放入到pqpair->outstanding_tr链表中|-->注册 io完成时的回调 bdev_nvme_writev_done-->nvme_pcie_qpair_submit_request ------------io提交完成------bdev_pcie层------↓-->req 封装到tr中-->将tr添加到 pqpair->outstanding_tr 链表中-->nvme_pcie_qpair_ring_sq_doorbell		门铃机制,通知硬件有数据要处理了-->spdk_wmb() 内存屏障,避免系统进行内存优化-->pqpair->stat->sq_mmio_doorbell_updates++;-->spdk_mmio_write_4	写入寄存器4个数据,通过mmio通知硬件,要处理数据了

六 硬件驱动层

        由于没有硬件代码,简单介绍下硬件读/写io的大体流程:

        1. 读取提交队列SQ
            控制器从SQ中取出命令,解析 PRP/SGL 字段,获取数据缓冲区的物理地址
        2.发起DMA操作
            写操作(Host -> Device): 控制器通过 DMA 从主机内存的 PRP/SGL 地址读取数据, 写入SSD
            读操作(Device -> Host):控制器通过 DMA 将SSD数据 写入到主机内存的 PRP/SGL 地址。
        3.写入完成队列 CQ
            操作完成后,控制器将完成状态写入CQ,并通过中断 或者 MSI-X 通知主机(SPDK通常采用轮训模式)

七 完整取io调用栈流程

-->vdev_worker		---------------------------vhost_blk层----↓-->process_vq --> process_blk_task-->vhost_user_process_blk_request-->virtio_blk_process_request |-->spdk_bdev_readv|-->spdk_bdev_writev|-->spdk_bdev_readv	------------------------------bdev层------↓
|	-->spdk_bdev_readv_blocks	
|		-->bdev_readv_blocks_with_md	从通道中取出一个bdev_io,初始化bdev_io, 然后提交
|			|-->bdev_io_init
|			|-->_bdev_io_submit_ext -->bdev_io_submit
|
|-->spdk_bdev_writev
|	-->spdk_bdev_writev_blocks
|		-->bdev_writev_blocks_with_md
|			|-->bdev_io_init
|			|-->_bdev_io_submit_ext -->bdev_io_submit
|				-->_bdev_io_submit -->bdev_io_do_submit
|					-->bdev_submit_request
|						-->bdev->fn_table->submit_request  --> vbdev_lvol_submit_request -->vbdev_lvol_submit_request -------------------------------bdev_lvol层--------↓-->lvol_write 	把bdev_io数据拆开-->spdk_blob_io_writev_ext		---------------------lib blob层--------↓-->blob_request_submit_rw_iov --> bs_sequence_writev_dev-->channel->dev->writev --> bdev_blob_writev-->spdk_bdev_writev_blocks			----------bdev层-------↓-->..... 同上bdev层的调用栈,最终走到 submit_request 回调。-->bdev->fn_table->submit_request  --> bdev_nvme_submit_request -->bdev_nvme_submit_request --> _bdev_nvme_submit_request  ---bdev_nvme层------↓-->bdev_nvme_writev-->spdk_nvme_ns_cmd_writev_with_md|-->_nvme_ns_cmd_rw 建立一个请求|	-->nvme_allocate_request 从qpair的free链表中找到一个可用的|	-->_nvme_ns_cmd_setup_request 1.从上层下发的参数放到req中 2.拼装nvme的cmd命令,操作码 spdk_nvme_nvm_opcode|-->nvme_qpair_submit_request-->_nvme_qpair_submit_request-->nvme_transport_qpair_submit_request-->transport->ops.qpair_submit_request --> nvme_pcie_qpair_submit_request|-->pqpair->free_tr 中取出一个,放入到pqpair->outstanding_tr链表中|-->注册 io完成时的回调 bdev_nvme_writev_done-->nvme_pcie_qpair_submit_request ------------io提交完成------bdev_pcie层------↓-->req 封装到tr中-->将tr添加到 pqpair->outstanding_tr 链表中-->nvme_pcie_qpair_ring_sq_doorbell		门铃机制,通知硬件有数据要处理了-->spdk_wmb() 内存屏障,避免系统进行内存优化-->pqpair->stat->sq_mmio_doorbell_updates++;-->spdk_mmio_write_4	写入寄存器4个数据,通过mmio通知硬件,要处理数据了

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词