前一篇我们已经了解了Event Channel是如何被注册和被Hypervisor实现的,本篇将利用kernel module,使用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 OPs
$XENDIR/xen/include/public/event_channel.hlink1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define EVTCHNOP_bind_interdomain 0 #define EVTCHNOP_bind_virq 1 #define EVTCHNOP_bind_pirq 2 #define EVTCHNOP_close 3 #define EVTCHNOP_send 4 #define EVTCHNOP_status 5 #define EVTCHNOP_alloc_unbound 6 #define EVTCHNOP_bind_ipi 7 #define EVTCHNOP_bind_vcpu 8 #define EVTCHNOP_unmask 9 #define EVTCHNOP_reset 10 #define EVTCHNOP_init_control 11 #define EVTCHNOP_expand_array 12 #define EVTCHNOP_set_priority 13
|
- 0-2 分别是绑定到不同类型的Evtchn上
- 3用来关闭Evtchn
- 4用来发送一个Event
- 5用来查询当前Evtchn的状态
- 6用于申请一个空闲的未被绑定的evtchn用于Interdomain的通信
- 7用于不同cpu间的通信
- 8用于evt到来时通知哪一个vcpu, ipi通知相关vcpu, per-vcpu virq通知相关vcpu, 但是其他的默认都是通知vcpu0的。
- 9用于启动某个evtchn (unmask用于启用)
- 10用于关闭和一个domid相关的所有evtchn
- 11-13 FIFO实现的Evtchn的相关操作
如果想要完成不同dom间通信,需要dom1申请一个未绑定的evtchn, 接受者绑定这个evtchn,而后将本地的中断绑定到这个evtchn,之后就和普通的中断处理一样了。
我的demo中会由domU申请,dom0绑定并通知domU, domU收到通知后在handler中再通知dom0, 而后dom0退出。
DomU Module
Alloc Event channel
alice_domU.clink1 2 3 4 5 6 7 8
| struct evtchn_alloc_unbound alloc_unbound; int err;
alloc_unbound.dom = DOMID_SELF; alloc_unbound.remote_dom = DOM0_ID;
err = HYPERVISOR_event_channel_op(EVTCHNOP_alloc_unbound, &alloc_unbound);
|
通过Hypercall来申请一个evtchn, Hypervisor会将值填入evtchn_alloc_unbound中
Alloc IRQ and Bind to Evtchn
alice_domU.clink1
| irq = bind_evtchn_to_irq(global_info.evtchn);
|
bind_evtchn_to_irq是linux封装的函数,其实现如↓
$DIR/drivers/xen/events/events_base.clink1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int bind_evtchn_to_irq(unsigned int evtchn) { ...
irq = get_evtchn_to_irq(evtchn); if (irq == -1) { irq = xen_allocate_irq_dynamic(); irq_set_chip_and_handler_name(irq, &xen_dynamic_chip, handle_edge_irq, "event");
ret = xen_irq_info_evtchn_setup(irq, evtchn);
bind_evtchn_to_cpu(evtchn, 0); }
return irq; }
|
在首次绑定的时候,linux会动态申请一个中断号,并配置好相应的描述符,并默认绑到vcpu0上
Bound IRQ Handler to IRQ
alice_domU.clink1 2 3
| static irqreturn_t domU_handler(int irq, void *dev_id) { ... }
err = request_irq(global_info.irq, domU_handler, 0, "alice_dev", &global_info);
|
这里定义好Handler函数后调用request_irq将Handler进行注册; request_irq实现如↓
$DIR/kernel/irq/manage.clink1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 1634: int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) { ... desc = irq_to_desc(irq); action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id;
retval = irq_chip_pm_get(&desc->irq_data); retval = __setup_irq(irq, desc, action);
return retval; }
|
可以发现, 函数将handler填入中断的描述符中后调用__setup_irq完成对中断的配置,这样Handler就与IRQ绑定了起来。
Notify via IRQ
alice_domU.clink1 2 3 4 5 6 7
| static irqreturn_t domU_handler(int irq, void *dev_id) { ... pr_info("DomU: Handled! irq: %d, evtchn:%d\n", irq, info->evtchn); notify_remote_via_irq(irq); return IRQ_HANDLED; }
|
这样domU就可以通过irq来通知远端,notify_remote_via_irq则是直接调用HYPERVISOR_event_channel_op(EVTCHNOP_send, &send) 让Hypervisor帮忙去set相应的pending bit.
Dom0 Module
dom0的Module原理上和domU类似,不同的是dom0不需要自己申请空闲的evtchn, 只需要直接绑定到domU的evtchn即可,并且linux中已经把上述过程都封装好了..
alice_dom0.clink1 2
| err = bind_interdomain_evtchn_to_irqhandler(global_info.remoteDomID, global_info.evtchn, dom0_handler, 0, "alice_dev", &global_info);
|
这个函数的执行过程和上面的过程类似,就不展开了。
Running Demo
1 2 3 4 5 6 7 8 9 10
| /* In domU, sudo insmod alice_domU.ko; dmesg */ [ 81.340632] DomU: Get new evtchn: 32 [ 81.340670] DomU: Bound local irq: 69 to evtchn:32 [ 114.224679] DomU: Handled! irq: 69, evtchn:32
/* IN dom0, sudo insmod alice_dom0.ko domid=id port=port; dmesg */ [79383.358635] Dom0: init info with remoteDomID:6, port:32 [79383.363885] Dom0: Handled, domid: 6, port: 32, local_irq:95 [79383.363902] Dom0: bound local irq:95 to evtchn:32 [79383.374352] Dom0: Handled, domid: 6, port: 32, local_irq:95
|
刚启动dom0的module后,dom0会立即处理一次中断,可能是之前的bit没有清掉导致的… 第二次的Handle则是domU通知的。