4、事件通道的使用
在GOS内部,除了位于特权级1的GOS内核需要使用事件通道外,位于特权级3的应用程序也可能需要使用事件通道。为此,Xen采用了类似于特权级3使用超级调用的方式(privcmd内核驱动)来完成在用户空间的应用程序对事件通道的使用,即evtchn驱动。不同的是,privcmd内核驱动是proc文件系统下的驱动程序,而evtchn驱动则是dev文件系统下的驱动程序。
//linux-2.6-xen-sparse/drivers/xen/evtchn/evtchn.c 433-437
static struct miscdevice evtchn_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "evtchn",
.fops = &evtchn_fops,
};
4.1 设备驱动evtchn
应用程序需要通过预先定义的一系列文件操作来访问设备文件evtchn。这些文件操作的入口点由file_operation数据结构中的函数指针提供,其中包括设备的打开、读写、关闭、选择和命令控制等。
//linux-2.6-xen-sparse/drivers/xen/evtchn/evtchn.c 422-437
static const struct file_operations evtchn_fops = {
.owner = THIS_MODULE,
.read = evtchn_read,
.write = evtchn_write,
.ioctl = evtchn_ioctl,
.poll = evtchn_poll,
.fasync = evtchn_fasync,
.open = evtchn_open,
.release = evtchn_release,
};
设备evtchn的控制和管理由底层驱动提供的ioctl函数实现,用来改变evtchn设备的运行状态和运行参数。在ioctl函数中按照ioctl系统调用命令码的格式定义了应用程序可能会用到的6种操作,包括虚拟IRQ绑定、域间通道绑定、未绑定事件通道分配、关闭事件通道、发送事件通知和重置evtchn设备缓冲区。在这些操作中,与事件通道相关的操作通过超级调用的方式实现(4-1)。例如,虚拟IRQ绑定操作通过调用超级调用的虚拟中断绑定操作(EVTCHNOP_bind_virq)实现。
命令码(cmd) | 超级调用操作 | 说明 |
IOCTL_EVTCHN_BIND_VIRQ | EVTCHNOP_bind_virq | 虚拟中断(vIRQ)绑定 |
IOCTL_EVTCHN_BIND_INTERDOMAIN | EVTCHNOP_bind_interdomain | 域间通道绑定 |
IOCTL_EVTCHN_BIND_UNBOUND_PORT | EVTCHNOP_alloc_unbound | 未绑定事件通道绑定 |
IOCTL_EVTCHN_UNBIND | EVTCHNOP_close | 关闭事件通道 |
IOCTL_EVTCHN_NOTIFY | EVTCHNOP_send | 发送事件通知 |
IOCTL_EVTCHN_RESET | N/A |
|
4.2 事件处理程序
事件通道产生事件通知,并交由指定的VCPU进行处理。VCPU根据不同的事件通知选择相应的事件处理程序(Events Handlier)处理事件通知。由于事件通道机制的异步性,使得VCPU在任何时刻都有可能接收到未处理的事件通知,其中包括在VCPU执行用户空间程序期间。因次,在VCPU处理事件通知之前,需要能够保存当前的状态信息,在VCPU执行完成后还原这些状态信息。在VCPU调用处理程序处理事件通知时,Xen将屏蔽到该VCPU的所有事件通知;打开(取消屏蔽)操作则由处理程序来完成。事件通道的处理过程与Linux系统中的中断处理过程类似。
在X86平台上,从中断处理程序(Interrupt Handler)返回都是通过调用指令IRET完成。IRET指令能够自动恢复中断前状态,继续执行原先的代码并重新打开中断。但是,直接通过IRET指令返回并不能适用于事件处理程序。与中断相比,Xen系统中的事件仅仅是一个定义的软件结构,IRET指令无法知道如何去重新打开这些事件。
通过IRET指令对应的超级调用HYPERVISOR_iret可以实现从事件处理程序返回。尽管超级调用IRET使用简便,但是由于超级调用的使用需要进行上下文切换(Context Switch),增大了系统的开销。因此, Xen系统中提供了另一个非自动的方案,即手动的保存和恢复相关的状态信息。
//linux-2.6-xen-sparse/arch/i386/kernel/entry-xen.S
scrit: /**** START OF CRITICAL REGION ****/
__TEST_PENDING
jnz 14f # 可能需要处理其它事件通知.
RESTORE_REGS
addl $4, %esp
CFI_ADJUST_CFA_OFFSET -4
1: iret
.section __ex_table,"a"
.align 4
.long 1b,iret_exc
.previous
14: __DISABLE_INTERRUPTS
TRACE_IRQS_OFF
jmp 11f
ecrit: /**** END OF CRITICAL REGION ****/
ENTRY(hypervisor_callback)
RING0_INT_FRAME
pushl %eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
movl EIP(%esp),%eax
cmpl $scrit,%eax
jb 11f
cmpl $ecrit,%eax
jb critical_region_fixup
cmpl $sysexit_scrit,%eax
jb 11f
cmpl $sysexit_ecrit,%eax
ja 11f
addl $OLDESP,%esp
11: push %esp
CFI_ADJUST_CFA_OFFSET 4
call evtchn_do_upcall
add $4,%esp
CFI_ADJUST_CFA_OFFSET -4
jmp ret_from_intr
CFI_ENDPROC
事件通知在被送入各自的处理函数前,都要先交由一个简单的处理程序evtchn_do_upcall()进行处理,进而为不同的事件通知选择不同的处理路径。该函数将检查所有活动的事件通道(Active Event Channels)中是否存在未处理的事件通知,并将不同的事件通知送入不同的处理程序:与物理中断绑定的事件通知交由do_IRQ()处理,其它的则通过函数evtchn_device_upcall()交由设备evtchn进行处理。
//linux-2.6-xen-sparse/drivers/xen/core/evtchn.c 219-262
asmlinkage void evtchn_do_upcall(struct pt_regs *regs)
{
unsigned long l1, l2;
unsigned int l1i, l2i, port, count;
int irq, cpu = smp_processor_id();
shared_info_t *s = HYPERVISOR_shared_info;
vcpu_info_t *vcpu_info = &s->vcpu_info[cpu];
do {
/* 重新开启事件传递时,要避免回调“风暴”(可能会有大量的回调函数)*/
vcpu_info->evtchn_upcall_pending = 0;
if (unlikely(per_cpu(upcall_count, cpu)++))
return;
#ifndef CONFIG_X86
rmb(); /* 读内存屏障*/
#endif
/*清除evtchn_pending_sel 的值并返回原值*/
l1 = xchg(&vcpu_info->evtchn_pending_sel, 0);
while (l1 != 0) {
/*取evtchn_pending_sel 值的第一位,并将其清除*/
l1i = __ffs(l1);
l1 &= ~(1UL << l1i);
/*获取未屏蔽事件通知*/
while ((l2 = active_evtchns(cpu, s, l1i)) != 0) {
l2i = __ffs(l2);
port = (l1i * BITS_PER_LONG) + l2i;
if ((irq = evtchn_to_irq[port]) != -1)
do_IRQ(irq, regs);
else {
exit_idle();
evtchn_device_upcall(port);
}
}
}
/*如果有内嵌的回调函数*/
count = per_cpu(upcall_count, cpu);
per_cpu(upcall_count, cpu) = 0;
} while (unlikely(count != 1));
}
通过函数evtchn_device_upcall()发送到设备evtchn的事件通知都将存入设备的环形缓冲区内等待处理。每个事件通知的处理函数都需要预先进行设置。不同的事件通道,其对应的设置函数亦不相同(表 42)。此外,系统还定义了设置函数bind_caller_port_to_irqhandler()用于Xen控制台(Xen Console)和Xenbus的事件通道处理函数的设定。
事件通道类型(state) | 设置函数 | 说明 |
ECS_UNBOUND | bind_listening_port_to_irqhandler | 未绑定(域间通道) |
ECS_INTERDOMAIN | bind_interdomain_evtchn_to_irqhandler | 域间通道 |
ECS_VIRQ | bind_virq_to_irqhandler | 虚拟中断 |
ECS_IPI | bind_ipi_to_irqhandler | 虚拟IPI |
事件通道处理函数的使用借用了传统的中断处理机制,即将事件通道绑定到中断上,则相应的处理函数成为中断处理函数。利用中断处理函数的调用方法调用事件处理函数。因此,在设置函数中都需要调用中断处理注册函数request_irq()。