3、事件通道的操作
Dom与事件通道相关的操作都需要通过Xen提供的超级调用HYPERVISOR_event_channel_op或HYPERVISOR_event_channel_op_compat来完成。其中HYPERVISOR_event_channel_op_compat被用来兼容Xen 3.0.2以前的超级调用方式。在早期的版本中,系统定义了与该超级调用对应的操作结构体evtchn_op,其中包含两个成员:cmd为操作码,联合体u中保存与操作码对应的结构体,用以保存操作的输入输出参数。
//xen\include\public\event_channel.h 232-251
typedef struct evtchn_op {
uint32_t cmd;
/*
*事件通道分配但未绑定 // #define EVTCHNOP_alloc_unbound 6
*事件通道绑定域间通信 // #define EVTCHNOP_bind_interdomain 0
*事件通道绑定虚拟中断 // #define EVTCHNOP_bind_virq 1
*事件通道绑定物理中断 // #define EVTCHNOP_bind_pirq 2
*事件通道绑定ipi // #define EVTCHNOP_bind_ipi 7
*事件通道关闭 // #define EVTCHNOP_close 3
*发送事件 // #define EVTCHNOP_send 4
*事件通道状态查询 // #define EVTCHNOP_status 5
*绑定vcpu // #define EVTCHNOP_bind_vcpu 8
*清除MASK位 // #define EVTCHNOP_unmask 9
*重置所有事件通道 // #define EVTCHNOP_reset 10
*/
union {
evtchn_alloc_unbound_t alloc_unbound;
evtchn_bind_interdomain_t bind_interdomain;
evtchn_bind_virq_t bind_virq;
evtchn_bind_pirq_t bind_pirq;
evtchn_bind_ipi_t bind_ipi;
evtchn_close_t close;
evtchn_send_t send;
evtchn_status_t status;
evtchn_bind_vcpu_t bind_vcpu;
evtchn_unmask_t unmask;
} u;
} evtchn_op_t;
之后的版本中,超级调用舍弃以evtchn_op结构体为参数的方式,改为直接调用其中的两个成员,即操作码和与操作码对应的结构体参数。
//xen/arch/X86/compat.c 29-38
/* 原始超级调用 (版本号:0x00030202). */
long do_event_channel_op_compat(Xen_GUEST_HANDLE(evtchn_op_t) uop)
{
struct evtchn_op op;
if ( unlikely(copy_from_guest(&op, uop, 1) != 0) )
return -EFAULT;
/*超级调用服务例程*/
return do_event_channel_op(op.cmd, guest_handle_from_ptr(&uop.p->u, void));
}
目前,系统定义了11种事件通道操作,其中包括事件通道的绑定、关闭和重置操作、发送事件通知和事件通道状态查询操作(如表3-1所示)
表 31 事件通道操作
操作码(cmd) | 值 | 结构体参数 | 说明 |
EVTCHNOP_alloc_unbound | 6 | evtchn_alloc_unbound | 分配端口并处于未绑定状态 |
EVTCHNOP_bind_interdomain | 0 | evtchn_bind_interdomain | 域间绑定 |
EVTCHNOP_bind_virq | 1 | evtchn_bind_virq | 绑定虚拟中断(vIRQ) |
EVTCHNOP_bind_pirq | 2 | evtchn_bind_pirq | 绑定物理中断(pIRQ) |
EVTCHNOP_bind_ipi | 7 | evtchn_bind_ipi | 绑定IPI中断 |
EVTCHNOP_close | 3 | evtchn_close | 关闭事件通道端口 |
EVTCHNOP_send | 4 | evtchn_send | 发送事件通知消息 |
EVTCHNOP_status | 5 | evtchn_status | 查看事件通道状态 |
EVTCHNOP_bind_vcpu | 8 | evtchn_bind_vcpu | 绑定VCPU |
EVTCHNOP_unmask | 9 | evtchn_unmask | 清除MASK位 |
EVTCHNOP_reset | 10 | evtchn_reset | 关闭所有事件通道 |
上述操作的处理代码都定义在该超级调用的服务例程do_event_channel_op()中。在调用时,根据操作码cmd的值选择相对应的操作。每个操作都拥有自己的结构体参数,其中包含必要的输入参数和输出参数。例如,域间绑定操作的结构体参数为evtchn_bind_interdomain。这些结构体参数都定义在文件xen/include/public/event_channel.h中。
//xen\include\public\Event_channel.c 769-881
long do_event_channel_op(int cmd, Xen_GUEST_HANDLE(void) arg)
{
long rc;
switch ( cmd )
{
case EVTCHNOP_alloc_unbound: {
struct evtchn_alloc_unbound alloc_unbound;
if ( copy_from_guest(&alloc_unbound, arg, 1) != 0 )
return -EFAULT;
rc = evtchn_alloc_unbound(&alloc_unbound);
if ( (rc == 0) && (copy_to_guest(arg, &alloc_unbound, 1) != 0) )
rc = -EFAULT; /* Cleaning up here would be a mess! */
break;
}
case EVTCHNOP_bind_interdomain: {
struct evtchn_bind_interdomain bind_interdomain;
if ( copy_from_guest(&bind_interdomain, arg, 1) != 0 )
return -EFAULT;
rc = evtchn_bind_interdomain(&bind_interdomain);
if ( (rc == 0) && (copy_to_guest(arg, &bind_interdomain, 1) != 0) )
rc = -EFAULT; /* Cleaning up here would be a mess! */
break;
}
……
default:
rc = -ENOSYS;
break;
}
return rc;
}
值得注意的是,在超级调用HYPERVISOR_event_channel_op的服务例程中,是利用函数copy_from_guest()和copy_to_guest()来实现Xen空间与Guest OS空间数据的拷贝。在执行操作前,copy_from_guest()将结构体参数拷贝到Xen空间;操作完成后,copy_to_guest()再将其拷贝回Guest OS空间。若结构体参数中没有输出参数,则不用调用copy_to_guest()执行拷贝回操作。这在其它超级调用中也有应用。
3.1 绑定事件通道
在事件通道初始化完成后,需要对事件通道进行绑定才能够使用。是事件通道相关的绑定操作主要包括:VCPU绑定(EVTCHNOP_bind_vcpu)、域间绑定(EVTCHNOP_bind_interdomain)、虚拟中断绑定(EVTCHNOP_bind_virq)、物理中断绑定(EVTCHNOP_bind_pirq)以及虚拟IPI中断绑定(EVTCHNOP_bind_ipi)。
3.1.1 VCPU绑定(EVTCHNOP_bind_vcpu)
在Dom接收到来之Xen或其它Dom的事件通知后,需要将其送到该Dom的VCPU进行处理。因此,在事件通道使用前需要将其与特定的VCPU。特别对于多VCPU(SMP)的Dom,不同类型的事件通道有不同的VCPU绑定方式。
一般情况下,大多数事件通道在生成时(notify_vcpu_id为0,即所有的事件通道默认都和dom中vcpu 0进行绑定)首先与Dom的第一个VCPU(VCPU0)绑定,随后通过VCPU绑定操作再与其它VCPU绑定。在虚拟IPI中,一对事件通道被用于VCPU之间的通信,因此实际上它们已经完成了与VCPU的绑定;而在虚拟IRQ中,一部分虚拟中断直接和特定的VCPU关联,不能通过VCPU绑定操作再与其它VCPU绑定。这部分和特定的VCPU关联的虚拟中断称之为单个VCPU(per-VCPU)型虚拟中断,即每个VCPU可以通过事件通道绑定一个此类虚拟中断。另一类虚拟中断称之为全局(Global)型虚拟中断,即每个Dom只能绑定一个此类虚拟中断,在所有VCPU间共享。虚拟中断通过绑定事件通道首先与VCPU0绑定,再执行VCPU绑定操作与其它VCPU绑定。
绑定VCPU操作(EVTCHNOP_bind_vcpu)对应的结构体参数为结构体evtchn_bind_vcpu。其中仅包含两个输入参数:事件通道端口号(port)和VCPU序号(vcpu)。
//xen/include/public/event_channel.h 200-206
#define EVTCHNOP_bind_vcpu 8
struct evtchn_bind_vcpu {
/* 输入参数 */
evtchn_port_t port;
uint32_t vcpu;
};
typedef struct evtchn_bind_vcpu evtchn_bind_vcpu_t;
绑定VCPU操作仅仅将事件通道结构体evtchn成员notify_vcpu_id设置为绑定的VCPU即可。在服务例程do_event_channel_op()中,完成绑定VCPU操作的实际处理函数是evtchn_bind_vcpu()。特别的,对于VIRQ,只有全局型才能够进行绑定VCPU操作。
//xen/common/event_channel.c 661-708
long evtchn_bind_vcpu(unsigned int port, unsigned int vcpu_id)
{
struct domain *d = current->domain;
struct evtchn *chn;
long rc = 0;
if ( (vcpu_id >= ARRAY_SIZE(d->vcpu)) || (d->vcpu[vcpu_id] == NULL) )
return -ENOENT;
spin_lock(&d->evtchn_lock);
//端口必须有效,并且对应了事件通道
if ( !port_is_valid(d, port) )
{
rc = -EINVAL;
goto out;
}
//获取端口的事件通道结构
chn = evtchn_from_port(d, port);
/* Guest OS不能绑定Xen使用的事件通道 */
if ( unlikely(chn->consumer_is_xen) )
{
rc = -EINVAL;
goto out;
}
switch ( chn->state )
{
case ECS_VIRQ: /*只有全局型才能够进行操作*/
if ( virq_is_global(chn->u.virq) )
//将事件通道结构体evtchn成员notify_vcpu_id设置为绑定的VCPU
chn->notify_vcpu_id = vcpu_id;
else
rc = -EINVAL;
break;
case ECS_UNBOUND:
case ECS_INTERDOMAIN:
case ECS_PIRQ:
chn->notify_vcpu_id = vcpu_id;
break;
default:
rc = -EINVAL;
break;
}
out:
spin_unlock(&d->evtchn_lock);
return rc;
}
将事件通道绑定到特定VCPU后,事件通知的处理由该VCPU完成,一般情况下,在VCPU处理事件通知时,将屏蔽其它事件通道传递过来的事件通知。事件通道的屏蔽操作可以在两个层面上进行:即能够使所有的VCPU屏蔽一个特定的事件通道;也能够使特定VCPU屏蔽所有的事件通道。对于前者仅仅需要在shared_info中设置该事件通道对应的MASK位;对于后者,VCPU则定义了三个变量来完成这项工作,即evtchn_upcall_pending、evtchn_upcall_mask和evtchn_pending_sel。
//xen/include/public/xen.h 380-411
struct vcpu_info {
uint8_t evtchn_upcall_pending;
uint8_t evtchn_upcall_mask;
/*事件选择器*/
unsigned long evtchn_pending_sel;
struct arch_vcpu_info arch;
struct vcpu_time_info time;
};
其中,evtchn_upcall_pending和evtchn_upcall_mask的读写规则类似于PENDING位和MASK位:evtchn_upcall_pending只能由Xen设置为1,GOS清除为0; evtchn_upcall_mask则由GOS更新(0或1),Xen只有权访问。将evtchn_upcall_mask置1,该VCPU将屏蔽所有的事件通道。此时,若该事件通道产生一个事件通知,Xen将不会通过upcall将其发送给其绑定的VCPU进行处理,也不会设置evtchn_upcall_pending的值。
第三个变量evtchn_pending_sel能够帮助VCPU对未处理的事件通知进行定位,因此也被称作事件选择器(Events Slector)。evtchn_pending_sel的类型为无符号长整数(unsigned long),在X86平台上为32位,其中每一位代表evtchn_pending数组中一组32个事件通道。因此,可以将evtchn_pending_sel中的每一位看做evtchn_pending数组的下标指针(图 31)。结合两者可以方便的得到对应的事件通道端口号值。
Xen在发送事件通知时,在设置evtchn_upcall_pending之后,同样需要设置evtchn_pending_sel中与事件通道相对应的位以便VCPU能够获知。设置工作通过函数evtchn_set_pending()来完成。
//xen/common/event_channel.c 509-544
void evtchn_set_pending(struct vcpu *v, int port)
{
struct domain *d = v->domain;
shared_info_t *s = d->shared_info;
/*设置PENDING位,置位并返回port对应事件通道evtchn_pending位原来的值,如果本来已经置1了,说明已经存在一个未处理的事件通知,新的事件通知将被丢弃,直接return*/
if ( test_and_set_bit(port, __shared_info_addr(d, s, evtchn_pending)) )
return;
/*如果之前不存在未处理的事件,检查share_info中是否设置了事件通道对应的MASK,并且对事件选择器置位,port / BITS_PER_GUEST_LONG(定义为32)用于获得事件通道所在数组的组号*/
if ( !test_bit (port, __shared_info_addr(d, s, evtchn_mask)) &&
!test_and_set_bit(port / BITS_PER_GUEST_LONG(d),
vcpu_info_addr(v, evtchn_pending_sel)) )
{
//如果没设置MASK并且事件选择器之前也没有置位,则设置evtchn_upcall_pending位
vcpu_mark_events_pending(v);
}
/*关闭VCPU轮询*/ if ( unlikely(d->is_polling) )
{
d->is_polling = 0;
smp_mb();
for_each_vcpu ( d, v )
{
if ( !v->is_polling )
continue;
v->is_polling = 0;
vcpu_unblock(v);
}
}
}
当事件通道产生事件通知,不论其MASK位是否为1,PENDING位都将被置1。若此时MASK位为1,则VCPU将不能处理该事件通知,即evtchn_upcall_pending和evtchn_pending_sel不被设置。由于绑定到VCPU的所有事件通道共用一个evtchn_upcall_pending标志,因此若之前还存在未处理的事件通知,则Xen无需更改该标志位;若根据evtchn_pending_sel划分组中存在其它未处的理事件通知, Xen也无需更改evtchn_pending_sel中对应的标志位。前面提到,在Xen通过upcall发送事件通知前需要检查对应的PENDING位和MASK位,只有在两者都为0时才会进行发送。但是,若该事件通道未与绑定VCPU,或者绑定的VCPU将事件通道屏蔽,Xen都将不会发送事件通知到VCPU。因此,Xen放弃发送该事件通知还存在第三种情况:
(1)对应的PENDING位为1。在这种情况下,表明该事件通道已经存在一个未处理的事件通知,新的事件通知将被丢弃。
(2)对应的MASK位为1。GOS主动屏蔽了该事件通道的事件通知,Xen将放弃发送。
(3)未绑定VCPU或VCPU屏蔽事件通道。一般在VCPU正在处理其它事件通知时会主动屏蔽事件通知。
在图1-2的基础上,新的Xen发送事件通知流程如图 3-2所示。
3.1.2 域间绑定(EVTCHNOP_bind_interdomain)
域间绑定是将一对事件通道绑定到两个Dom之间,通过事件通知实现Dom之间的通信。对于每个Dom而言,通信的对方被称之为远端Dom(Remote Domian)。域间绑定操作大致分为两个步骤(DomA和DomB):DomB预先分配一个未绑定的事件通道端口号,并授权DomA可以使用该端口号;DomA获取该未绑定的端口号,将其绑定用于域间通信。这个过程涉及超级调用HYPERVISOR_event_channel_op的两个操作:DomB分配未绑定端口号以及DomA进行域间绑定。
分配未绑定端口号操作对应的结构体参数为结构体evtchn_alloc_unbound,其中输入参数为提供未绑定端口号的Dom B(dom)和授权使用的Dom A(remote_dom),输出参数为分配的端口号(port)。
//xen\include\public\Event_channel.h 48-55
#define EVTCHNOP_alloc_unbound 6
struct evtchn_alloc_unbound {
/* 入参*/
domid_t dom, remote_dom;
/* 出参*/
evtchn_port_t port;
};
typedef struct evtchn_alloc_unbound evtchn_alloc_unbound_t;
若Dom B不是特权Dom,则可以通过特权域为其分配事件通道。若由特权域进行分配,输入参数dom的值为DomB的ID;若自己分配,则dom值为DOMID_SELF。此外,Dom B可以分配端口号供自己使用(Dom A与Dom B相同),即另一个输入参数remote_dom可以为DOMID_SELF。分配操作的核心处理函数为evtchn_alloc_unbound()。
//xen\include\public\Event_channel.c 99-136
static long evtchn_alloc_unbound(evtchn_alloc_unbound_t *alloc)
{
struct evtchn *chn;
struct domain *d;
int port;
domid_t dom = alloc->dom;
long rc;
if ( (rc = acm_pre_eventchannel_unbound(dom, alloc->remote_dom)) != 0 )
return rc;
/*如果是自己给自己分,dom == DOMID_SELF,并且执行该线程的domain id即为分配事件通道端口的domain id,如果不是自己给自己分,那么执行该线程的dom必须是特权域,因为只有特权域可以为其它Dom分配事件通道*/
if ( dom == DOMID_SELF )
dom = current->domain->domain_id;
else if ( !IS_PRIV(current->domain) )
return -EPERM;
//根据dom id获得相应的数据结构
if ( (d = rcu_lock_domain_by_id(dom)) == NULL )
return -ESRCH;
spin_lock(&d->evtchn_lock);
//在有效并且对应了事件通道的端口中寻找第一个没有使用的事件通道的端口
if ( (port = get_free_port(d)) < 0 )
ERROR_EXIT(port);
//获取端口对应的事件通道数据结构
chn = evtchn_from_port(d, port);
//将该事件通道的状态标志为分配但为绑定
chn->state = ECS_UNBOUND;
//remote_dom为DOMID_SELF的前提应该是dom也为DOMID_SELF,即自己给自己分配
if ( (chn->u.unbound.remote_domid = alloc->remote_dom) == DOMID_SELF )
chn->u.unbound.remote_domid = current->domain->domain_id;
alloc->port = port;
out:
spin_unlock(&d->evtchn_lock); rcu_unlock_domain(d);
return rc;
}
分配操作完成后,事件通道的状态为未绑定(ECS_UNBOUND),并将端口号存入输出参数(port)。同时,输入参数remote_dom的值被放入事件通道结构体evtchn的成员共同体u中。若remote_domid的值为DOMID_SELF,则需要转换成ID(domain_id)。
在Dom B完成分配操作后,其分配的端口号(port)被放入XenStore,以便Dom A能够获取该端口号。Dom A通过XenStore获取该端口号后,可以进行域间绑定操作。域间绑定操作对应结构体参数为结构体evtchn_bind_interdomain,其中输入参数包括远端Dom(remote_dom)和其分配的端口号(remote_port),输出参数为操作分配的本地端口号(local_port)。
//xen\include\public\Event_channel.h 66-74
#define EVTCHNOP_bind_interdomain 0
struct evtchn_bind_interdomain {
/* 入参 */
domid_t remote_dom; //预先分配事件通道的dom id
evtchn_port_t remote_port; //预先分配事件通道对应的端口
/* 出参*/
evtchn_port_t local_port; //执行域间绑定操作的本地端口
};
typedef struct evtchn_bind_interdomain evtchn_bind_interdomain_t;
除了Dom B提供的未绑定事件通道,Dom A也需要分配一个事件通道用于域间绑定。两个事件通道共同构成Dom A和Dom B之间的双向通信连接。Dom A向Dom B发送事件通知只需将该事件通知发送到本地端口即可。为了避免遗漏在预先分配的Dom B端口中存在的事件通知,在操作完成后调用evtchn_set_pending()设置VCPU0的evtchn_upcall_pending和evtchn_pending_sel,以便与本地事件通道绑定的VCPU0能够处理可能存在的事件通知。域间绑定操作的核心处理函数为evtchn_bind_interdomain()。
//xen\include\public\Event_channel.c 139-204
static long evtchn_bind_interdomain(evtchn_bind_interdomain_t *bind)
{
struct evtchn *lchn, *rchn; //本地事件通道与远端事件通道
struct domain *ld = current->domain, *rd; //本地dom id与远端dom id
int lport, rport = bind->remote_port; //本地事件通道端口与远端事件通道端口
domid_t rdom = bind->remote_dom;
long rc;
/*ACM访问控制*/
if ( (rc = acm_pre_eventchannel_interdomain(rdom)) != 0 )
return rc;
if ( rdom == DOMID_SELF )
rdom = current->domain->domain_id;
if ( (rd = rcu_lock_domain_by_id(rdom)) == NULL )
return -ESRCH;
/*通过请求较小的dom id作域间锁来避免死锁,因为如果是自己分配事件通道给自己,只需要锁一次就够了 */
if ( ld < rd )
{
spin_lock(&ld->evtchn_lock);
spin_lock(&rd->evtchn_lock);
}
else
{
if ( ld != rd )
spin_lock(&rd->evtchn_lock);
spin_lock(&ld->evtchn_lock);
}
//在本地dom中找到一个最小的有效并且未被使用的事件通道端口
if ( (lport = get_free_port(ld)) < 0 )
ERROR_EXIT(lport);
lchn = evtchn_from_port(ld, lport);
//检查远端的端口是否有效
if ( !port_is_valid(rd, rport) )
ERROR_EXIT(-EINVAL);
//获取远端的事件通道结构
rchn = evtchn_from_port(rd, rport);
/*远端事件通道检查*/
if ( (rchn->state != ECS_UNBOUND) ||
(rchn->u.unbound.remote_domid != ld->domain_id) )
ERROR_EXIT(-EINVAL);
/*本地、远端事件通道配置*/
lchn->u.interdomain.remote_dom = rd;
lchn->u.interdomain.remote_port = (u16)rport;
lchn->state = ECS_INTERDOMAIN;
rchn->u.interdomain.remote_dom = ld;
rchn->u.interdomain.remote_port = (u16)lport;
rchn->state = ECS_INTERDOMAIN;
/*处理远端事件通道中可能存在的事件通知*/
evtchn_set_pending(ld->vcpu[lchn->notify_vcpu_id], lport);
/*设置输出参数*/
bind->local_port = lport;
out:
spin_unlock(&ld->evtchn_lock);
if ( ld != rd )
spin_unlock(&rd->evtchn_lock);
rcu_unlock_domain(rd);
return rc;
}
操作完成后,Dom A(或Dom B)分配的事件通道状态更新为ECS_INTERDOMAIN,且结构体evtchn中成员共同体u包含结构体interdomain,用以保存远端Dom的ID和其分配的端口号。两个事件通道结构体参数设置如图 33所示。
图 33 事件通道设置
3.1.3 虚拟IPI绑定(EVTCHNOP_bind_ipi)
虚拟IPI(域内通信)被用于Dom内部VCPU之间的通信。对于绑定IPI的事件通道而言,通道两端都为VCPU,其中一端为调用的VCPU,而另一端需要通过设置事件通道结构体evtchn成员notify_vcpu_id进行指定。
虚拟IPI绑定操作对应的结构体参数为结构体evtchn_bind_ipi,其中输入参数为需要指定的另一端VCPU(vcpu),输出参数为分配的事件通道端口号(port)。
//xen\include\public\Event_channel.h 121-127
#define EVTCHNOP_bind_ipi 7
struct evtchn_bind_ipi {
/*入参*/
uint32_t vcpu;
/* 出参*/
evtchn_port_t port;
};
typedef struct evtchn_bind_ipi evtchn_bind_ipi_t;
虚拟IPI绑定操作将改变事件通道的状态为ECS_IPI。在结构体evtchn成员共同体u中并没有与之对应的结构体,因此完成操作只需要修改notify_vcpu_id即可。该操作的核心处理函数为evtchn_bind_ipi()。
//xen\include\public\Event_channel.c 247-273
static long evtchn_bind_ipi(evtchn_bind_ipi_t *bind)
{
struct evtchn *chn;
struct domain *d = current->domain;
int port, vcpu = bind->vcpu;
long rc = 0;
if ( (vcpu < 0) || (vcpu >= ARRAY_SIZE(d->vcpu)) || (d->vcpu[vcpu] == NULL) )
return -ENOENT;
spin_lock(&d->evtchn_lock);
if ( (port = get_free_port(d)) < 0 )
ERROR_EXIT(port);
chn = evtchn_from_port(d, port);
chn->state = ECS_IPI;
chn->notify_vcpu_id = vcpu;
bind->port = port;
out:
spin_unlock(&d->evtchn_lock);
return rc;}
3.1.4 虚拟中断绑定(EVTCHNOP_bind_virq)
虚拟中断分为全局(Global)型虚拟中断和单个VCPU(per-VCPU)型虚拟中断。每个全局型虚拟中断为Dom的所有VCPU共享,因而虚拟中断绑定事件通道时,只能先与VCPU0绑定,再通过执行VCPU绑定操作与其它VCPU绑定。而单个VCPU型虚拟中断则没有这样的限制,每个虚拟中断都可以通过绑定事件通道与特定VCPU绑定。
虚拟中断绑定操作对应的结构体参数为结构体evtchn_bind_virq,其中输入参数包括虚拟中断号(virq)和绑定的VCPU(vcpu),输出参数为分配的事件通道端口号(port)。
//xen\include\public\Event_channel.h 88-96
#define EVTCHNOP_bind_virq 1
struct evtchn_bind_virq {
/*入参*/
uint32_t virq;
uint32_t vcpu;
/* 出参*/
evtchn_port_t port;
};
typedef struct evtchn_bind_virq evtchn_bind_virq_t;
在VCPU结构体中,定义了数组virq_to_evtchn[NR_VIRQS],用来保存与各个虚拟中断绑定的事件通道端口号,以虚拟中断号为下标可以在数组中得到相应的事件通道端口号。宏NR_VIRQS值为24,即虚拟中断个数的最大值。每个虚拟中断在绑定事件通道后,将不能够重新绑定,即若在数组中虚拟中断对应的值不为0,意味着对该虚拟中断的绑定操作无法完成。操作的核心处理函数为evtchn_bind_virq()。
//xen\include\public\Event_channel.c 207-244
static long evtchn_bind_virq(evtchn_bind_virq_t *bind)
{
struct evtchn *chn;
struct vcpu *v;
struct domain *d = current->domain;
int port, virq = bind->virq, vcpu = bind->vcpu;
long rc = 0;
if ( (virq < 0) || (virq >= ARRAY_SIZE(v->virq_to_evtchn)) )
return -EINVAL;
/*虚拟中断类型检查,全局型只能与VCPU0绑定,其它无限制*/
if ( virq_is_global(virq) && (vcpu != 0) )
return -EINVAL;
if ( (vcpu < 0) || (vcpu >= ARRAY_SIZE(d->vcpu)) ||
((v = d->vcpu[vcpu]) == NULL) )
return -ENOENT;
spin_lock(&d->evtchn_lock);
/*虚拟中断不能重复绑定事件通道*/
if ( v->virq_to_evtchn[virq] != 0 )
ERROR_EXIT(-EEXIST);
if ( (port = get_free_port(d)) < 0 )
ERROR_EXIT(port);
chn = evtchn_from_port(d, port);
chn->state = ECS_VIRQ;
chn->notify_vcpu_id = vcpu;
chn->u.virq = virq;
/*更新virq_to_evtchn[]数组,与分配的端口建立映射*/
v->virq_to_evtchn[virq] = bind->port = port; (两次赋值)
out:
spin_unlock(&d->evtchn_lock);
return rc;
}
3.1.5 物理中断绑定(EVTCHNOP_bind_pirq)
在Xen系统中,Dom对物理设备的访问有严格的限制。除了Dom0和设备驱动域(IDD),其它的Dom无权申请物理中断。拥有权限的Dom通过物理中断绑定操作申请物理中断,但是Dom不能够直接处理物理中断,需要先由Xen接收中断信号,再转换成事件通知交由Dom处理。
物理中断绑定操作对应的结构体参数为结构体evtchn_bind_pirq,其中输入参数包括物理中断号(pirq)和操作标识(flags),输出参数为分配的事件通道端口号(port)。
//xen\include\public\Event_channel.h 104-113
#define EVTCHNOP_bind_pirq 2
struct evtchn_bind_pirq {
/* 入参*/
uint32_t pirq;
#define BIND_PIRQ__WILL_SHARE 1
uint32_t flags; /* BIND_PIRQ__* */
/* 出参*/
evtchn_port_t port;
};
typedef struct evtchn_bind_pirq evtchn_bind_pirq_t;
与虚拟中断类似,在Dom结构体中,定义了数组pirq_to_evtchn[NR_IRQS],用来保存与各个物理中断绑定的事件通道端口号,以物理中断号为下标可以在数组中得到相应的事件通道端口号。每个物理中断在绑定事件通道后,同样不能够重新绑定。操作的核心处理函数为evtchn_bind_pirq ()。
//xen/include/public/event_channel.c 276-317
static long evtchn_bind_pirq(evtchn_bind_pirq_t *bind)
{
struct evtchn *chn;
struct domain *d = current->domain;
int port, pirq = bind->pirq;
long rc;
if ( (pirq < 0) || (pirq >= ARRAY_SIZE(d->pirq_to_evtchn)) )
return -EINVAL;
/*Dom权限判断*/
if ( !irq_access_permitted(d, pirq) )
return -EPERM;
spin_lock(&d->evtchn_lock);
/*不能重复绑定事件通道*/
if ( d->pirq_to_evtchn[pirq] != 0 )
ERROR_EXIT(-EEXIST);
if ( (port = get_free_port(d)) < 0 )
ERROR_EXIT(port);
chn = evtchn_from_port(d, port);
d->pirq_to_evtchn[pirq] = port;
rc = pirq_guest_bind(d->vcpu[0], pirq,
!!(bind->flags & BIND_PIRQ__WILL_SHARE));
/*检查函数是否成功返回*/
if ( rc != 0 )
{
d->pirq_to_evtchn[pirq] = 0;
goto out;
}
chn->state = ECS_PIRQ;
chn->u.pirq = pirq;
bind->port = port;
out:
spin_unlock(&d->evtchn_lock);
return rc;
函数pirq_guest_bind()完成具体的物理中断绑定操作。只有函数成功返回,物理中断的绑定操作才算完成。