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

news2025/12/14 22:06:49

目录

一 简介

二 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++;			//通道提交数量+1
	shared_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通知硬件,要处理数据了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2396680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式(行为型)-中介者模式

目录 定义 类图结构展示 角色职责详解 模式的优缺点分析 优点 缺点 适用场景 应用实例 与其他模式的结合与拓展 总结 定义 中介者模式的核心思想可以概括为&#xff1a;用一个中介对象来封装一系列的对象交互。这个中介者就像一个通信枢纽&#xff0c;使各对象不需要…

【Java学习笔记】异常

异常&#xff08;Exception&#xff09; 一、基本介绍 在 Java 程序中&#xff0c;将运行中发生的不正常情况称为 “异常”&#xff0c;开发过程中的语法错误和运行时发生的异常情况是不一样的。 二、异常的分类 1. Error&#xff08;错误&#xff09;&#xff1a;Java 虚拟…

MySQL:视图+用户管理+访问+连接池原理

一、视图 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff08;相当于是把查询的内容当成一个临时表来使用&#xff09;&#xff0c;视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表&#xff0c;基表的数据变化也会影响到视图。 1.1 为…

neo4j 5.19.0安装、apoc csv导入导出 及相关问题处理

前言 突然有需求需要用apoc 导入 低版本的图谱数据&#xff0c;网上资料又比较少&#xff0c;所以就看官网资料并处理了apoc 导入的一些问题。 相关地址 apoc 官方安装网址 apoc 官方导出csv 教程地址 apoc 官方 导入 csv 地址 docker 安装 执行如下命令启动镜像 doc…

无人机桥梁3D建模的拍摄频率

无人机桥梁3D建模的拍摄频率 无人机桥梁3D建模的拍摄频率&#xff08;每秒拍摄照片数&#xff09;需根据建模精度、飞行速度、相机性能等因素综合确定。以下是专业级作业的详细参数分析&#xff1a; 1. 核心计算公式 拍摄频率&#xff08;fps&#xff09; \frac{飞行速度&…

ESP32-idf学习(三)esp32C3连接iot

一、前言 上一篇用蓝牙作为通信方式&#xff0c;虽然勉强完成了控制&#xff0c;但结果显然不是那么符合我们的预期&#xff0c;既然用蓝牙还需要研究一段时间&#xff0c;那我们就先整一些现成的&#xff0c;不需要研究的&#xff01;iot云平台&#xff01;这里当然也是通过w…

详解鸿蒙仓颉开发语言中的计时器

今天又到了大家喜闻乐见的科普环节&#xff0c;也可以说是踩坑环节&#xff0c;哈哈哈。今天聊一聊仓颉开发语言中的计时器&#xff0c;这部分可老有意思了。 为什么这么说呢&#xff0c;因为关于仓颉的计时器你几乎搜不到任何的文档&#xff0c;也没有相关的代码提示&#xf…

【计算机网络】第3章:传输层—拥塞控制原理

目录 一、PPT 二、总结 &#xff08;一&#xff09;拥塞的定义 &#xff08;二&#xff09;拥塞产生的原因 &#xff08;三&#xff09;拥塞控制的目标 &#xff08;四&#xff09;拥塞控制方法分类 1. 端到端拥塞控制 2. 网络辅助拥塞控制 &#xff08;五&#xff09;…

Vue3(watch,watchEffect,标签中ref的使用,TS,props,生命周期)

Vue3&#xff08;watch&#xff0c;watchEffect&#xff0c;标签中ref的使用,TS,props,生命周期&#xff09; watch监视 情况三&#xff1a;监视reactive定义的对象类型的数据 监视reactive定义的对象类型的数据&#xff0c;默认开启深度监视。地址没变&#xff0c;新值和旧…

【nssctf第三题】[NSSCTF 2022 Spring Recruit]easy C

这是题目&#xff0c;下载附件打开是个C文件 #include <stdio.h> #include <string.h>int main(){char a[]"wwwwwww";char b[]"dvxbQd";//try to find out the flagprintf("please input flag:");scanf(" %s",&a);if…

DBeaver导入/导出数据库时报错解决方案

导出&#xff1a; 报错&#xff1a;mysqldump: Got error: 2026: SSL connection error: error:0A000102:SSL routines::unsupported protocol when trying to connect 在额外的命令参数中添加"--ssl-modeDISABLED"可以关闭SSL服务&#xff0c;从而成功解决问题。这…

uniapp与微信小程序开发平台联调无法打开IDE

经测试属于网络问题。本机需要联网。否则会出现Hbuilder运行微信小程序到模拟器时无法打开 微信开发者工具 这个页面出不来会一直显示异常。这期间微信小程序开发工具的端口是通的 需要先联网

第十二节:第五部分:集合框架:Set集合的特点、底层原理、哈希表、去重复原理

Set系列集合特点 哈希值 HashSet集合的底层原理 HashSet集合去重复 代码 代码一&#xff1a;整体了解一下Set系列集合的特点 package com.itheima.day20_Collection_set;import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.…

【C++项目】:仿 muduo 库 One-Thread-One-Loop 式并发服务器

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;C从入门到精通 目录 &#x1f525; 前言 一&#xff1a;&#x1f525; 项目储备知识 &#x1f98b; HTTP 服务器&#x1f98b; Reactor 模型&#x1f380; 单 Reactor 单线程&#xff1a;单I/O多路…

基于大数据的个性化购房推荐系统设计与实现(源码+定制+开发)面向房产电商的智能购房推荐与数据可视化系统 基于Spark与Hive的房源数据挖掘与推荐系统设计

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

FFmpeg学习笔记

1. 播放器的架构 2. 播放器的渲染流程 3. ffmpeg下载与安装 3.0 查看PC是否已经安装了ffmpeg ffmpeg 3.1 下载 wget https://ffmpeg.org/releases/ffmpeg-7.0.tar.gz 3.2 解压 tar zxvf ffmpeg-7.0.tar.gz && cd ./ffmpeg-7.0 3.3 查看配置文件 ./configure …

Chrome 通过FTP,HTTP 调用 Everything 浏览和搜索本地文件系统

【提问1】 Chrome调用本地 everything.exe, everything 好像有本地 FTP 服务器&#xff1f; 【DeepSeek R1 回答】 是的&#xff0c;Everything 确实内置了 HTTP/FTP 服务器功能&#xff0c;这提供了一种相对安全的浏览器与本地应用交互的方式。以下是完整的实现方案&#x…

GpuGeek如何成为AI基础设施市场的中坚力量

AI时代&#xff0c;算力基础设施已成为支撑技术创新和产业升级的关键要素。作为国内专注服务算法工程师群体的智算平台&#xff0c;GpuGeek通过持续创新的服务模式、精准的市场定位和系统化的生态建设&#xff0c;正快速成长为AI基础设施领域的中坚力量。本文将深入分析GpuGeek…

【Hot 100】45. 跳跃游戏 II

目录 引言跳跃游戏 IIdp解题贪心解题 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】45. 跳跃游戏 II❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难&#xff01; 引言 跳跃…

Python数学可视化——显函数、隐函数及复杂曲线的交互式绘图技术

Python数学可视化——显函数、隐函数及复杂曲线的交互式绘图技术 一、引言 在科学计算和数据分析中&#xff0c;函数与方程的可视化是理解数学关系和物理现象的重要工具。本文基于Python的Tkinter和Matplotlib库&#xff0c;实现一个功能完善的函数与方程可视化工具&#xff…