调用irqfd触发中断后向KVM虚拟机注入中断的实现流程是什么?
KVM irqfd触发虚拟机中断完整流程解析
1. irqfd初始化入口
KVM irqfd ioctl用于为文件描述符启动irqfd功能,其内核入口处理逻辑如下:
case KVM_IRQFD: { struct kvm_irqfd data; r = -EFAULT; if (copy_from_user(&data, argp, sizeof(data))) goto out; r = kvm_irqfd(kvm, &data); break; }
其中kvm_irqfd会调用kvm_irqfd_assign初始化等待队列,绑定唤醒回调:
init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
2. 中断触发回调处理
当irqfd对应的eventfd被写入触发事件时,会调用绑定的irqfd_wakeup回调,逻辑如下:
if (flags & EPOLLIN) { u64 cnt; eventfd_ctx_do_read(irqfd->eventfd, &cnt); idx = srcu_read_lock(&kvm->irq_srcu); do { seq = read_seqcount_begin(&irqfd->irq_entry_sc); irq = irqfd->irq_entry; } while (read_seqcount_retry(&irqfd->irq_entry_sc, seq)); /* An event has been signaled, inject an interrupt */ if (kvm_arch_set_irq_inatomic(&irq, kvm, KVM_USERSPACE_IRQ_SOURCE_ID, 1, false) == -EWOULDBLOCK) schedule_work(&irqfd->inject); srcu_read_unlock(&kvm->irq_srcu, idx); ret = 1; }
如果原子注入失败返回-EWOULDBLOCK,就会调度异步的inject工作队列处理中断注入。
3. 异步中断注入逻辑
irqfd_inject函数实现如下:
static void irqfd_inject(struct work_struct *work) { struct kvm_kernel_irqfd *irqfd = container_of(work, struct kvm_kernel_irqfd, inject); struct kvm *kvm = irqfd->kvm; if (!irqfd->resampler) { kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID, irqfd->gsi, 1, false); kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID, irqfd->gsi, 0, false); } else kvm_set_irq(kvm, KVM_IRQFD_RESAMPLE_IRQ_SOURCE_ID, irqfd->gsi, 1, false); }
该函数会调用kvm_set_irq完成通用中断路由匹配与注入:
int kvm_set_irq(struct kvm *kvm, int irq_source_id, u32 irq, int level, bool line_status) { struct kvm_kernel_irq_routing_entry irq_set[KVM_NR_IRQCHIPS]; int ret = -1, i, idx; trace_kvm_set_irq(irq, level, irq_source_id); /* Not possible to detect if the guest uses the PIC or the * IOAPIC. So set the bit in both. The guest will ignore * writes to the unused one. */ idx = srcu_read_lock(&kvm->irq_srcu); i = kvm_irq_map_gsi(kvm, irq_set, irq); srcu_read_unlock(&kvm->irq_srcu, idx); while (i--) { int r; r = irq_set[i].set(&irq_set[i], kvm, irq_source_id, level, line_status); if (r < 0) continue; ret = r + ((ret < 0) ? 0 : ret); } return ret; }
4. 架构相关中断注入(ARM64 VGIC场景)
在ARM64平台上,匹配到路由后会调用对应的set回调vgic_irqfd_set_irq:
static int vgic_irqfd_set_irq(struct kvm_kernel_irq_routing_entry *e, struct kvm *kvm, int irq_source_id, int level, bool line_status) { unsigned int spi_id = e->irqchip.pin + VGIC_NR_PRIVATE_IRQS; if (!vgic_valid_spi(kvm, spi_id)) return -EINVAL; return kvm_vgic_inject_irq(kvm, 0, spi_id, level, NULL); }
核心疑问解答
你跟踪到__vgic_put_lpi_locked只看到链表删除,是因为跳过了中断注入的核心逻辑:kvm_vgic_inject_irq在调用vgic_put_irq之前,已经完成了虚拟GIC的中断状态更新与通知:
kvm_vgic_inject_irq首先会找到对应中断的vgic_irq结构体,设置中断的pending/active状态- 随后会检查该中断是否需要向vCPU投递,如果目标vCPU正在运行,会通过硬件辅助虚拟化接口(如ARM的HCR_EL2.VI位)触发vCPU的中断退出,或者直接将中断注入到vCPU的中断待处理队列
- 等上述中断注入逻辑完成,才会调用
vgic_put_irq释放vgic_irq的引用计数,如果引用计数归零才会走后续的链表删除、内存释放逻辑,这步只是资源回收,不是中断注入的核心路径。
完整流程总结
- 用户态写入irqfd对应的eventfd触发事件
- 内核唤醒
irqfd_wakeup回调,尝试原子注入中断,失败则调度异步注入 - 调用
kvm_set_irq匹配中断路由 - 调用架构相关的中断注入回调(ARM64下为
vgic_irqfd_set_irq) - 更新虚拟GIC的中断状态,向目标vCPU投递中断
- 释放中断结构体引用,完成注入
之后vCPU在运行时会感知到虚拟GIC的pending中断,自动进入虚拟机的中断处理流程。
内容的提问来源于stack exchange,提问作者Rafaelo
相关产品推荐
相关产品推荐

