Xen事件通道详细介绍(一)

来源:互联网 发布:下载淘宝 编辑:程序博客网 时间:2024/05/20 02:21

1、基本概念

事件通道(Event Channel)是Xen用于Dom和Xen之间、Dom和Dom之间的异步事件通知机制,事件通道的应用非常广泛,Xen体系结构上的物理中断(pIRQ)、虚拟中断(vIRQ)、虚拟处理器间中断(Virtual Inter-Processor Interrupt,vIPI)以及Dom域间通信(Inter-Domain Communication,IDC)均需通过事件通道实现。

事件通道机制和超级调用机制一起完成Xen和Dom之间的控制和交互,即:使用超级调用产生从Dom到Xen的同步调用;使用异步事件机制完成从Xen到Dom的通知(Notification)递交。由事件通道提供的从Xen到Dom的通信机制能够取代常用的利用设备中断的递交机制,使得采用轻量级的通知形式成为可能。域间通信(IDC)机制,配合以I/O环(I/O Ring)为基础的共享内存机制使得Dom之间传递信息和数据更加有效。

Xen系统中,每个Dom都能够拥有自己的事件通道。系统规定,每个Dom最多能够分配NR_EVENT_CHANNELS个事件通道。在X86平台上,宏NR_EVENT_CHANNELS的值为1024,即最多能够拥有1024个事件通道。

//xen/include/public/xen.h 47-51

/*每个Dom的事件通道个数:*/

#define NR_EVENT_CHANNELS (sizeof(unsigned long) * sizeof(unsigned long) * 64)

每个事件通道都有自己唯一的编号,即端口(port),其范围为0 -- NR_EVENT_CHANNELS-1。在X86平台上,事件通道的端口范围为0—1023,这些事件通道被分为8个组(Bucket),每组128个。

//xen/include/xen/sched.h 40-41

#define EVTCHNS_PER_BUCKET 128

#define NR_EVTCHN_BUCKETS  (NR_EVENT_CHANNELS / EVTCHNS_PER_BUCKET)

系统为每个Dom分配NR_EVTCHN_BUCKETS个事件通道,每个事件通道与一个结构体evtchn对应。这些结构体组成一个长度为NR_EVTCHN_BUCKETS的结构体数组,供Dom调用。结构体数组的定义在代表Dom的结构体domain中。

//xen/include/xen/sched.h 143-227

struct domain

{

    domid_t          domain_id;

    shared_info_t   *shared_info;     /* 共享数据区 */

……

   /*事件通道信息*/ 

//数组指针,定位一个事件通道需要二维信息(哪一组中的哪一个)

struct evtchn   *evtchn[NR_EVTCHN_BUCKETS];   

   spinlock_t       evtchn_lock;

   struct grant_table *grant_table;

/*对事件通道映射的中断*/

   u16              pirq_to_evtchn[NR_IRQS];

   DECLARE_BITMAP(pirq_mask, NR_IRQS);

   ……

   struct vcpu *vcpu[MAX_VIRT_CPUS];

 

   /*CPU的掩码将控制每个域的状态*/

cpumask_t        domain_dirty_cpumask;

   ……

};

在结构体domain中,长度为NR_EVTCHN_BUCKETS的结构体数组以二维数组的形式存在,即满足8组,每组128个事件通道的划分。事件通道端口号和数组下标之间一一对应,它们之间的转换由系统定义的宏完成。这些宏主要包括bucket_from_port(d,p)、port_is_valid(d,p)和evtchn_from_port(d,p)。其中,port_is_valid(d,p)用来判断端口号是否有效以及端口号对应的事件通道是否存在;evtchn_from_port(d,p)和bucket_from_port(d,p)则是根据端口号获取该事件通道对应的结构体evtch n以及其所在的组别。例如,端口号为129的事件通道在结构体数组中所处的位置为evtchn[1][1]。这三个宏的定义在文件xen/common/event_channel.c中。

//xen/common/event_channel.c 34-40

 //得到该端口事件通道在domain中的组别

#define bucket_from_port(d,p) \

    ((d)->evtchn[(p)/EVTCHNS_PER_BUCKET])   

//判断端口号是否有效以及端口号对应的事件通道是否存在

#define port_is_valid(d,p)    \

    (((p) >= 0) && ((p) < MAX_EVTCHNS(d)) && \  

     (bucket_from_port(d,p) != NULL))

//根据端口号获取该事件通道对应的结构体evtch n

#define evtchn_from_port(d,p) \

 //首先获得组号,再与低7位做与操作获得具体组内标识,从而得到具体的事件通道结构体位置

    (&(bucket_from_port(d,p))[(p)&(EVTCHNS_PER_BUCKET-1)]) 

