virtio前端驱动通知机制分析

news2024/11/24 22:26:23

virtio前端驱动通知机制分析


virtio 前后端主要通过PCI配置空间的寄存器来完成通信,I/O 请求的数据地址存放于 vring 中,并通过共享vring这个区域来实现 I/O 请求数据的共享。

在这里插入图片描述

由上图可知,虚拟机与主机之间交互用到了两个结构体:pci_configvringvirtio 设备的前后端通知机制由PCI配置空间完成。


PCI配置空间

虚拟机中创建的 virtio 设备都是 PCI 设备,他们被挂载在 PCI 总线上,遵循通用的 PCI 设备的发现、挂在等机制。

当虚拟机启动时发现 virtio PCI 设备时,只有 PCI 配置空间可以被访问,配置空间内保存着该设备工作所需的厂家、功能、资源要求等信息,通过对这个空间信息的读取,完成对 PCI 设备的配置。同时配置空间上有一块存储器空间,里面包含了一些寄存器和 I/O 空间。

前后端的通知消息就是写在这些存储空间的寄存器,virtio 会为它的 PCI设备注册一个 PCI BAR 来访问这块寄存器空间。配置空间如下图所示:

在这里插入图片描述

PCI配置空间的获取

虚拟机系统在启动过程中在 PCI 总线上发现 virtio-pci 设备,就会调用 virtio-pciprobe 函数。该函数会将 PCI 配置空间上的寄存器映射到内存空间,并将这个地址赋值给 virtio_pci_deviceioaddr 变量。之后要对 PCI 配置空间上的寄存器操作时,只需要 ioaddr+偏移量

virtio_pci_legacy_probe
    ---->vp_legacy_probe
    	---->pci_iomap(pci_dev, 0, 0);

virtio_pci_legacy_probe

源码位置/drivers/virtio/virtio_pci_legacy.c

int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
{
	struct virtio_pci_legacy_device *ldev = &vp_dev->ldev;
	struct pci_dev *pci_dev = vp_dev->pci_dev;
	int rc;

	ldev->pci_dev = pci_dev;

	rc = vp_legacy_probe(ldev);
	if (rc)
		return rc;

	vp_dev->isr = ldev->isr;
	vp_dev->vdev.id = ldev->id;

	vp_dev->vdev.config = &virtio_pci_config_ops;

	vp_dev->config_vector = vp_config_vector;
	vp_dev->setup_vq = setup_vq;
	vp_dev->del_vq = del_vq;

	return 0;
}

vp_legacy_probe

源码位置/drivers/virtio/virtio_pci_legacy_dev.c

int vp_legacy_probe(struct virtio_pci_legacy_device *ldev)
{
	struct pci_dev *pci_dev = ldev->pci_dev;
	int rc;

	/* We only own devices >= 0x1000 and <= 0x103f: leave the rest. */
	if (pci_dev->device < 0x1000 || pci_dev->device > 0x103f)
		return -ENODEV;

	if (pci_dev->revision != VIRTIO_PCI_ABI_VERSION)
		return -ENODEV;

	rc = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(64));
	if (rc) {
		rc = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32));
	} else {
		/*
		 * The virtio ring base address is expressed as a 32-bit PFN,
		 * with a page size of 1 << VIRTIO_PCI_QUEUE_ADDR_SHIFT.
		 */
		dma_set_coherent_mask(&pci_dev->dev,
				DMA_BIT_MASK(32 + VIRTIO_PCI_QUEUE_ADDR_SHIFT));
	}

	if (rc)
		dev_warn(&pci_dev->dev, "Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might not work.\n");

	rc = pci_request_region(pci_dev, 0, "virtio-pci-legacy");
	if (rc)
		return rc;
	/* 将PCI配置空间上的寄存器映射到内存空间 */
	ldev->ioaddr = pci_iomap(pci_dev, 0, 0);
	if (!ldev->ioaddr) {
		rc = -EIO;
		goto err_iomap;
	}

	ldev->isr = ldev->ioaddr + VIRTIO_PCI_ISR;

	ldev->id.vendor = pci_dev->subsystem_vendor;
	ldev->id.device = pci_dev->subsystem_device;

	return 0;
err_iomap:
	pci_release_region(pci_dev, 0);
	return rc;
}

