QEMU和KVM 中断处理过程

时间:2024-04-12 10:13:44

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

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

QEMU和KVM 中断处理过程

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)函数说明

[html] view plain copy
  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()说明

[html] view plain copy
  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()进行中断注入的过程了

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

QEMU和KVM 中断处理过程

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()