QEMU和KVM 中断处理过程

本文讲述一个网络数据包从到达物理网卡,一直到中断注入给VM的整个过程。

为了讲述清晰,假设宿主物理机有两个物理CPU,分别为CPU0和CPU1。假设GuestOS运行在CPU1上,物理网卡接到数据包后把中断请求发送到CPU0.

1.网络数据包Package到达物理网卡NIC, NIC收到数据包后,向CPU0发送中断请求,通知CPU0有网络数据包到达。

2.CPU0收到中断请求后,调用中断处理函数,处理这个中断请求。但是,这个数据包可能是发给VM的,所以这里Host不会直接调用Host的中断处理函数,而是会调用QEMU中实现的Bridge(Brigde就是一个网桥)。Bridge会把请求再转发给QEMU模拟的TAP设备。

3.TAP设备会进行判断这个请求是发给谁的,如果是发给Host的,则直接调用Host的中断处理函数;如果是发给VM的,那么就会TAP就会调用一系列函数,比如select,知道调用到kvm_vm_ioctl(, KVM_IRQ_LINE_STATUS,)。到此为止,QEMU模拟结束,开始进入KVM执行。

4.kvm_set_irq()  (irqchip.c)函数说明

 

 
  1. int kvm_set_irq(struct kvm *kvm, int irq_source_id, u32 irq, int level,bool line_status){

  2. ...

  3.  
  4. if (irq < irq_rt->nr_rt_entries)

  5. /*提取中断路由表中对应的中断路由实体,map[irq]是一个对应中断的路由实体表头结点,这里遍历它能够得到所有对应的路由实体。*/

  6. hlist_for_each_entry(e, &irq_rt->map[irq], link)

  7.  
  8. while(i--) {

  9. int r;

  10. /*触发对应路由实体的触发函数,这个函数在之前的安装中断路由的时候已经注册,注册函数是 <span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px; text-align: -webkit-left; ">setup_routing_entry() </span>*/

  11. r = irq_set[i].set(&irq_set[i], kvm, irq_source_id, level);

  12. if (r < 0)

  13. continue;

  14. ret = r + ((ret < 0) ? 0 : ret);

  15. }

  16. return ret;

  17. }

由 setup_routing_entry() (在irqchip.c中)代码,可知上面ira_set[i].set(..) 调用的就是kvm_set_ioapic_irq().

具体注册路径为:setup_routing_entry() -->kvm_set_routing_entry()-->e->set = kvm_set_ioapic_irq()

 

5. ioapic_service()说明

 

 
  1. static int ioapic_service(struct kvm_ioapic *ioapic, unsigned int idx,

  2. bool line_status)

  3. {

  4. union kvm_ioapic_redirect_entry *pent;

  5. int injected = -1;

  6. /*读取“中断重定向表”*/

  7. pent = &ioapic->redirtbl[idx];

  8. /*检查中断是否被屏蔽,如果被屏蔽,则不触发中断*/

  9. if (!pent->fields.mask) {

  10. /*发送到LAPIC*/

  11. injected = ioapic_deliver(ioapic, idx, line_status);

  12. if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)

  13. pent->fields.remote_irr = 1;

  14. }

  15.  
  16. return injected;

  17. }


6.ioapic_deliver() 

这个函数主要功能:读取中断重定向表,设置中断请求的dest_id, vector,dest_mode等请求信息,然后再调用kvm_irq_delivery_to_apic()把中断请求发送给LAPIC

7.kvm_irq_delivery_to_apic()函数

这是一个非常关键的函数! 是一个枢纽函数,无论IOAPIC发给LAPIC中断请求(外部I/O中断),还是LAPIC发个LAPIC中断请求(IPI中断),最后都是调用的这个函数!

这个函数的功能有: 根据传入的参数irq,获取目标LAPIC编号dest_id,再根据dest_id找到其对应的vcpu,最后调用kvm_apic_set_irq() 设置目标LAPIC的中断请求寄存器。

8.__apic_accept_irq()

函数功能:向lapic添加一个Pending  IRQ

首先根据delivery_mode选择添加的方式,基本都会调用下面两个函数:

kvm_make_request(..,vcpu): 这个函数就是向目标vcpu添加一个reqest请求。当目标vcpu再次enter_guest时,check到这个中断请求,进行中断注入,vcpu重新运行,捕获这个中断。

kvm_vcpu_kick(vcpu): 这个函数的功能就是,判断vcpu是否正在物理cpu上运行,如果是,则让vcpu退出,以便进行中断注入。