1.1 结构体evtchn

在事件通道对应的结构体evtchn中,主要定义了事件通道的各种状态以及不同状态所必须的基本参数。这些通过宏定义的不同状态既包含了事件通道的类型,又包含了事件通道在使用过程中可能的状态。

//xen/include/xen/sched.h 43-66

struct evtchn

{

#define ECS_FREE         0   /* 该通道可用                  */

#define ECS_RESERVED     1  /* 该通道被保留                 */

#define ECS_UNBOUND      2  /* 该通道可以跟远端的域绑定        */

#define ECS_INTERDOMAIN  3  /* 该通道已经绑定另一个域          */

#define ECS_PIRQ         4  /* 该通道绑定物理中断             */

#define ECS_VIRQ         5  /* 该通道绑定虚拟中断.            */

#define ECS_IPI          6  /* 该通道绑定虚拟IPI             */

    u8  state;              /* 该通道的状态                     */

    u8  consumer_is_xen;    /* 是用Xen还是由客户使用          */

    u16 notify_vcpu_id;     /*用来传递通知的VCPU号            */

    union {

        struct {

            domid_t remote_domid;

        } unbound;          /* state 值为 ECS_UNBOUND */

        struct {

            u16          remote_port;

            struct domain *remote_dom;

        } interdomain;   /* state 值为 ECS_INTERDOMAIN */

        u16 pirq;        /* state 值为 ECS_PIRQ */

        u16 virq;        /* state 值为 ECS_VIRQ */

    } u;

};

 

目前,事件通道有7种可能的状态(如表1-1所示):ECS_FREE表示该事件通道处于等待使用的状态,即事件通道已经完成初始化,等待分配;ECS_RESERVED表示该事件通道被系统保留,不参与分配; ECS_UNBOUND表示该事件通道处于未绑定状态,ECS_INTERDOMAIN表示该事件通道已经处于和其它Dom绑定的状态;ECS_PIRQ、ECS_VIRQ和ECS_IPI则分别表示该事件通道绑定某一物理中断(pIRQ)、虚拟中断(vIRQ)和虚拟处理器间中断(vIPI)。

表 1-1 事件通道的状态

状态(state)

说明

ECS_FREE

已初始化,等待分配

ECS_RESERVED

保留(关闭)

ECS_UNBOUND

未绑定

ECS_INTERDOMAIN

与另一Dom绑定(域间通信)

ECS_PIRQ

物理中断

ECS_VIRQ

虚拟中断

ECS_IPI

虚拟处理器间中断(域内通信)

 

在这7种可能的状态中,ECS_INTERDOMAIN、ECS_PIRQ、ECS_VIRQ和ECS_IPI都表示该事件通道处于绑定状态,区别在于它们的绑定对象不同,即用途不同。事件通道根据用途可以划分为四种类型:域间通信、域内通信、物理中断和虚拟中断。

结构体evtchn包含4个成员:state表示该事件通道的类型和所处的状态,取值上述7种状态之一;consumer_is_xen表示该事件通道是否为Xen使用,取值为1则说明该事件通道的控制权属于Xen,Dom无权对其执行关闭、发送事件通知,绑定VCPU等操作;notify_vcpu_id则表示接收并处理事件通知的本地VCPU的ID,即vcpu_id;联合体u中保存不同类型的事件通道所必须的基本参数。联合体u的具体取值随state变化:若state为ECS_UNBOUND,联合体u中为结构体unbound,其中包含远端Dom的ID;若state为ECS_INTERDOMAIN,联合体u中为结构体interdomain,其中包含远端Dom的domain结构体指针和远端Dom分配的事件通道端口;若state为ECS_PIRQ时,联合体u中为事件通道所绑定的物理中断号;若state为ECS_VIRQ,则是所绑定的虚拟中断号。

1.1.1 域间通信(Inter-Domain Communication,IDC)

此类事件通道用于在Dom之间建立双向(Bi-directional)连接。所谓双向连接,是指在域间通信中,事件通道都是成对使用的,即通信双方Dom各需要分配一个事件通道来建立连接。建立双向连接大致需要两个步骤:DomA预先分配一个新的事件通道(端口),并标注为未绑定状态(ECS_UNBOUND),并授权其它的Dom(Remote Domain,远端Dom)可以绑定这个端口;当远端Dom(DomB)需要与该DomA建立连接时,DomB分配一个事件通道并与DomAin提供的端口进行绑定(ECS_INTERDOMAIN)。在连接建立后,DomA通过想本地端口发送事件通知DomB,反之亦然。域间通信大多应用在分离设备驱动模型中,Dom0和DomU通过域间通信完成对前后端设备的操作。后端设备通过发送通知消息告知前端设备有数据在等待接受;前端设备则通过发送通知消息通知后端设备有数据需要发送

