I/O Ring能够让我们通过共享内存传输数据,但是在准备好数据后又该如何告知远端的Dom呢?因此类似中断,这里就要用到Event_Channel来进行异步通知。Event Channel是Xen提供的一套 等价于硬件中断的通信机制,其融合了Physical Interrupt, Virtual Interrupt和Inter/Intradomain Communication.
本篇博文分为两部分,这篇介绍Event Channel的实现机制,另一篇介绍Event Channel如何在Guest中使用。
- Grant Table
- I/O Ring Structure
- Event Channel Implementation(本篇)
- Event Channel Usage
- XenStore Usage
- Write a PV Driver
- Connect to XenBus
Event Channel Model
Event Channel是Xen提供的通信机制,Xen允许Guest将以下四种中断映射成为Event Channel。
- 利用Pass-through的方式将硬件直接交给某个Guest,或使用支持SR-IOV的硬件时是可以直接使用这个中断的。
- 但是Guest有的时候需要一些中断(e.g. 时钟中断)来完成某个功能,因此Xen提供了虚拟中断(VIRQ),Hypervisor设置某个bit使得Guest以为有了中断。
- Interdomain communication, Domain之间的通信需要依赖某个机制。
- Intradomain communication, Domainn内部通信,属于Interdomain Communication的一种特殊情况, (DomID相同,cpuID不同)
在Event Channel部分, remote port往往与evtchn互换使用,而local port则与irq互换使用。
一个Guest会通过Event Channel绑定到一个时间源上,并设置对应的handler.
Event Source可以是另一个dom的port(Case 3, 4), 真实的物理中断(Case 1)或者一个虚拟中断(Case 2)。当Event Channel建好后,事件源就可以通过这个Channel发通知给接收端。
1 | domM irq_handler<--- bound --->local_irq <--- bound ---> |
PV Guest (No CPU Hardware Virtualization)
PV Guest由于可以对Kernel进行修改,因此Event是通过callback来完成的。Guest在初始化的时候会利用Hypercall注册好callback的handler, 每次Xen调用callback时直接跳转到handler中
PV Register Callback
1 | /* $DIR/arch/x86/kernel/setup.c: 851: */ |
可以发现,Linux最终通过调用HYPERVISOR_callback_op(callback_register, &callback)
将xen_hypervisor_callback()
与xen_failsafe_callback()
注册成为Upcall的Handler和失败处理。
每当有一个Event时,Xen会set shared_info中对应vcpu的event map的对应bit, 再通过这个upcall来通知Guest, 控制流会直接跳转到xen_hypervisor_callback()
中,
这个函数定义在arch/x86/entry/entry_64.S or entry_32.S
中, 处理逻辑与中断类似,保存当前状态并跳到实际的处理函数xen_evtchn_do_upcall
。在实际的处理函数中会去查event map中的bit并调用相应的函数进行处理。
1 | Hypervisor --- Callback ---> Guest Callback Handler |
HVM Guest (CPU Hardware Virtualization)
在引入HVM后,根据不同程度虚拟化可以将Guest分为下述几类
关于上面这幅图的详细介绍以及如何确定自己的Guest运行在哪个模式下,在下一篇博文中我会详述。
这里为了提高性能,我之后主要使用的是PVHVM, 充分利用CPU和内存/MMU的硬件虚拟化,同时利用PV Driver (Split drivers in Xen)和Interrupt与Timer。与PV不同的是HVM使用了CPU的硬件虚拟化,拥有root和non-root模式 Hypervisor不可能单单使用Callback跳转到ring3, 必须使用VMENTER/VMRUN进入non-root。 HVM中CPU虚拟化提供了VMCS(Intel)/VMCB(AMD), 在VMENTER/VMRUN之前设置里面的对应bit,就可以在回到Non-root模式的时候引起Guest的中断处理。 因此,Hypervisor所需要做的就是设置引起中断的bit即可。
Guest Register Callback
1 | /* $DIR/arch/x86/xen/enlighten.c */ |
可以发现,Guest 通过调用HYPERVISOR_hvm_op
Hypercall 告诉Xen有一个HVM_PARAM_CALLBACK_IRQ
, 在Xen Hypervisor中,会根据via传来的参数创建一个新的irq。via是callback的vector号。
而后Guest会为这个vector分配一个中断。
1 | 914: apicinterrupt3 HYPERVISOR_CALLBACK_VECTOR \ |
最终xen_hvm_callback_vector
会由xen_evtchn_do_upcall
来处理,
Hypervisor Set VMCB According to Pending Bit
而在Hypervisor中,每次 VMRUN/VMENTER会查询之前注册的号,如果发现有被set的就会在VMCB/VMCS里面set中断位,在VMRUN/VMENTER后Guest就会去处理相应的逻辑。
1 | /* $XENDIR/xen/arch/x86/hvm/svm/intr.c */ |
因此每次VMExit处理完后,Hypervisor都会检查pending的中断并设置VMCB中相应的bit,在VMRUN的时候Guest会进入中断处理来处理Event。
1 | Hypervisor --- Check Pending bit ---> Set VMCB/VMCS |
Real Handler: xen_evtchn_do_upcall
无论是PV还是HVM, 最终Event都会交由xen_evtchn_do_upcall
来处理:
1 | 1253: void xen_evtchn_do_upcall(struct pt_regs *regs) |
之后xen_evtchn_handle_events
会根据event的实现采用2l/fifo的event_channel来处理,(以fifo为例),
1 | 327: static void __evtchn_fifo_handle_events(unsigned cpu, bool drop) |
之后就和普通处理irq的逻辑一样了, 查找对应的描述符并调用相应的处理函数,Event Channel至此就再次被转换为上层的IPI、VIRQ或IRQ.
在2-level ABI(2l)的中是通过查找shared_info中的bitmap来处理event的,但是最终也会调用generic_handle_irq
回到统一的处理逻辑上。
Summary
Xen提供的Event Channel是Hypervisor中的一种机制,对于上层Guest来说其目的是将IRQ、VIRQ、IPI借助Event Channel这一统一形式进行传递。从而实现异步通信。
在发升IRQ的时候Guest都会进行下陷,由Hypervisor去set相应的bit, 不过一种新技术Post-Interrupt可以在不下陷的情况下完成这一要求, 有机会再详细介绍。