pci_iomap 函数完成 PCI BAR 的映射,第一个参数是 PCI 设备的指针,第二个参数指定我们要映射的是0号 BAR ,第三个参数确定要映射的 BAR 空间多大,当第三个参数为0时,就将整个 BAR 空间都映射到内存空间上。 VirtioPCI 设备的0号BAR指向的就是配置空间的寄存器空间,也就是配置空间上用于消息通知的寄存器。

通过 pci_iomap 之后,我们就可以像操作普通内存一样(调用 ioreadiowrite )来读写 PCI 硬件设备上的寄存器。

前端驱动通知KVM

总流程

virtqueue_kick
    ---->virtqueue_kick_prepare	/* 再次判断是否需要kick,需要的话调用virtqueue_notify */
    	---->virtqueue_kick_prepare_split
    		---->vring_need_event
    		---->vring_avail_event
    ---->virtqueue_notify
    	---->vq->notify(_vq)
    		---->vp_notify	/* iowrite VIRTIO_PCI_QUEUE_NOTIFY通知KVM */

virtio 发包流程中,start_xmit 的最后调用了 virtqueue_kick 函数来通知后端驱动。

virtqueue_kick 通知后端驱动,host主机队列里面有消息需要它处理。

virtqueue_kick

源码位置/drivers/virtio/virtio_ring.c

bool virtqueue_kick(struct virtqueue *vq)
{
    /* 再次判断是否需要kick,需要的话调用virtqueue_notify */
	if (virtqueue_kick_prepare(vq))
		return virtqueue_notify(vq);
	return true;
}
virtqueue_kick_prepare

源码位置 /drivers/virtio/virtio_ring.c

bool virtqueue_kick_prepare(struct virtqueue *_vq)
{
	struct vring_virtqueue *vq = to_vvq(_vq);

	return vq->packed_ring ? virtqueue_kick_prepare_packed(_vq) :
				 virtqueue_kick_prepare_split(_vq);
}

virtqueue_kick_prepare_split

源码位置 /drivers/virtio/virtio_ring.c

static bool virtqueue_kick_prepare_split(struct virtqueue *_vq)
{
	struct vring_virtqueue *vq = to_vvq(_vq);
    /* old表示上次kick后的vring.avail->idx;new表示当前idx */
	u16 new, old;
	bool needs_kick;

	START_USE(vq);
	/* We need to expose available array entries before checking avail
	 * event. */
	virtio_mb(vq->weak_barriers);

	old = vq->split.avail_idx_shadow - vq->num_added;
	new = vq->split.avail_idx_shadow;
	vq->num_added = 0;

	LAST_ADD_TIME_CHECK(vq);
	LAST_ADD_TIME_INVALID(vq);

    /* 当VIRTIO_F_EVENT_IDX被置位vq->event为1 */
	if (vq->event) {
		needs_kick = vring_need_event(virtio16_to_cpu(_vq->vdev,
					vring_avail_event(&vq->split.vring)),
					      new, old);
	} else {
		needs_kick = !(vq->split.vring.used->flags &
					cpu_to_virtio16(_vq->vdev,
						VRING_USED_F_NO_NOTIFY));
	}
	END_USE(vq);
	return needs_kick;
}

vring_need_event

vring_need_event 函数调用中传入的 event_idx 经过计算时 used ring 的最后一个元素的值。

源码位置/include/uapi/linux/virtio_ring.h

static inline int vring_need_event(__u16 event_idx, __u16 new_idx, __u16 old)
{
	/* Note: Xen has similar logic for notification hold-off
	 * in include/xen/interface/io/ring.h with req_event and req_prod
	 * corresponding to event_idx + 1 and new_idx respectively.
	 * Note also that req_event and req_prod in Xen start at 1,
	 * event indexes in virtio start at 0. */
	return (__u16)(new_idx - event_idx - 1) < (__u16)(new_idx - old);
}

在上述代码中,如果 (u16)(new_idx - event_idx - 1) < (u16)(new_idx - old) 成立说明 backend 的处理速度够快,那么返回 true 表示可以 kick backend ,否则说明 backend 当前处理的位置 event_idx 落后于 old ,此时 backend 处理速度较慢,返回 false 等待下次一起 kick backend

vring_avail_event

源码位置/include/uapi/linux/virtio_ring.h

#define vring_avail_event(vr) (*(__virtio16 *)&(vr)->used->ring[(vr)->num])

对于VirtIO的机制来说,backend一直消耗avail ring,frontend一直消耗used ring,因此backend用used ring的last entry告诉frontend自己当前处理到哪了。

