内容简介:这两天在看Xen的事件通知event-channel机制。同时也很大程度上参考了小明的废话不多说,直接进入正题。首先,关于event-channel的概念,可以直接参照小明的博客:
这两天在看Xen的事件通知event-channel机制。同时也很大程度上参考了小明的 这篇博客 ,欢迎围观。
废话不多说,直接进入正题。
首先,关于event-channel的概念,可以直接参照小明的博客:
Event Channel是Xen提供的通信机制,Xen允许Guest将以下四种中断映射成为event-channel。
- 利用Pass-through的方式将硬件直接交给某个Guest,或使用支持SR-IOV的硬件时是可以直接使用这个中断的。
- 但是Guest有的时候需要一些中断(e.g. 时钟中断)来完成某个功能,因此Xen提供了虚拟中断(VIRQ),Hypervisor设置某个bit使得Guest以为有了中断。
- Interdomain communication, 虚拟机之间的通信需要依赖某个机制。
- Intradomain communication, 虚拟机内部通信,属于Interdomain Communication的一种特殊情况, (DomID相同,cpuID不同)
在event-channel部分, remote port往往与evtchn互换使用,而local port则与irq互换使用。
一个Guest会通过event-channel绑定到一个事件源(event source)上,并设置对应的handler. 其中,事件源可以是另一个dom的port(Case 3, 4), 真实的物理中断(Case 1)或者一个虚拟中断(Case 2)。当event-channel建好后,事件源就可以通过这个Channel发通知给接收端。
在这片文章中,我们主要来看看在Xen的实现中,基于虚拟机之间inter-domain的event-channel机制是如何实现的。
FIFO-based event-channel
首先我们先来看一下Xen中event channel的数据结构。在早期Xen的实现中,event-channel实现的是一种被称为2-level的event-channel,由于它的scalability不好,所以后来实现了一种被称为fifo-based event channel,这也是当前Xen默认使用的event-channel机制,有一个 文档 专门对其进行了描述。
如下图所示,简单来说,fifo-based event-channel将event-channel分成了16个不同的priority,每个priority对应一个queue,每个queue对应一组单链表形式组织的event array。每个event array都是由一系列32 bits长度的event words组成,而每个event word包含了3个bits(pending bit, mask bit和linked bit),以及一个17bits长度的link信息,指向下一个event word。
而在Xen的实现中,每个vcpu都有一个类型为 struct evtchn_fifo_vcpu
的变量 evtchn_fifo
,如下图所示:
里面的 event_fifo_queue
就是前面所说的对应于每个priority的queue,而queue中的head和tail域就会指向相应的event word。这些event word实际存在于event array中,在Xen的实现中,每个domain都有一个类型为 struct evtchn_fifo_domain
的变量 evtchn_fifo
,如下图所示:
其中 *event_array[]
存储了event array的实际数据。同时我们还可以看到,在 struct domain
数据结构中,还有一个类型为 struct evtchn *
的变量 evtchn
和类型为 struct evtchn **
的变量 evtchn_group
。这些数组存储了event-channel的对象,他们通过一个两级的数据结构组成,如下图所示:
其中,每个group包含了一个page大小的bucket指针,而每个bucket包含了一个page大小的 struct evtchn
的数组。其中,第一个bucket(即bucket 0)可以直接通过 d->evtchn
变量访问。
因此,给定一个port,我们可以通过 evtchn_from_port
函数计算得出其相应的 struct evtchn
对象:
#define group_from_port(d, p) \ ((d)->evtchn_group[(p) / EVTCHNS_PER_GROUP]) #define bucket_from_port(d, p) \ ((group_from_port(d, p))[((p) % EVTCHNS_PER_GROUP) / EVTCHNS_PER_BUCKET]) static inline struct evtchn *evtchn_from_port(struct domain *d, unsigned int p) { if ( p < EVTCHNS_PER_BUCKET ) return &d->evtchn[p]; return bucket_from_port(d, p) + (p % EVTCHNS_PER_BUCKET); }
另外,给定一个port,我们也可以通过 evtchn_fifo_word_from_port
函数计算得出其相应的 event word
:
static inline event_word_t *evtchn_fifo_word_from_port(struct domain *d, unsigned int port) { unsigned int p, w; if ( unlikely(port >= d->evtchn_fifo->num_evtchns) ) return NULL; p = port / EVTCHN_FIFO_EVENT_WORDS_PER_PAGE; w = port % EVTCHN_FIFO_EVENT_WORDS_PER_PAGE; return d->evtchn_fifo->event_array[p] + w; }
HVM虚拟机中event-channel事件通知流程
在了解了event channel的数据结构之后,我们就可以来介绍整个事件通知的流程是怎么样的。这里我们利用PVHVM中的front-end driver举例进行说明。在PVHVM架构中,CPU和内存是通过VT-x的硬件虚拟化实现的,而I/O依然是通过split I/O实现的。在split I/O模型中,客户虚拟机中的front-end driver在往共享内存中填好I/O数据之后,会调用 flush_request
函数:
static int blkif_queue_rq() { ... blkif_queue_request(qd->rq, rinfo); ... flush_requests(rinfo); ... }
在 flush_request
函数中,会通过event-channel机制最终调用 HYPERVISOR_event_channel_op(EVTCHNOP_send)
通知hypervisor:
static inline void flush_requests(struct blkfront_ring_info *rinfo) { ... notify_remote_via_irq(rinfo->irq); }
void notify_remote_via_irq(int irq) { int evtchn = evtchn_from_irq(irq); ... notify_remote_via_evtchn(evtchn); }
static inline void notify_remote_via_evtchn(int port) { struct evtchn_send send = { .port = port }; (void)HYPERVISOR_event_channel_op(EVTCHNOP_send, &send); }
而在hypervisor中, EVTCHNOP_send
的处理函数会调用 evtchn_send
,在该函数中,会根据local port得到其相应的remote domain和remote port,然后调用 evtchn_set_pending
函数:
int evtchn_send(struct domain *d, unsigned int lport) { lchn = evtchn_from_port(d, lport); switch ( lchn->state ) { case ECS_INTERDOMAIN: rd = lchn->u.interdomain.remote_dom; rport = lchn->u.interdomain.remote_port; rchn = evtchn_from_port(rd, rport); evtchn_set_pending(rvcpu, rport); break; ... } }
static void evtchn_set_pending(struct vcpu *v, int port) { v->domain->evtchn_port_ops->set_pending(v, evtchn_from_port(v->domain, port)); }
由于Xen采用的是fifo based event channel,所以最终会调用到 evtchn_fifo_set_pending
函数:
static void evtchn_fifo_set_pending(struct vcpu *v, struct evtchn *evtchn) { port = evtchn->port; word = evtchn_fifo_word_from_port(d, port); test_and_set_bit(EVTCHN_FIFO_PENDING, word); ... q = &v->evtchn_fifo->queue[evtchn->priority]; if ( q->tail ) { tail_word = evtchn_fifo_word_from_port(d, q->tail); linked = evtchn_fifo_set_link(d, tail_word, port); } if ( !linked ) write_atomic(q->head, port); q->tail = port; ... vcpu_mark_events_pending(v); ... }
在该函数中,会将driver domain中port相对应的event word中的pending bit设上,同时将其加入priority相对应的event queue中的tail中,并且将tail指向它。最后它会调用 vcpu_mark_events_pending
函数:
void vcpu_mark_events_pending(struct vcpu *v) { test_and_set_bit(0, (unsigned long *)&vcpu_info(v, evtchn_upcall_pending)); }
该函数会将 vcpu_info
中的 evtchn_upcall_pending
置上。
然后hypervisor就返回客户虚拟机了。可以看出,这个事件通知的机制是异步的,在其返回客户虚拟机的时候,该事件其实还并没有被通知到back-end driver。那么,back-end driver又是什么时候被通知到要处理相应的时间的呢?
在Xen需要返回back-end driver所在的driver domain(我们假设driver domain为HVM)的时候,即在调用vmentry之前,会首先调用一个 vmx_intr_assist
函数:
... .Lvmx_do_vmentry: call vmx_intr_assist ... VMLAUNCH
void vmx_intr_assist(void) { do { intack = hvm_vcpu_has_pending_irq(v); ... } ... vmx_inject_extint(intack.vector, intack.source); }
其中 hvm_vcpu_has_pending_irq
会根据 evtchn_upcall_pending
的值返回一个 source
为 hvm_intsrc_vector
的 struct hvm_intack
数据结构:
struct hvm_intack hvm_vcpu_has_pending_irq(struct vcpu *v) { struct hvm_domain *plat = &v->domain->arch.hvm_domain; if ( (plat->irq.callback_via_type == HVMIRQ_callback_vector) && vcpu_info(v, evtchn_upcall_pending) ){ return hvm_intack_vector(plat->irq.callback_via.vector); } ... }
而 vmx_inject_extint
则会将该 struct hvm_intack
中的 vector
值写入VMCS中的 VM_ENTRY_INTR_INFO
域中。因此在vmentry回driver domain的时候,则会根据这个 vector
的值触发相应的handler。
那么,这个 source
为 hvm_intsrc_vector
的 struct hvm_intack
中的 vector
域是什么呢?
我们发现,在每个HVM虚拟机(包括driver domain)启动的时候,会调用一个 xen_set_callback_via
函数:
int xen_set_callback_via(uint64_t via) { struct xen_hvm_param a; a.domid = DOMID_SELF; a.index = HVM_PARAM_CALLBACK_IRQ; a.value = via; return HYPERVISOR_hvm_op(HVMOP_set_param, &a); } void xen_callback_vector(void) { callback_via = HVM_CALLBACK_VECTOR(HYPERVISOR_CALLBACK_VECTOR); rc = xen_set_callback_via(callback_via); ... alloc_intr_gate(HYPERVISOR_CALLBACK_VECTOR, xen_hvm_callback_vector); }
它将 HYPERVISOR_CALLBACK_VECTOR
对应的handler设置成了 xen_hvm_callback_vector
而在Xen处理这个hypercall时,会调用 hvm_set_callback_via
函数:
void hvm_set_callback_via(struct domain *d, uint64_t via) { ... switch ( hvm_irq->callback_via_type = via_type ) { ... case HVMIRQ_callback_vector: hvm_irq->callback_via.vector = (uint8_t)via; break; ... } }
所以在这里它将 source
为 hvm_intsrc_vector
的 struct hvm_intack
中的 vector
设置成了 HYPERVISOR_CALLBACK_VECTOR
。
也就是说在vmentry回客户虚拟机之后,会调用 HYPERVISOR_CALLBACK_VECTOR
对应的handler xen_hvm_callback_vector
。
接下来我们来看看这个 xen_hvm_callback_vector
函数的实现。
在 entry.S
中,它被设置成了 xen_evtchn_do_upcall
函数
apicinterrupt3 HYPERVISOR_CALLBACK_VECTOR \ xen_hvm_callback_vector xen_evtchn_do_upcall
void xen_evtchn_do_upcall(struct pt_regs *regs) { struct vcpu_info *vcpu_info = __this_cpu_read(xen_vcpu); do { vcpu_info->evtchn_upcall_pending = 0; xen_evtchn_handle_events(cpu); /* call evtchn_ops->handle_events(cpu); -> */ } while (...) }
其中 evtchn_ops->handle_events(cpu)
最终会调用到 evtchn_fifo_handle_events
:
static void evtchn_fifo_handle_events(unsigned cpu) { ... consume_one_event(cpu, control_block, q, &ready, drop); ... }
最终在 consume_one_event
中会调用 handle_irq_for_port(port)
处理相应的event。
这就是HVM中event-channel时间通知的整个过程。
PV虚拟机中event-channel事件通知流程
当然,如果driver domain是domain-0,也就是说其不是HVM,而是PV的话,那么采用的又是另外一套流程。
在PV虚拟机初始化的时候会调用 register_callback
函数注册一个 CALLBACKTYPE_event
:
void __init xen_pvmmu_arch_setup(void) { ... register_callback(CALLBACKTYPE_event, xen_hypervisor_callback); ... } static int register_callback(unsigned type, const void *func) { struct callback_register callback = { .type = type, .address = XEN_CALLBACK(__KERNEL_CS, func), .flags = CALLBACKF_mask_events, }; return HYPERVISOR_callback_op(CALLBACKOP_register, &callback); }
而在Xen中,会将该callback的地址付给 VCPU_event_addr
变量:
static long register_guest_callback(struct callback_register *reg) { ... switch ( reg->type ) { case CALLBACKTYPE_event: v->arch.pv_vcpu.event_callback_eip = reg->address; ... } ... }
#define VCPU_event_addr 1312 /* offsetof(struct vcpu, arch.pv_vcpu.event_callback_eip) */
之后Xen在会通过这个callback来直接调用PV虚拟机中相应的 xen_hypervisor_callback
函数,该函数最终也会调用到实际的处理函数 xen_evtchn_do_upcall
。
以上所述就是小编给大家介绍的《Event-channel in Xen》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!