1.1.2 域内通信(Intra-Domain Communication)

域内通信可以看作是域间通信的特例,即通信双方Dom为同一Dom。它用于Dom内部虚拟CPU(VCPU)之间的通信,因此被称之为虚拟处理器间中断(vIPI)。相应的事件通道的状态被标记为ECS_IPI。

1.1.3 物理中断(pIRQ)

由于系统中物理中断的异步性,Xen系统中的物理中断被事件通道取代。对于有权访问硬件设备的Dom(Dom0或IDD),可以通过为每个物理中断源绑定一个事件通道端口,接收和处理由硬件设备发送的物理中断。物理中断的绑定信息以数组的形式(pirq_to_evtchn[NR_IRQS])保存在结构体domain中。其中,数组以中断号为下标,成员中存放的则是事件通道端口号。用于物理中断的事件通道是单个使用的

1.1.4 虚拟中断(vIRQ)

虚拟中断和物理中断类似,区别在于虚拟中断中参与绑定的虚拟设备。例如,系统中虚拟计时器设备同物理计时器一样,能够使GOS在一个特定的时间请求计时器事件,引发计时器中断。将该虚拟计时器中断(VIRQ_TIMER)同事件通道绑定,在发生计时器中断时GOS将收到来之该事件通道的事件通知。用于虚拟中断的事件通道是单个使用的

1.2 PENDING和MASK位

在基于事件通道的异步通知机制中,除了结构体数组evtchn[NR_EVTCHN_BUCKETS]外,还有两个数组至关重要,即前面提到的数组evtchn_pending[]和evtchn_mask[]。事件通道异步机制的实现需要借助于这两个数组。

//xen/include/public/xen.h 424-474

struct shared_info {

    struct vcpu_info vcpu_info[MAX_VIRT_CPUS]; //用于特定的VCPU对所有事件通道操作

/*所有VCPU对特定事件通道的操作(事件通知与屏蔽),长整型为4字节,32位,数组中共32个长整型,总共1024个二进制位,每一位对应一个事件通道*/

    unsigned long evtchn_pending[sizeof(unsigned long) * 8];

    unsigned long evtchn_mask[sizeof(unsigned long) * 8];

……

};

数组evtchn_pending和evtchn_mask中分别保存与事件通道对应的两个标志位:PENDING位和MASK位。每个事件通道都在数组中对应这样一对标志位。其中,PENDING位表示在事件通道中是否存在一个未处理的事件通知;MASK位表示是否屏蔽该事件通道的事件通知。事件通道的使用包含两个使用者:发送事件通知的发送方(Sender)和接收事件通知的接收方(Reciever)。两者对PENDING位和MASK位的读写权限各不相同。

例如,在用于物理中断或虚拟中断的事件通道中,其发送方为Xen,接收方为GOS。这两者对于PENDING位和MASK位的操作如图所示。


图 11 PENDING位和MASK位的操作

1.2.1 PENDING

PENDING位只能由发送方(Xen)设置为1,并由接收方(GOS)在处理完事件后清除为0当某一事件通道产生一个事件通知时,Xen将其对应的PENDING位置1,并通过upcall向目标Dom发送该事件通知。若目标Dom处于阻塞(Blocked)状态,则该Dom将会被调度进入运行队列。值得注意的是每一个事件通道中只能包含一个未处理的事件通知。也就是说,当事件通知产生时,对应的PENDIG位值已经为1,则新产生的事件通知自动被忽略

1.2.2 MASK

MASK位只能由接收方(GOS)更新(01),发送方(Xen)对其只有访问权限,不能够进行修改。若事件通道的MASK位被GOS1,则表明GOS将主动屏蔽该事件通道的事件通知。当MASK位为1时,若该事件通道产生一个事件通知,Xen只将对应的PENDING位置1,并不会通过upcall向目标Dom发送事件通知;且若目标Dom阻塞,Xen也不会唤醒阻塞的目标Dom。为了防止GOS处理一些不必要的upcall,在GOS更新MASK前,需要预先清除其对应的PENDING位。

1.2.3 事件通知流程

在事件通道产生一个事件通知后,Xen通过upcall向目标Dom发送该事件通知前,Xen需要进行一系列的判断(图 12)。在下面两种情况下,Xen将放弃发送该事件通知:

