Event-channel in Xen

栏目: 服务器 · 发布时间: 6年前

内容简介:Event-channel in Xen

这两天在看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。

Event-channel in Xen

而在Xen的实现中,每个vcpu都有一个类型为 struct evtchn_fifo_vcpu 的变量 evtchn_fifo ,如下图所示:

Event-channel in Xen

里面的 event_fifo_queue 就是前面所说的对应于每个priority的queue,而queue中的head和tail域就会指向相应的event word。这些event word实际存在于event array中,在Xen的实现中,每个domain都有一个类型为 struct evtchn_fifo_domain 的变量 evtchn_fifo ,如下图所示:

Event-channel in Xen

其中 *event_array[] 存储了event array的实际数据。同时我们还可以看到,在 struct domain 数据结构中,还有一个类型为 struct evtchn * 的变量 evtchn 和类型为 struct evtchn ** 的变量 evtchn_group 。这些数组存储了event-channel的对象,他们通过一个两级的数据结构组成,如下图所示:

Event-channel in Xen

其中,每个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 函数:

linux-src/drivers/block/xen-blkfront.c
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:

linux-src/drivers/block/xen-blkfront.c
static inline void flush_requests(struct blkfront_ring_info *rinfo)
{
  ...
  notify_remote_via_irq(rinfo->irq);
}
linux-src/drivers/xen/events/events_base.c
void notify_remote_via_irq(int irq)
{
  int evtchn = evtchn_from_irq(irq);
  ...
  notify_remote_via_evtchn(evtchn);
}
linux-src/include/xen/events.h
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 函数:

xen/common/event_channel.c
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 函数:

xen/common/event_fifo.c
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 函数:

xen/arch/x86/hvm/vmx/entry.S
...
.Lvmx_do_vmentry:
  call vmx_intr_assist
  ...
  VMLAUNCH
xen/arch/x86/hvm/vmx/intr.c
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 的值返回一个 sourcehvm_intsrc_vectorstruct hvm_intack 数据结构:

xen/arch/x86/hvm/irq.c
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。

那么,这个 sourcehvm_intsrc_vectorstruct hvm_intack 中的 vector 域是什么呢?

我们发现,在每个HVM虚拟机(包括driver domain)启动的时候,会调用一个 xen_set_callback_via 函数:

linux-src/drivers/xen/events/events_base.c
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 函数:

xen/arch/x86/hvm/irq.c
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;
      ...

  }
}

所以在这里它将 sourcehvm_intsrc_vectorstruct 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 函数

linux-src/arch/x86/entry/entry_64.S
apicinterrupt3 HYPERVISOR_CALLBACK_VECTOR \
  xen_hvm_callback_vector xen_evtchn_do_upcall
linux-src/drivers/xen/events/events_base.c
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

linux-src/drivers/xen/events/events_fifo.c
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

linux-src/arch/x86/xen/setup.c
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 变量:

xen/arch/x86/x86_64/traps.c
static long register_guest_callback(struct callback_register *reg)
{
  ...
  switch ( reg->type )
  {
  case CALLBACKTYPE_event:
    v->arch.pv_vcpu.event_callback_eip    = reg->address;
    ...
  }
  ...
}
xen/include/asm-x86/asm-offsets.h
#define VCPU_event_addr 1312 /* offsetof(struct vcpu, arch.pv_vcpu.event_callback_eip) */

之后Xen在会通过这个callback来直接调用PV虚拟机中相应的 xen_hypervisor_callback 函数,该函数最终也会调用到实际的处理函数 xen_evtchn_do_upcall


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

触点管理

触点管理

[德] 安妮·M·许勒尔(Anne M. Schuller) / 于嵩楠 / 中国人民大学出版社 / 2015-12-1 / 49.00元

我们所处的时代正经历着巨大的变革,变得越来越数字化、复杂化和社会化。互联网浪潮猛烈冲击着传统商业世界,数字原住民队伍不断壮大,改变了企业的内外生态环境;金字塔式结构正在瓦解,组织变得越来越网络化和扁平化;员工接管了企业的话语权,我们比任何时期都更需要员工的忠诚,并期望他们表现出更加自主的创造力和协作精神。 在数字化商业世界里,公司内部员工与组织和领导之间接触点的数量直线上升,任何真相都无法对......一起来看看 《触点管理》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具