qemu-kvm的ioeventfd机制
qemu-kvm的ioeventfd机制
Guest一个完整的IO流程包括从虚拟机内部到KVM,再到QEMU,并由QEMU最终进行分发,IO完成之后的原路返回。这样的一次路径称为同步IO,即指Guest需要等待IO操作的结果才能继续运行,但是存在这样一种情况,即某次IO操作只是作为一个通知事件,用于通知QEMU/KVM完成另一个具体的IO,这种情况下没有必要像普通IO一样等待数据完全写完,只需要触发通知并等待具体IO完成即可。
ioeventfd正是为IO通知提供机制的东西,QEMU可以将虚拟机特定地址关联一个eventfd,对该eventfd进行POLL,并利用ioctl(KVM_IOEVENTFD)向KVM注册这段特定地址,当Guest进行IO操作exit到kvm后,kvm可以判断本次exit是否发生在这段特定地址中,如果是,则直接调用eventfd_signal发送信号到对应的eventfd,导致QEMU的监听循环返回,触发具体的操作函数,进行普通IO操作。这样的一次IO操作相比于不使用ioeventfd的IO操作,能节省一次在QEMU中分发IO请求和处理IO请求的时间。
QEMU注册ioeventfd
注册EventNotifier
QEMU进行ioeventfd注册的时候需要一个EventNotifier,该EventNotifier由event_notifier_init()初始化,event_notifier_init中判断系统是否支持EVENTFD机制,如果支持,那么EventNotifier中的rfd和wfd相等,均为eventfd()系统调用返回的新建的fd,并根据event_notifier_init收到的参数active决定是否唤醒POLLIN事件,即直接触发eventfd/EventNotifer对应的handler。
如果系统不支持EVENTFD机制,则QEMU会利用pipe模拟eventfd,略过不看。
关联IO地址&注册进KVM
在注册了EventNotifier之后,需要将EventNotifier中含有的fd(ioeventfd)与对应的Guest IO地址关联起来。
核心函数为memory_region_add_eventfd。
参数中:
- mr指IO地址所在的MemoryRegion
- addr表示IO地址(GPA)
- size表示IO地址的大小
- match_data是一个bool值,表示的是Guest向addr写入的值是否要与参数data完全一致才让KVM走ioeventfd路径,如果match_data为true,那么需要完全一致才让KVM走ioeventfd路径,如果为false。则不需要完全一致。
- data与match_data共同作用,用于限制Guest向addr写入的值
- e指前面注册的EventNotifier
MemoryRegion中有很多ioeventfd,他们以地址从小到大的顺序排列,ioeventfd_nb是MemoryRegion中ioeventfd的数量,通过for循环找到本次要添加的ioeventfd应该放在ioeventfd数组中的什么位置,为ioeventfd数组分配原大小+sizeof(ioeventfd)的空间,然后将之前找到的ioeventfd数组中位置之后的ioeventfd向后移动一个位置,然后将新的ioeventfd插入到ioeventfd数组中。最后设置ioevetfd_update_pending标志,调用memory_region_transaction_commit更新KVM中的ioeventfd布局。
即memory_region_add_eventfd最终会调用memory_region_transaction_commit,而后者会调用eventfd_add函数,该eventfd_add函数在qemu中的定义如下:
对于MMIO和PIO,最终调用的eventfd_add函数不同,MMIO对应的是kvm_mem_ioeventfd_add,而PIO调用的是kvm_io_ioeventfd_add。KVM对MMIO和PIO注册的ioeventfd进行分辨,靠的是在调用kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &iofd)中的iofd->flags进行辨认,如果flag为0,则为MMIO,如果flag为2,则为PIO。
接下来分别看这两个不同的eventfd_add函数。
kvm_io_ioeventfd_add
kvm_io_ioeventfd_add的逻辑很简单,就是先获取本次要注册到kvm的ioeventfd的相关信息,然后调用ioctl注册进kvm。
kvm_mem_ioeventfd_add
可以看到,kvm_mem_ioeventfd_add与kvm_io_ioeventfd_add的处理步骤几乎完全一样,只是在kvm_ioeventfd结构中将flags置为0,标志这是一个MMIO ioeventfd注册。
KVM注册ioeventfd
在QEMU调用了kvm_vm_ioctl(KVM_IOEVENTFD)之后,kvm会对该ioctl做出反应。
kvm在获得了QEMU传入的参数,也就是kvm_ioeventfd结构的值之后,会调用kvm_ioeventfd。
kvm_assign_ioeventfd中首先从kvm_ioeventfd->flags中提取出该eventfd是MMIO还是PIO,并获得相应的总线号,也就是代码中的bus_idx,然后对kvm_ioeventfd结构中的flags进行一些检查,最终调用kvm_assign_ioeventfd_idx进行实际关联。
以上代码段为kvm_assign_ioeventfd_idx,即,将ioeventfd和具体IO地址进行关联的主要过程。其中的核心数据为_ioeventfd,具体结构如下:
list用于将当前ioeventfd链接到kvm的ioeventfd链表中去.
addr是ioeventfd对应的IO地址.
Length指的是eventfd关联的长度.
eventfd即指的是该ioeventfd对应的eventfd.
datamatch用于确认Guest访问该io地址是否需要全匹配才走ioeventfd路径.
dev用于将该ioeventfd与Guest关联起来(通过注册该dev到Guest实现).
bus_idx指的是该ioeventfd要注册到kvm的MMIO总线还是PIO总线.
wildcard与datamatch互斥,如果kvm_ioeventfd中datamatch为false,则_ioeventfd->wildcard设备true.
所以_ioeventfd描述符了一个ioeventfd要注册到kvm中的所有信息,其中包含了ioeventfd信息和需要注册到Guest的总线和设备信息。
所以整个KVM注册ioeventfd的逻辑是:
- 将一个ioeventfd与一个虚拟设备dev联系起来
- 该虚拟设备dev拥有写函数
- 当Guest访问ioeventfd对应的io地址时,则调用虚拟设备的write方法。
需要注意的是,ioeventfd对应的文件操作只有write操作,而没有read操作。
write操作对应Guest中写入ioeventfd对应的IO地址时触发的操作,也就是Guest执行OUT类汇编指令时触发的操作,相反read操作就是Guest执行IN类汇编指令时触发的操作,OUT类指令只是简单向外部输出数据,无需等待QEMU处理完成即可继续运行Guest,但IN指令需要从外部获取数据,必须要等待QEMU处理完成IO请求再继续运行Guest。
ioeventfd设计的初衷就是节省Guest运行OUT类指令时的时间,IN类指令执行时间无法节省,因此这里的ioeventfd 文件操作中只有write而没有read。
ioeventfd对应的虚拟dev的操作(write)
可以看到ioeventfd_write函数首先从kvm_io_device得到了_ioeventfd,然后检查访问的地址和长度是否符合ioeventfd设置的条件,如果符合,则触发eventfd_signal,后者增加了eventfd_ctx->count的值,并唤醒等待队列中的EPOLLIN进程。
虚拟机进行IO操作时QEMU-kvm的处理
当虚拟机向注册了ioeventfd的地址写数据时,与所有IO操作一样,会产生vmexit,接下来的函数处理流程为:
kvm_io_bus_write首先构造了一个kvm_io_range结构,其中记录了本次Guest操作的IO地址和长度,然后调用__kvm_io_bus_write.
在__kvm_io_bus_write中,kvm_io_bus_get_first_dev用于获得bus上由kvm_io_range指定的具体地址和长度范围内的第一个设备的id,然后在bus的这个地址范围内,针对每一个设备调用kvm_iodevice_write,该函数会调用每个设备之前注册好的kvm_io_device_ops操作函数,对于ioeventfd”设备”来说,就是我们上面提到的ioeventfd_write,该函数检查访问的地址和长度是否符合ioeventfd设置的要求,如果符合则调用eventfd_signal触发一次POLLIN事件,如果QEMU有对该eventfd的检测,便会在QEMU中进行本次IO的处理,与此同时,kvm中的kernel_pio会返回0,表示成功完成了IO请求。
总结
整个ioeventfd的逻辑流程如下:
- QEMU分配一个eventfd,并将该eventfd加入KVM维护的eventfd数组中
- QEMU向KVM发送更新eventfd数组内容的请求
- QEMU构造一个包含IO地址,IO地址范围等元素的ioeventfd结构,并向KVM发送注册ioeventfd请求
- KVM根据传入的ioeventfd参数内容确定该段IO地址所属的总线,并在该总线上注册一个ioeventfd虚拟设备,该虚拟设备的write方法也被注册
- Guest执行OUT类指令(包括MMIO Write操作)
- VMEXIT到KVM
- 调用虚拟设备的write方法
- write方法中检查本次OUT类指令访问的IO地址和范围是否符合ioeventfd设置的要求
- 如果符合则调用eventfd_signal触发一次POLLIN事件并返回Guest
- QEMU监测到ioeventfd上出现了POLLIN,则调用相应的处理函数处理IO
__EOF__

本文链接:https://www.cnblogs.com/haiyonghao/p/14440743.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律