You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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的中断状态更新与通知:

  1. kvm_vgic_inject_irq首先会找到对应中断的vgic_irq结构体,设置中断的pending/active状态
  2. 随后会检查该中断是否需要向vCPU投递,如果目标vCPU正在运行,会通过硬件辅助虚拟化接口(如ARM的HCR_EL2.VI位)触发vCPU的中断退出,或者直接将中断注入到vCPU的中断待处理队列
  3. 等上述中断注入逻辑完成,才会调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 23:18:03