Event-channel in Xen

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

内容简介:这两天在看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。

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


以上所述就是小编给大家介绍的《Event-channel in Xen》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Designing for Emotion

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》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具