前一篇我们已经了解了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
1 | /* ` enum event_channel_op { // EVTCHNOP_* => struct evtchn_* */ |
- 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
1 | struct evtchn_alloc_unbound alloc_unbound; |
通过Hypercall来申请一个evtchn, Hypervisor会将值填入evtchn_alloc_unbound
中
Alloc IRQ and Bind to Evtchn
1 | irq = bind_evtchn_to_irq(global_info.evtchn); |
bind_evtchn_to_irq
是linux封装的函数,其实现如↓
1 | int bind_evtchn_to_irq(unsigned int evtchn) |
在首次绑定的时候,linux会动态申请一个中断号,并配置好相应的描述符,并默认绑到vcpu0上
Bound IRQ Handler to IRQ
1 | static irqreturn_t domU_handler(int irq, void *dev_id) { ... } |
这里定义好Handler函数后调用request_irq
将Handler进行注册; request_irq
实现如↓
1 | 1634: int request_threaded_irq(unsigned int irq, irq_handler_t handler, |
可以发现, 函数将handler填入中断的描述符中后调用__setup_irq
完成对中断的配置,这样Handler就与IRQ绑定了起来。
Notify via IRQ
1 | static irqreturn_t domU_handler(int irq, void *dev_id) |
这样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中已经把上述过程都封装好了..
1 | err = bind_interdomain_evtchn_to_irqhandler(global_info.remoteDomID, |
这个函数的执行过程和上面的过程类似,就不展开了。
Running Demo
1 | /* In domU, sudo insmod alice_domU.ko; dmesg */ |
刚启动dom0的module后,dom0会立即处理一次中断,可能是之前的bit没有清掉导致的… 第二次的Handle则是domU通知的。