----------------------------------------

到此为止,kvm模拟的IOAPIC, LAPIC就结束了。下面就是vcpu_enter_guest()进行中断注入的过程了

----------------------------------------

9.vcpu_enter_guest()

当vcpu再次被调度进行vm_entry时,就会执行这个vcpu_enter_guest()函数。

在vcpu run之前,会调用kvm_check_reqest(KVM_REQ_EVENT,vcpu)检查是否有中断请求需要注入。

在上面8中,调用过kvm_make_request(),所以这里if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win)条件判断成立,执行下面的inject_pending_event(vcpu)

10.inject_pending_event()

这个函数的功能就是,把中断请求写入到目标vcpu的中断请求寄存器。最后就是写VMCS中断的irq寄存器。

调用为kvm_x86_ops->set_irq(vcpu),这正调用的就是vmx_inject_irq()

在static struct kvm_x86_ops vmx_x86_ops ={ ..     .set_irq = vmx_inject_irq  }.

vmx_inject_irq() 调用vmx_write32() 写VMCS  的IRQ寄存器。

11. vmx_vcpu_run()

vcpu_enter_guest()最后调用 vmx_vcpu_run(), vcpu开始执行。

在执行前,会读取VMCS中的寄存器信息,由于前面写了IRQ,所以vcpu运行就会知道有中断请求到来,再调用GuestOS的中断处理函数处理中断请求。

----------------------

到此,从一个网络包到达网卡,到qemu 和kvm模拟中断,到中断注入给vcpu,再到vcpu运行发现中断,调用中断处理函数,完整过程大概如此。

----------------------

 

 

处理器间IPI中断

IPI中断处理过程要简单的多。

LAPIC中有两个重要的寄存器:

LAPIC_ICR: 存放中断向量

LAPIC_ICR2: 存放中断请求目标

如果一个vcpu0要个vcpu1发IPI中断,vcpu0只要调用apic_reg_write()写LAPIC的这两个寄存器就可以了。

再调apic_sent_ipi()-->kvm_irq_deliver_to_apic()

 

中断注入
中断注入实际是向客户机CPU注入一个事件,这个事件包括异常和外部中断和NMI。异常我们一般看作为同步,中断被认为异步。
硬件具体实现就中断注入实际就是设置VMCS中字段VM-Entry interruption-infomation字段。中断注入实际在VM运行前完成的,具体代码如下:
static int vcpu_enter_guest(struct kvm_vcpu *vcpu) {
     inject_pending_event(vcpu);
}
vcpu_enter_guest函数运行虚拟机,运行虚拟机代码已省掉。中断注入实际在VM运行前,接下来看看具体如何注入。
static void inject_pending_event(struct kvm_vcpu *vcpu)
{
   if (vcpu->arch.nmi_injected) {
                kvm_x86_ops->set_nmi(vcpu);
                return;
        }

   if (vcpu->arch.interrupt.pending) {
                kvm_x86_ops->set_irq(vcpu);
                return;
        }
     /* try to inject new event if pending */
        if (vcpu->arch.nmi_pending) {
                if (kvm_x86_ops->nmi_allowed(vcpu)) {
                        vcpu->arch.nmi_pending = false;
                        vcpu->arch.nmi_injected = true;
                        kvm_x86_ops->set_nmi(vcpu);
                }
        } else if (kvm_cpu_has_interrupt(vcpu)) {
                if (kvm_x86_ops->interrupt_allowed(vcpu)) {
                        kvm_queue_interrupt(vcpu, kvm_cpu_get_interrupt(vcpu),
                                            false);
                        kvm_x86_ops->set_irq(vcpu);
                }
        }

}
首先用户态实现中断控制器,不可屏蔽中断和其他中断注入过程。用户态中断采集在qemu代码中实现
判断是否有等待注入中断,存在话立即注入
接下来内核态模拟的中断控制器,中断注入过程,不可屏蔽中断和其他中断注入过程。
判断KVM内核态是否有不可屏蔽中断,有并且客户机cpu允许中断话,注入中断到客户机cpu中。
判断KVM内核态是否有中断,有中断并且客户机cpu允许中断话,获取优先级高中断进行排队,注入中断到客户机cpu中。
另外一个情况,如果有中断但是客户机不允许中断,只能等待下一下中断注入。如果下一次有更高级别中断发生,该中断还是不能注入而选择更高级别中断注入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值