virtqueue_notify

virtqueue_notify 函数调用了 vq->notify(_vq)notify 定义在 struct ving_virtqueue 中, notify 具体是哪个函数是在 setup_vq 中创建 virtqueue 时绑定的。

源码位置/drivers/virtio/virtio_ring.c

bool virtqueue_notify(struct virtqueue *_vq)
{
	struct vring_virtqueue *vq = to_vvq(_vq);

	if (unlikely(vq->broken))
		return false;

	/* Prod other side to tell it about changes. */
	if (!vq->notify(_vq)) {
		vq->broken = true;
		return false;
	}
	return true;
}
vring_virtqueue
struct vring_virtqueue {
	struct virtqueue vq;

	/* Is this a packed ring? */
	bool packed_ring;

	/* Is DMA API used? */
	bool use_dma_api;

	/* Can we use weak barriers? */
	bool weak_barriers;

	/* Other side has made a mess, don't try any more. */
	bool broken;

	/* Host supports indirect buffers */
	bool indirect;

	/* Host publishes avail event idx */
	bool event;

	/* Head of free buffer list. */
	unsigned int free_head;
	/* Number we've added since last sync. */
	unsigned int num_added;

	/* Last used index  we've seen.
	 * for split ring, it just contains last used index
	 * for packed ring:
	 * bits up to VRING_PACKED_EVENT_F_WRAP_CTR include the last used index.
	 * bits from VRING_PACKED_EVENT_F_WRAP_CTR include the used wrap counter.
	 */
	u16 last_used_idx;

	/* Hint for event idx: already triggered no need to disable. */
	bool event_triggered;

	union {
		/* Available for split ring */
		struct vring_virtqueue_split split;

		/* Available for packed ring */
		struct vring_virtqueue_packed packed;
	};

	/* How to notify other side. FIXME: commonalize hcalls! */
	bool (*notify)(struct virtqueue *vq);

	/* DMA, allocation, and size information */
	bool we_own_ring;

#ifdef DEBUG
	/* They're supposed to lock for us. */
	unsigned int in_use;

	/* Figure out if their kicks are too delayed. */
	bool last_add_time_valid;
	ktime_t last_add_time;
#endif
};
setup_vq
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
				  struct virtio_pci_vq_info *info,
				  unsigned int index,
				  void (*callback)(struct virtqueue *vq),
				  const char *name,
				  bool ctx,
				  u16 msix_vec)
{
	struct virtqueue *vq;
	u16 num;
	int err;
	u64 q_pfn;

	/* Check if queue is either not available or already active. */
	num = vp_legacy_get_queue_size(&vp_dev->ldev, index);
	if (!num || vp_legacy_get_queue_enable(&vp_dev->ldev, index))
		return ERR_PTR(-ENOENT);

	info->msix_vector = msix_vec;

	/* create the vring */
	vq = vring_create_virtqueue(index, num,
				    VIRTIO_PCI_VRING_ALIGN, &vp_dev->vdev,
				    true, false, ctx,
				    vp_notify, callback, name);
	if (!vq)
		return ERR_PTR(-ENOMEM);

	vq->num_max = num;

	q_pfn = virtqueue_get_desc_addr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;
	if (q_pfn >> 32) {
		dev_err(&vp_dev->pci_dev->dev,
			"platform bug: legacy virtio-pci must not be used with RAM above 0x%llxGB\n",
			0x1ULL << (32 + PAGE_SHIFT - 30));
		err = -E2BIG;
		goto out_del_vq;
	}

	/* activate the queue */
	vp_legacy_set_queue_address(&vp_dev->ldev, index, q_pfn);

    /* 需要写VIRTIO_PCI_QUEUE_NOTIFY,在vp_notify中用到 */
	vq->priv = (void __force *)vp_dev->ldev.ioaddr + VIRTIO_PCI_QUEUE_NOTIFY;

	if (msix_vec != VIRTIO_MSI_NO_VECTOR) {
		msix_vec = vp_legacy_queue_vector(&vp_dev->ldev, index, msix_vec);
		if (msix_vec == VIRTIO_MSI_NO_VECTOR) {
			err = -EBUSY;
			goto out_deactivate;
		}
	}

	return vq;

out_deactivate:
	vp_legacy_set_queue_address(&vp_dev->ldev, index, 0);
out_del_vq:
	vring_del_virtqueue(vq);
	return ERR_PTR(err);
}