1)对应的PENDING位为1。在这种情况下,表明该事件通道已经存在一个未处理的事件通知,新的事件通知将被丢弃。

2)对应的MASK位为1GOS主动屏蔽了该事件通道的事件通知,Xen将放弃发送。



图 12 Xen发送事件通知流程

事件通道的使用不仅仅局限在XenGuest OS之间。在域间通信的应用中,事件通道被用于Dom之间,即事件通知对应的发送方和接收方都为Dom(如Dom ADom B)。在Dom ADom B之间存在双向的事件通道。这意味着通信双方都能够通过设置其中一个事件通道的PENDING位向另一个Dom发送事件通知;也能够通过设置另一个事件通道的MASK位屏蔽来之另一个Dom的事件通知。对于任一个事件通道,它对应的PENDING位和MASK位的读写规则与Xen-GOS事件通道相同。

2、事件通道的初始化

Xen系统中,每个Dom都拥有各自的事件通道。事件通道的初始化在Dom创建时完成,即在Dom创建函数domain_create()中调用事件通道初始化函数evtchn_init()。在X86平台上,每个Dom最多能够分配1024个事件通道。这些事件通道被分为8组(Bucket),每组128个。事件通道的初始化以组为单位。且在首次初始化过程中,仅初始化第一组的128个事件通道。只有在第一组事件通道被分配完后,才会初始化第二组以供使用

 

//xen/common/event_channel.c 949-956

int evtchn_init(struct domain *d)

{

    spin_lock_init(&d->evtchn_lock);

 /*初始化时,函数返回值为0,即第一个事件通道端口号*/

    if ( get_free_port(d) != 0 )

        return -EINVAL;

 /*保留evtchn [0][0],即第一个事件通道 */

    evtchn_from_port(d, 0)->state = ECS_RESERVED;

    return 0;

}

在事件通道初始化函数evtchn_init()中,具体的初始化工作由函数get_free_port()完成。但是该函数的作用不仅仅局限于此,它实际上是在已经初始化的事件通道中顺次找到第一个未分配的事件通道(ECS_FREE),若查找失败则将初始化下一组并返回该组第一个事件通道的端口号。第一次初始化前,调用get_free_port()会找不到任何一个可用的端口,因此它将初始化第一组事件通道,并返回第一个可用的事件通道evtchn[0][0],其端口号为0

在事件通道初始化函数evtchn_init()中,具体的初始化工作由函数get_free_port()完成。但是该函数的作用不仅仅局限于此,它实际上是在已经初始化的事件通道中顺次找到第一个未分配的事件通道(ECS_FREE),若查找失败则将初始化下一组并返回该组第一个事件通道的端口号。第一次初始化前,调用get_free_port()会找不到任何一个可用的端口,因此它将初始化第一组事件通道,并返回第一个可用的事件通道evtchn[0][0],其端口号为0

//xen/common/event_channel.c 77-96

static int get_free_port(struct domain *d)

{

    struct evtchn *chn;

    int         port;

//在有效并且对应了事件通道的端口中寻找第一个没有使用的事件通道的端口

    for ( port = 0; port_is_valid(d, port); port++ )

        if ( evtchn_from_port(d, port)->state == ECS_FREE )

            return port;

    if ( port == MAX_EVTCHNS(d) )

        return -ENOSPC;

//分配一组事件通道空间,并将事件通道数组的首地址复制给一个事件通道结构指针

    chn = xmalloc_array(struct evtchn, EVTCHNS_PER_BUCKET);

    if ( unlikely(chn == NULL) )

        return -ENOMEM;

 /*结构体数组清零*/

    memset(chn, 0, EVTCHNS_PER_BUCKET * sizeof(*chn));

    bucket_from_port(d, port) = chn; /*加入新的一组事件通道*/

 

    return port;

}

 

在第一次初始化过程中,get_free_port()通过memset()将分配的结构体数组全部清零,其中包括结构体evtchn成员state的值。成员state值为0,意味着初始化后事件通道都处于未分配状态(ECS_FREE)。在第一批初始化的事件通道中,第一个(端口号为0)被系统保留(ECS_RESERVED)。

事件通道完成初始化后,系统为了避免接收非正常的事件通知,将对应的MASK位全部置1以屏蔽发送事件通知,并在事件通道被绑定后t再清除MASK位;同时在进行事件通道绑定前前,需要将该事件通道对应的PENDING位清0,以保证之前可能存在的非正常事件通知不会对现在的使用产生干扰。



0 0
原创粉丝点击