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流程大体可分为两部分:
- 将io从ring环中取出,提交给磁盘驱动
- 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通知硬件,要处理数据了
相关文章:
virtio介绍 (三)--spdk作为virtio后端处理nvme盘io的流程--上
目录 一 简介 二 vhost-blk层 三 bdev层 四 lvol层 五 bdev_nvme层 六 硬件驱动层 七 完整取io调用栈流程 一 简介 上节介绍了virito的基本原理,后面根据实际代码介绍virtio的流程。virtio后端代码相对于前端代码更简单,我们先以spdk中的virtio后…...
关于BackgroundScheduler的pause
在APScheduler中,pausedTrue参数的作用对象取决于其使用场景: 1. 作用于调度器(Scheduler) 当在start()方法中使用时(如 scheduler.start(pausedTrue)) 表示调度器本身启动后立即进入暂停状态&…...

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

【Java学习笔记】异常
异常(Exception) 一、基本介绍 在 Java 程序中,将运行中发生的不正常情况称为 “异常”,开发过程中的语法错误和运行时发生的异常情况是不一样的。 二、异常的分类 1. Error(错误):Java 虚拟…...

MySQL:视图+用户管理+访问+连接池原理
一、视图 视图是一个虚拟表,其内容由查询定义。同真实的表一样(相当于是把查询的内容当成一个临时表来使用),视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表,基表的数据变化也会影响到视图。 1.1 为…...

neo4j 5.19.0安装、apoc csv导入导出 及相关问题处理
前言 突然有需求需要用apoc 导入 低版本的图谱数据,网上资料又比较少,所以就看官网资料并处理了apoc 导入的一些问题。 相关地址 apoc 官方安装网址 apoc 官方导出csv 教程地址 apoc 官方 导入 csv 地址 docker 安装 执行如下命令启动镜像 doc…...
C/C++ OpenCV 矩阵运算
C/C OpenCV 矩阵运算详解 💡 OpenCV 是一个强大的开源计算机视觉和机器学习库,它提供了丰富的矩阵运算功能,这对于图像处理和计算机视觉算法至关重要。本文将详细介绍如何使用 C/C 和 OpenCV 进行常见的矩阵运算。 矩阵的创建与初始化 在进…...

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

ESP32-idf学习(三)esp32C3连接iot
一、前言 上一篇用蓝牙作为通信方式,虽然勉强完成了控制,但结果显然不是那么符合我们的预期,既然用蓝牙还需要研究一段时间,那我们就先整一些现成的,不需要研究的!iot云平台!这里当然也是通过w…...

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

【计算机网络】第3章:传输层—拥塞控制原理
目录 一、PPT 二、总结 (一)拥塞的定义 (二)拥塞产生的原因 (三)拥塞控制的目标 (四)拥塞控制方法分类 1. 端到端拥塞控制 2. 网络辅助拥塞控制 (五)…...

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

【nssctf第三题】[NSSCTF 2022 Spring Recruit]easy C
这是题目,下载附件打开是个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…...
Cocos 打包 APK 兼容环境表(Android API Level 10~15)
使用 Cocos 打包 APK:Android 10 ~ Android 15 兼容版本对照表 ✅ 本表基于 Cocos Creator 3.x 实际测试及官方建议整理 📆 最后更新时间:2025年6月 💡 推荐使用 Android Studio 2022 或命令行构建工具 Android 版本API Level推荐…...
数据结构之堆:解析与应用
一、堆的核心定义与性质 堆是一种特殊的完全二叉树,分为最大堆和最小堆: 最大堆:每个节点的值 ≥ 子节点值,根节点为最大值。最小堆:每个节点的值 ≤ 子节点值,根节点为最小值。 关键性质: …...

DBeaver导入/导出数据库时报错解决方案
导出: 报错:mysqldump: Got error: 2026: SSL connection error: error:0A000102:SSL routines::unsupported protocol when trying to connect 在额外的命令参数中添加"--ssl-modeDISABLED"可以关闭SSL服务,从而成功解决问题。这…...
GPIO模拟串口通信
在资源受限的嵌入式项目中,GPIO模拟串口(UART)仍有实际需求。尽管现代MCU多数具备多个硬件串口,但实际项目中仍可能遇到串口数量不足的情况,尤其在低成本、小封装芯片的应用场景中。 一、GPIO模拟串口的基本原理 GPIO模拟串口,顾名思义,就是通过软件控制普通IO口的高低…...

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

第十二节:第五部分:集合框架:Set集合的特点、底层原理、哈希表、去重复原理
Set系列集合特点 哈希值 HashSet集合的底层原理 HashSet集合去重复 代码 代码一:整体了解一下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 式并发服务器
🌈 个人主页:Zfox_ 🔥 系列专栏:C从入门到精通 目录 🔥 前言 一:🔥 项目储备知识 🦋 HTTP 服务器🦋 Reactor 模型🎀 单 Reactor 单线程:单I/O多路…...

基于大数据的个性化购房推荐系统设计与实现(源码+定制+开发)面向房产电商的智能购房推荐与数据可视化系统 基于Spark与Hive的房源数据挖掘与推荐系统设计
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、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 服务器? 【DeepSeek R1 回答】 是的,Everything 确实内置了 HTTP/FTP 服务器功能,这提供了一种相对安全的浏览器与本地应用交互的方式。以下是完整的实现方案&#x…...

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

【Hot 100】45. 跳跃游戏 II
目录 引言跳跃游戏 IIdp解题贪心解题 🙋♂️ 作者:海码007📜 专栏:算法专栏💥 标题:【Hot 100】45. 跳跃游戏 II❣️ 寄语:书到用时方恨少,事非经过不知难! 引言 跳跃…...
Codeforces Round 1026 (Div. 2) C. Racing
Codeforces Round 1026 (Div. 2) C. Racing 题目 In 2077, a sport called hobby-droning is gaining popularity among robots. You already have a drone, and you want to win. For this, your drone needs to fly through a course with n n n obstacles. The i i i-…...
Python库CloudScraper详细使用(绕过 Cloudflare 的反机器人页面的 Python 模块)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、CloudScraper概述1.1 CloudScraper 介绍1.2 安装二、基本使用方法2.1 创建scraper实例2.2 发送请求2.3 带参数的请求2.4 自定义浏览器指纹2.5 设置代理2.6 自定义请求头三、高级配置3.1 处理Cloudflare挑战-自动处理…...
oracle sql 语句 优化方法
1、表尽量使用别名,字段尽量使用别名.字段名,这样子,可以减少oracle数据库解析字段名。而且把 不需要的字段名剔除掉,只保留有用的字段名,不要一直使用 select *。 2、关联查询时,选择好主表 。oracle解析…...

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

代码随想录打卡|Day51 图论(dijkstra(堆优化版)精讲、Bellman_ford 算法精讲)
图论part09 dijkstra(堆优化版)精讲(不熟悉) 代码随想录链接 题目链接 import java.util.*;class Edge {int to; // 邻接顶点int val; // 边的权重Edge(int to, int val) {this.to to;this.val val;} }class MyComparison implements Comparator<…...