内容简介:这两天在看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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
需求
[美] 亚德里安•斯莱沃斯基(Adrian J. Slywotzky)、[美]卡尔•韦伯 (Karl Weber) / 魏薇、龙志勇 / 浙江人民出版社 / 2013-6 / 64.9
《财富汇•需求:缔造伟大商业传奇的根本力量》内容简介:需求,是缔造伟大商业传奇的根本力量。《财富汇•需求:缔造伟大商业传奇的根本力量》呈现了人们无法拒绝、竞争对手无法复制的需求创造的六大关键,在人们无奈接受的现状和心中真正期待的理想的这道鸿沟之上,架设起了一道桥梁。 创造需求,需要解开一个谜团,这个谜团是人类学、心理学、科技、设计、经济学、基础设施以及其他众多因素综合而成的奇特组合。《财富汇......一起来看看 《需求》 这本书的介绍吧!