setup_vq 函数中调用了 vring_create_virtqueuenotify 绑定为了 vp_notify

vp_notify
bool vp_notify(struct virtqueue *vq)
{
	/* we write the queue's selector into the notification register to
	 * signal the other end */
	iowrite16(vq->index, (void __iomem *)vq->priv);
	return true;
}

经过一系列的配置,可总结如下:

首先 virtqueue_kick_prepare 根据 feature bit 以及后端的处理速度来判断时候需要通知,如果需要则调用 vp_notify ,在其中 iowrite VIRTIO_PCI_QUEUE_NOTIFY 通知后端 KVM 主机。

在这里插入图片描述

图片引用自virtio前端通知机制分析 | Lauren·weblog (lihanlu.cn)

KVM通知QEMU

在这里插入图片描述

图片引用自virtio前端通知机制分析 | Lauren·weblog (lihanlu.cn)


后端驱动

在这里插入图片描述

图片引用自virtio前端通知机制分析 | Lauren·weblog (lihanlu.cn)


参考

virtio前端通知机制分析 | Lauren·weblog (lihanlu.cn)

virtIO前后端notify机制详解 - jack.chen - 博客园 (cnblogs.com)

virtio简介(一)—— 框架分析 - Edver - 博客园 (cnblogs.com)


🥌
😓
😳

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

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

相关文章

智能网联汽车行业发展

智能网 联汽车信息安全发展趋势 智能网联汽车行业发展 根据工信部发布的《国家车联网产业标准体系建设 指南&#xff08;智能网联汽车&#xff09;》的定义&#xff0c;智能网联汽车是指搭载先进的车载传感器 、控制器、执行器等装置&#xff0c;并融合现代通信与网络技术&a…

明道云联合思迈特打造会员管理应用可视化联合解决方案

背景介绍 明道云在协助企业数字化转型过程中&#xff0c;发现客户对利用业务数据形成企业级报表和数据可视化大屏的需求十分强烈。为了满足这种需求&#xff0c;企业通常需要成立专门的数据分析团队&#xff0c;但这需要巨大的人力和财力投入&#xff0c;时间周期也较长。为了…

信息数智化招采系统源码——信息数智化招采系统

信息数智化招采系统 服务框架&#xff1a;Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构&#xff1a;VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术&#xff1a;Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monitor、Stre…

React 学习笔记总结(五)

文章目录1. React 嵌套路由(多级路由)2. params参数 与 query参数3. React路由组件 传递params参数数据4. React路由组件 传递search参数5. React路由组件 传递search参数6. React路由组件 特殊情况: 刷新页面7. React路由 的 push 和 replace8. React的 编程式路由9. React路由…

Transformer架构:位置编码

2017年&#xff0c;Google等人提出了Vaswani提出了一种新颖的纯注意力序列到序列架构&#xff0c;闻名学术界与工业界的Transformer架构横空出世&#xff0c;它的可并行化训练能力和优越的性能称为自然语言处理领域和计算机视觉领域研究人员的热门选择&#xff0c;本文将重点讨…

elasticsearch--Master选举

最近一直在学习elasticsear相关的东西&#xff0c;在这学习的过程中记录一下比较重要的学习内容。方便以后看的时候加深印象。 假如宕机的节点是Master节点 下面是Maste节点选举 的流程图 在findMaster的方法中每隔一段时间就会ping所有的节点&#xff0c;看看有没有哪个节…

java设计模式三

文章目录4) 创建IOC容器相关的类5) 自定义IOC容器测试6) 案例中使用到的设计模式7.2 剖析MyBatis框架中用到的经典设计模式7.2.1 MyBatis回顾7.2.1.1 MyBatis与ORM框架7.2.1.1 MyBatis的基础使用7.2.2 MyBatis中使用到的设计模式7.2.2.1 Builder模式7.2.2.2 工厂模式7.2.2.3 单…

基于Java开发(PC)小说自检测系统【100010061】

Java 语言与系统设计课程&#xff08;小说自检测系统&#xff09; 一、目的与要求 ​ 自行下载最喜爱的小说 1 部。存到服务器中&#xff0c;格式自定。一般存储为文本文档。要求长篇小说&#xff0c;20 万字以上。举例说明&#xff1a;下载《三国演义》保存在服务器端。 ​…

Secure Boot功能简析

