目录
虚拟化技术栈 · Linux Kernel

KVM ioeventfd:省去门铃通知的用户态分派

请求内容由 virtqueue 传递,匹配的队列通知通过 eventfd 直接交给后端。

QEMU Internals: vhost architectureStefan Hajnoczi(QEMU / vhost 开发者) · 作者设计说明

1问题背景#

virtio 驱动将请求描述符放入共享 virtqueue 后,通过写通知寄存器告知后端处理请求。若每次通知都使 KVM_RUN 返回 QEMU,再由 QEMU 分派给后端,就会增加一次用户态分派。

2原有方案#

通用设备模拟让 KVM_RUN 返回 QEMU,再根据 I/O exit 分派寄存器写。这一路径支持完整的设备行为;对于仅表示队列有新请求的门铃通知,也需要经过同样的用户态分派。

3设计方案#

预先注册通知的地址、宽度和可选数据匹配条件。命中的写操作由 eventfd_signal 转为事件通知,请求数据仍由后端从 virtqueue 读取,KVM 无须参与后端设备的具体实现。

4改进分析#

匹配的通知写不再逐次使 KVM_RUN 返回用户态,省去了 QEMU 的寄存器写分派。KVM 与 vhost 通过 eventfd 交换通知,双方无须共享设备处理的内部接口。

5源码分析#

5.1通知地址与数据匹配#

ioeventfd_in_range 检查注册地址、访问宽度和可选数据条件。不匹配的访问由其余 I/O 设备继续处理,普通 MMIO 写的载荷不能按门铃通知忽略。

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 决定是否还需检查数据。数据比较位于完整函数的后半段,片段不包含它。

5.2eventfd 通知传递#

匹配成功后,ioeventfd_write 调用 eventfd_signal 通知后端。后端从 virtqueue 读取并处理请求,请求内容无须经过该函数。

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。

5.3virtio 队列通知注册#

legacy 通知寄存器共享一个地址,以写入值 n 识别队列;现代 MMIO 通知按队列地址注册。

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 中完成。

6适用范围与限制#

该路径只适用于通知语义,eventfd 不承载任意写入数据。硬件 VM exit、后端唤醒和队列同步的成本依然存在。

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

7参考资料#

QEMU Internals: vhost architecture

Linux v6.12 KVM_IOEVENTFD API

路径对比基于 KVM API、作者说明和源码;没有给出本站性能实测结论。

65 个匹配案例