Linux Kernel/KVM / virtio 通知路径

把虚拟机的通知写直接转成 eventfd 信号

KVM 预先匹配设备通知地址,命中后 signal eventfd,使通知无需让 vCPU 逐次退回 QEMU 分派。

v6.12并发与一致性核验 2026-10-09
相关概念

工程问题#

virtio 驱动已经把请求描述符放入共享 virtqueue,通知寄存器写只是在告诉后端有新工作;若每次都让 KVM_RUN 返回 QEMU,再分派给后端,就在通知路径上多走一段。

实际实现#

QEMU 先把通知地址、宽度、可选队列号与 eventfd 建立关联。KVM 的 ioeventfd_write 命中规则后发出 eventfd 信号;后端从自己的队列读取真正的请求内容。

处理流程#

  1. QEMU 注册设备通知地址与 EventNotifier 的关系
  2. KVM 按地址、宽度及可选数据匹配一次 PIO/MMIO 写
  3. 命中时发出 eventfd 信号,后端处理共享队列中的请求

源码片段#

固定版本 · 原文摘录
virt/kvm/eventfd.cv6.12 · L766–L778
查看原文件
766/* MMIO/PIO writes trigger an event if the addr/val match */767static int768ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr,769		int len, const void *val)770{771	struct _ioeventfd *p = to_ioeventfd(this);772773	if (!ioeventfd_in_range(p, addr, len, val))774		return -EOPNOTSUPP;775776	eventfd_signal(p->eventfd);777	return 0;778}
ioeventfd_write

命中预先登记的地址、宽度和可选写入值后,只向 eventfd 发信号并返回已处理;不匹配时返回 -EOPNOTSUPP,交由其余 I/O 处理路径继续处理。这里避免的是 KVM_RUN 返回用户态,并不意味着没有硬件 VM exit。

virt/kvm/eventfd.cv6.12 · L721–L740
查看原文件
721static bool722ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val)723{724	u64 _val;725726	if (addr != p->addr)727		/* address must be precise for a hit */728		return false;729730	if (!p->length)731		/* length = 0 means only look at the address, so always a hit */732		return true;733734	if (len != p->length)735		/* address-range must be precise for a hit */736		return false;737738	if (p->wildcard)739		/* all else equal, wildcard is always a hit */740		return true;
ioeventfd_in_range

地址必须精确匹配。长度为 0 时忽略写入宽度;非零长度必须匹配,wildcard 决定是否还需检查数据。数据比较位于完整函数的后半段,片段不包含它。

hw/virtio/virtio-pci.cv9.2.0 · L335–L355
查看原文件
335    bool modern_pio = proxy->flags & VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY;336    MemoryRegion *modern_mr = &proxy->notify.mr;337    MemoryRegion *modern_notify_mr = &proxy->notify_pio.mr;338    MemoryRegion *legacy_mr = &proxy->bar;339    hwaddr modern_addr = virtio_pci_queue_mem_mult(proxy) *340                         virtio_get_queue_index(vq);341    hwaddr legacy_addr = VIRTIO_PCI_QUEUE_NOTIFY;342343    if (assign) {344        if (modern) {345            memory_region_add_eventfd(modern_mr, modern_addr, 0,346                                      false, n, notifier);347            if (modern_pio) {348                memory_region_add_eventfd(modern_notify_mr, 0, 2,349                                              true, n, notifier);350            }351        }352        if (legacy) {353            memory_region_add_eventfd(legacy_mr, legacy_addr, 2,354                                      true, n, notifier);355        }
virtio_pci_ioeventfd_assign

QEMU 把 virtqueue 的通知地址与 EventNotifier 关联:现代 MMIO 分支通过每队列地址区分通知,legacy 分支在共享通知寄存器上匹配队列号 n。这一段只展示注册,具体 KVM_IOEVENTFD ioctl 在 KVM MemoryListener 中完成。

内核写处理、匹配规则和 QEMU 注册位置分别摘录,未拼接为同一个函数。

收益与取舍#

工程分析

带来的收益

  • 已注册且命中的通知写无需逐次让 KVM_RUN 返回用户态
  • eventfd 作为通知接口,使 KVM 与 vhost 后端不必直接依赖对方的设备实现

付出的代价

  • 只适合能用预登记条件识别的通知;eventfd 信号不携带任意 MMIO 写入负载
  • 仍可能发生硬件 VM exit、后端唤醒和队列同步,不能称为零退出或零开销

可比较的选择

用户态处理 KVM I/O exit通用性强,能读取每次访问的完整参数并执行设备模拟;纯门铃通知也需要往返用户态。

后端轮询 virtqueue可以减少等待唤醒,但空闲时也占用 CPU;与 eventfd 通知的成本和适用负载不同。

版本与适用范围#

代码固定为 Linux v6.12 与 QEMU v9.2.0。这里分析来宾到后端的 ioeventfd 通知,不包含反向中断注入 irqfd;不把 userspace exit 与硬件 VM exit 混为一谈。

参考来源#

源码、官方文档与原始论文
01
ioeventfd_write

virt/kvm/eventfd.c · ioeventfd_write

源码v6.12
02
ioeventfd_in_range

virt/kvm/eventfd.c · ioeventfd_in_range

源码v6.12
03
virtio_pci_ioeventfd_assign

hw/virtio/virtio-pci.c · virtio_pci_ioeventfd_assign

源码v9.2.0
04官方文档Linux v6.12
05官方文档2011-09-07(历史设计说明)

相关实现与比较#

2 个相关案例
6 个匹配案例