在数据中心中&#xff0c;云服务器由各种处理设备和外围组件&#xff08;如加速器和存储设备&#xff09;组成&#xff0c;这些设备通常都运行着固件。对云平台服务商来说&#xff0c;为保证这些设备的安全可靠&#xff0c;需要一种或多种机制&#xff0c;来保证这些设备在测试…

XYplorer使用教程

XYplorer使用教程 XYplorer是Windows的文件管理器。它具有标签式浏览&#xff0c;强大的文件搜索功能&#xff0c;多功能预览&#xff0c;高度可定制的界面&#xff0c;可选的双窗格以及一系列独特的方法&#xff0c;可以有效地自动执行频繁重复的任务。它快速&#xff0c;轻便…

【DCDC转换器】BUCK电路的演进

本文将是对BUCK型DCDC转换器的起步介绍&#xff0c;从BUCK电路模型的建立出发&#xff0c;可以对转换器原理有更清晰的认识。其次对三种不同类型开关电源转换器的原理进行计算&#xff0c;转换器的原理基本是类似的&#xff0c;相同的分析方法可以套用在其他模型上。最后引入了…

PHP基本语法(1)

前言&#xff1a;PHP是什么呢&#xff1f;PHP是一种后端开发用的语言&#xff0c;简单点说制作的网页分为静态和动态&#xff0c;静态网页用户体验性差&#xff0c;动态网页用户可以进行交互&#xff0c;而这种交互就需要PHP了。所以PHP他就是一门用于后端开发的语言。 注意&a…

操作系统实验1:操作系统的引导

操作系统实验1:操作系统的引导 现在是2022年12月22日&#xff0c;本人一如既往又是ddl战神&#x1f605; (虽然一周前刚立过flag) 两个月前就布置了的内容硬是拖到现在&#xff0c;这波实在是看不下去自己了&#x1f605;&#x1f605;&#x1f605; 拖到了ddl的最后一天&…

String、StringBuffer、StringBuilder的区别

文章目录一、StringBuffer1、介绍2、StringBuffer中方法二、StringBuilder1.介绍2.常用方法总结一、StringBuffer 1、介绍 是可以存储和操作字符串&#xff0c;即包含多个字符的字符串数据。对于StringBuffer而言&#xff0c;本身是一个具体的操作类&#xff0c;所以不能像St…

推送MOBPUSH-API说明

消息监听接口 MobPushReceiver: 消息监听接口&#xff08;包含接收自定义消息、通知消息、通知栏点击事件、别名和标签变更操作等&#xff09; MobPush.addPushReceiver(MobPushReceiver receiver): 设置消息监听 MobPush.removePushReceiver(MobPushReceiver receiver): 移除…

2022年全国最新消防设施操作员模拟试题题库及答案

百分百题库提供消防设施操作员考试试题、消防设施操作员考试预测题、消防设施操作员考试真题、消防设施操作员证考试题库等,提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 38.下列说法正确的是&#xff08;&#xff09;。 A.低压供配电系统中&…

vue的基本概念与vue2的指令

目录 一、 Vue的基本概念 1、前端技术的发展&#xff08;html、CSS、JavaScript&#xff09; ​ 2、MVVM架构&#xff1a; 数据的双向绑定&#xff1a; 二、Vue开发的方式 1、基本方式&#xff1a;在页面中引入vue.js文件。&#xff08;vscode&#xff09; 2、组件…

智能网 联汽车信息安全发展趋势

智能网 联汽车信息安全发展趋势 智能网联汽车行业发展 根据工信部发布的《国家车联网产业标准体系建设 指南&#xff08;智能网联汽车&#xff09;》的定义&#xff0c;智能网联汽车是指搭载先进的车载传感器 、控制器、执行器等装置&#xff0c;并融合现代通信与网络技术&a…

Android设计模式详解之备忘录模式

前言 备忘录模式是一种行为模式&#xff0c;该模式用于保存对象当前状态&#xff0c;并且在之后可以再次恢复到此状态&#xff1b; 定义&#xff1a;在不破坏封闭的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;这样&#xf…

【单目3D目标检测】MonoDLE论文精读与代码解析

文章目录PrefaceAbstractContributionsDiagnostic ExperimentsPipelineRevisiting Center DetectionTraining SamplesIoU Oriented OptimizationExperimental ResultsPreface [CVPR2021] Ma X, Zhang Y, Xu D. Delving into localization errors for monocular 3d object detec…