3.2 發送事件通知
事件通道的使用包含發送方和接收方兩個使用者。結構上,事件通道的發送具有單向性,即只能由發送方通過事件通道發送事件通知,由接收方接收並處理事件通知。事件通知的發送,僅僅只是通過設置相應的標誌位通知接收方,其本身並不包含具體的信息。
在四個類型的事件通道中,發送方和接收方不盡相同(圖 34)。其中,物理中斷和虛擬中斷的發送方爲Xen,接收方爲Dom;域間通信的發送方和接收方爲不同的Dom;域內通信(虛擬IPI)的發送方和接收方爲相同的Dom。在物理中斷、虛擬中斷和域間通信中,接收方(Dom)是該事件通道的提供者。發送方通過修改事件通道對應標誌位,以便事件通道所有者(接收方)在檢查這些標誌位時就能夠獲取相應的事件通知並加以處理。
而在虛擬IPI中,事件通知的發送是在Dom內部,情況有所不同。實際上,由於事件通道用於VCPU通信,因此其發送方和接收方都是VCPU(VCPU_A和VCPU_B)。由VCPU_A發送事件通知,設置相應標誌位;再據結構體evtchn成員notify_vcpu_id的值,該事件通知由VCPU_B處理。通過這種方式達到VCPU_A和VCPU_B通信的目的。因此,可以認爲在虛擬IPI中事件通道由發送方提供。
在上述四種類型的事件通道中,域間通信和虛擬IPI的發送方爲Dom,需要通過申請超級調用(HYPERVISOR_event_channel_op)完成事件通知的發送。而虛擬中斷和物理中斷的發送方爲Xen,只需在Xen空間調用相關的發送函數即可,不必再申請超級調用。
發送事件通知操作(EVTCHNOP_send)對應的結構體參數爲結構體evtchn_send,其中輸入參數爲發送方提供的事件通道端口號(port)。
//xen\include\public\Event_channel.h 145-150
#define EVTCHNOP_send 4
struct evtchn_send {
/* 入慘 */
evtchn_port_t port;
};
typedef struct evtchn_send evtchn_send_t;
通過超級調用發送事件通知操作僅適用於域間通信和虛擬IPI。對於域間通信,發送方提供的事件端口爲本方爲建立該域間通道所提供的事件通道端口號,即本地端口號(Local Port)。通過本地端口號可以獲取遠端Dom和該域間通道相關的信息(通過本地端口號獲取本地事件通道結構,結構體中保存了遠端通訊的dom id以及port),包括Dom的ID、遠端Dom提供的事件通道結構體和端口號(遠端端口號,Remote Port)以及處理該事件通道事件通知的VCPU。發送時間通知時,只需調用evtchn_set_pending()設置與遠端事件通道相關的標誌位即可。而對於虛擬IPI,由於發送方提供的即爲目標VCPU能夠處理的事件通道端口號,因此直接設置標誌位就可完成事件通知的發送。
//xen/include/public/event_channel.c 450-506
long evtchn_send(unsigned int lport)
{
struct evtchn *lchn, *rchn;
struct domain *ld = current->domain, *rd;
struct vcpu *rvcpu;
int rport, ret = 0;
spin_lock(&ld->evtchn_lock);
if ( unlikely(!port_is_valid(ld, lport)) )
{
spin_unlock(&ld->evtchn_lock);
return -EINVAL;
}
//獲取本地端口所對應的事件通道結構
lchn = evtchn_from_port(ld, lport);
/*GOS無法通過Xen擁有的事件通道發送事件通知*/
if ( unlikely(lchn->consumer_is_xen) )
{
spin_unlock(&ld->evtchn_lock);
return -EINVAL;
}
switch ( lchn->state )
{
case ECS_INTERDOMAIN:
/*在域間通訊中,本地事件通道只是起一個獲取遠端信息的作用,根據本地事件通道的state從聯合體u中獲取遠端通訊的dom id以及port,之後便可得到遠端對應的事件通道結構,獲取遠端事件通道所綁定的vcpu(默認vcpu 0),設置pending,通知有事件到達*/
rd = lchn->u.interdomain.remote_dom;
rport = lchn->u.interdomain.remote_port;
rchn = evtchn_from_port(rd, rport);
rvcpu = rd->vcpu[rchn->notify_vcpu_id];
if ( rchn->consumer_is_xen )
{
/*Xen*/
if ( test_and_clear_bit(_VPF_blocked_in_xen,
&rvcpu->pause_flags) )
vcpu_wake(rvcpu);
}
else
{
evtchn_set_pending(rvcpu, rport);
}
break;
case ECS_IPI:
evtchn_set_pending(ld->vcpu[lchn->notify_vcpu_id], lport);
break;
case ECS_UNBOUND:
/* silently drop the notification */
break;
default:
ret = -EINVAL;
}
spin_unlock(&ld->evtchn_lock);
return ret;
}
3.3 屏蔽事件通道
屏蔽事件通道有兩種方式:設置對應MASK標誌位屏蔽一個特定的事件通道;設置與指定VCPU對應的evtchn_upcall_mask使得該VCPU屏蔽來自任何事件通道的事件通知。不論是MASK標誌位還是evtchn_upcall_mask,都只能有Guest OS進行更新,Xen只擁有訪問權限。
3.3.1 屏蔽單個事件通道
通過設置對應MASK標誌位可以使綁定VCPU屏蔽該事件通道。GOS屏蔽單個事件通知只需要將共享信息頁(shared_info)成員evtchn_mask中對應的位置1即可。
//linux-2.6-xen-sparse/drivers/xen/core/evtchn.c 859-864
void mask_evtchn(int port)
{
shared_info_t *s = HYPERVISOR_shared_info;
synch_set_bit(port, s->evtchn_mask);
}
EXPORT_SYMBOL_GPL(mask_evtchn);
在MASK位置1後,若該事件通道產生事件通知,系統並不會直接丟棄該事件通知,而是先要檢查PENDING位。若對應PENDING位爲0,則將其置1,然後纔會檢查MASK位選擇是否丟棄該事件通道。也就是說,在屏蔽該事件通道後,事件通道也可能包含未處理的事件通知。因此,在執行取消屏蔽(Unmask)操作後,還需要檢查是否有這樣的事件通知存在。若存在,則需要設置evtchn_upcall_pending和evtchn_pending_sel通知VCPU進行處理。
//linux-2.6-xen-sparse/drivers/xen/core/evtchn.c 866-889
void unmask_evtchn(int port)
{
shared_info_t *s = HYPERVISOR_shared_info;
//獲取dom中執行線程的vcpu id
unsigned int cpu = smp_processor_id();
vcpu_info_t *vcpu_info = &s->vcpu_info[cpu];
BUG_ON(!irqs_disabled());
/* 與其它vcpu綁定的事件通道只能通過超級調用設置 */
if (unlikely(cpu != cpu_from_evtchn(port))) {
struct evtchn_unmask unmask = { .port = port };
(void)HYPERVISOR_event_channel_op(EVTCHNOP_unmask, &unmask);
return;
}
//與當前VCPU自身綁定的事件通道MASK清理可以直接進行設置
synch_clear_bit(port, s->evtchn_mask);
/* 檢查是否含有未處理的事件通知 */
if (synch_test_bit(port, s->evtchn_pending) &&
!synch_test_and_set_bit(port / BITS_PER_LONG,
&vcpu_info->evtchn_pending_sel))
vcpu_info->evtchn_upcall_pending = 1;
}
EXPORT_SYMBOL_GPL(unmask_evtchn);
與屏蔽操作不同,當前VCPU只能夠對直接綁定在其上的事件通道執行取消屏蔽操作,而對於綁定到其它VCPU的事件通道,則需要通過超級調用HYPERVISOR_event_channel_op執行,即操作碼EVTCHNOP_unmask。
取消屏蔽操作(EVTCHNOP_unmask)對應的結構體參數爲結構體evtchn_unmask,其中輸入參數爲事件通道端口號(port)。
//xen\include\public\Event_channel.h 212-217
#define EVTCHNOP_unmask 9
struct evtchn_unmask {
/* IN parameters. */
evtchn_port_t port;
};
typedef struct evtchn_unmask evtchn_unmask_t;
該操作核心處理程序爲evtchn_unmask()。函數在將MASK位清零後,仍然需要檢查PENDING位。若存在未處理的事件通知,則調用函數vcpu_mark_events_pending()設置綁定VCPU的相應標誌位,並喚醒該VCPU。
//xen/common/event_channel.c 711-743
static long evtchn_unmask(evtchn_unmask_t *unmask)
{
struct domain *d = current->domain;
shared_info_t *s = d->shared_info;
int port = unmask->port;
struct vcpu *v;
spin_lock(&d->evtchn_lock);
if ( unlikely(!port_is_valid(d, port)) )
{
spin_unlock(&d->evtchn_lock);
return -EINVAL;
}
//獲得綁定了該事件通道的VCPU
v = d->vcpu[evtchn_from_port(d, port)->notify_vcpu_id];
//檢查是否設置了MASK位,pengding位以及之前是否有設定事件選擇器
if ( test_and_clear_bit(port, __shared_info_addr(d, s, evtchn_mask)) &&
test_bit (port, __shared_info_addr(d, s, evtchn_pending)) &&
!test_and_set_bit (port / BITS_PER_GUEST_LONG(d),
vcpu_info_addr(v, evtchn_pending_sel)) )
{
/*設置VCPU標誌位evtchn_upcall_pending並喚醒該VCPU*/
vcpu_mark_events_pending(v);
}
spin_unlock(&d->evtchn_lock);
return 0;
}
由於設置MASK位後,事件通道仍然可能產生事件通道並將PENDING位置1,因此在事件通道屏蔽期間,系統仍然可以採用輪詢(Poll)的方式檢查其對應PENDING位並處理存在的未處理事件通知。該檢查工作一般在當前正在執行的事件通知處理程序(Ecent Handler)將要退出時進行。若檢查存在未處理的事件通知,則處理程序將繼續執行。
此外,輪詢方式也可應用在比較繁忙的事件通道中。事件通道產生事件通知,Xen通過upcall將其發送到綁定的VCPU進行處理。考慮到upcall機制的開銷,採用輪詢方式可以大大減少upcall的使用次數,即屏蔽該事件通道,通過輪詢獲取未處理的事件通知並進行處理。
3.3.2屏蔽所有事件通道
通過設置VCPU對應的evtchn_upcall_mask可以屏蔽與該VCPU綁定的事件通道。
//xen/include/asm-X86/event.h 52-60
static inline void local_event_delivery_disable(void)
{
current->vcpu_info->evtchn_upcall_mask = 1;
}
static inline void local_event_delivery_enable(void)
{
current->vcpu_info->evtchn_upcall_mask = 0;
}
3.4 獲取事件通道狀態
在事件通道的使用過程中,可能需要查詢事件通道的狀態。儘管在事件通道結構體evtchn中有成員state保存當前事件通道的狀態,但是GOS無法訪問該結構體,只能通過超級調用獲取事件通道的狀態,
獲取事件通道狀態操作(EVTCHNOP_status)對應的結構體參數爲結構體evtchn_status,其中輸入參數包括事件通道所屬Dom的ID(dom)和端口號(port);輸出參數包括事件通道狀態(status)、綁定的VCPU(vcpu)以及保存相關信息的成員共同體(u)。
//xen/include/public/event_channel.h 161-186
struct evtchn_status {
/* 入參 */
domid_t dom;
evtchn_port_t port;
/* 出參 */
#define EVTCHNSTAT_closed 0 /* 該通道未被使用 */
#define EVTCHNSTAT_unbound 1 /* 該通道正等待域間連接 */
#define EVTCHNSTAT_interdomain 2 /* 該通道已經與遠端域相連 */
#define EVTCHNSTAT_pirq 3 /* 該通道已經綁定物理中斷 */
#define EVTCHNSTAT_virq 4 /* 該通道已經綁定虛擬中斷 */
#define EVTCHNSTAT_ipi 5 /* 該通道綁定虛擬IPI */
uint32_t status;
uint32_t vcpu; /* 事件通道綁定的VCPU. */
union {
struct {
domid_t dom;
} unbound; /* 事件通道未被綁定 */
struct {
domid_t dom;
evtchn_port_t port;
} interdomain; /* 域間事件通道 */
uint32_t pirq; /* 物理中斷事件通道 */
uint32_t virq; /* 虛擬中斷事件通道 */
} u;
};
typedef struct evtchn_status evtchn_status_t;
根據事件通道結構體evtchn中定義的7種事件通道類型,操作EVTCHNOP_status可能返回6種狀體。當事件通道類型爲ECS_FREE和ECS_RESERVED時,操作返回同一種狀態,即關閉狀態,表示該事件通道可能被保留或等待分配。事件通道類型和返回狀態之間的對應關係如表 32所示。
事件通道類型(state) |
返回狀態(Status) |
說明 |
ECS_FREE |
EVTCHNSTAT_closed |
通道關閉(未使用) |
ECS_RESERVED |
EVTCHNSTAT_closed |
通道關閉(保留) |
ECS_UNBOUND |
EVTCHNSTAT_unbound |
未綁定 |
ECS_INTERDOMAIN |
EVTCHNSTAT_interdomain |
域間通信 |
ECS_PIRQ |
EVTCHNSTAT_pirq |
物理中斷 |
ECS_VIRQ |
EVTCHNSTAT_virq |
虛擬中斷 |
ECS_IPI |
EVTCHNSTAT_ipi |
虛擬處理器間中斷 |
操作核心處理函數evtchn_status()根據事件通道的類型返回不同的狀態值,並將相關信息保存到共同體u中。
//xen/common/event_channel.c 596-658
static long evtchn_status(evtchn_status_t *status)
{
struct domain *d;
domid_t dom = status->dom;
int port = status->port;
struct evtchn *chn;
long rc = 0;
/*特權域Dom0可以查詢其它Dom事件通道狀態*/
if ( dom == DOMID_SELF )
dom = current->domain->domain_id;
else if ( !IS_PRIV(current->domain) )
return -EPERM;
if ( (d = rcu_lock_domain_by_id(dom)) == NULL )
return -ESRCH;
spin_lock(&d->evtchn_lock);
if ( !port_is_valid(d, port) )
{
rc = -EINVAL;
goto out;
}
chn = evtchn_from_port(d, port);
/*返回狀態值,保存相關信息*/
switch ( chn->state )
{
case ECS_FREE:
case ECS_RESERVED:
status->status = EVTCHNSTAT_closed;
break;
case ECS_UNBOUND:
status->status = EVTCHNSTAT_unbound;
status->u.unbound.dom = chn->u.unbound.remote_domid;
break;
case ECS_INTERDOMAIN:
status->status = EVTCHNSTAT_interdomain;
status->u.interdomain.dom =
chn->u.interdomain.remote_dom->domain_id;
status->u.interdomain.port = chn->u.interdomain.remote_port;
break;
case ECS_PIRQ:
status->status = EVTCHNSTAT_pirq;
status->u.pirq = chn->u.pirq;
break;
case ECS_VIRQ:
status->status = EVTCHNSTAT_virq;
status->u.virq = chn->u.virq;
break;
case ECS_IPI:
status->status = EVTCHNSTAT_ipi;
break;
default:
BUG();
}
status->vcpu = chn->notify_vcpu_id;
out:
spin_unlock(&d->evtchn_lock);
rcu_unlock_domain(d);
return rc;
}
查詢操作不僅用於查詢自身Dom擁有的事件通道的狀態,也可以查詢其它Dom的事件通道狀態。當然,只有特權域Dom0有權查詢其它Dom的事件通道狀態,而非特權域只能夠查詢本身的事件通道,即dom的值爲DOMID_SELF。
3.5 關閉和重置事件通道
在超級調用HYPERVISOR_event_channel_op剩下的兩個操作中,EVTCHNOP_close用於關閉事件通道,EVTCHNOP_reset則是重置事件通道。這兩個操作本質上類似,都是將事件通道的狀態(state)設置爲“未使用”,即ECS_FREE。所不同的是,事件通道的關閉操作是關閉特定的事件通道,而重置操作則是關閉整個Dom中所有的有效事件通道。
3.5.1 關閉事件通道
事件通道關閉操作(EVTCHNOP_close)對應的結構體參數爲結構體evtchn_close,其中輸入參數爲需要關閉的事件通道端口號(port)。
//xen/include/public/event_channel.h 134-139
#define EVTCHNOP_close 3
struct evtchn_close {
/* 入慘 */
evtchn_port_t port;
};
typedef struct evtchn_close evtchn_close_t;
通過該操作,Dom只能夠關閉自己的事件通道,即在事件通道對應的(dom,port)中,dom的值爲DOMID_SELF。在操作的核心函數__evtchn_close()中,函數根據不同的事件通道類型採取不同的處理方法。關閉用於物理中斷的事件通道,需要在完成取消物理中斷綁定之後清除物理中斷對應數組pirq_to_evtchn中與該物理中斷對應的值。關閉用於虛擬中斷的事件通道,只需要清除虛擬中斷對應數組virq_to_evtchn中對應的值。關閉用於虛擬IPI的事件通道,僅需要清除結構體evtchn成員notify_vcpu_id。關閉用於域間通信的事件通道相對要複雜些,因爲關閉操作需要處理創建域間通道的另一個事件通道。在關閉本地事件通道後,遠端事件通道將被設置爲未綁定(ECS_UNBOUND)狀態,等待下一次域間通道的建立。
//xen/include/public/event_channel.c 444-447
static long evtchn_close(evtchn_close_t *close)
{
/*只能關閉自己的事件通道*/
return __evtchn_close(current->domain, close->port);
}
//xen/include/public/event_channel.c 320-441
static long __evtchn_close(struct domain *d1, int port1)
{
struct domain *d2 = NULL;
struct vcpu *v;
struct evtchn *chn1, *chn2;
int port2;
long rc = 0;
again:
spin_lock(&d1->evtchn_lock);
if ( !port_is_valid(d1, port1) )
{
rc = -EINVAL;
goto out;
}
//獲得本地事件通道結構
chn1 = evtchn_from_port(d1, port1);
/*無法關閉Xen所有的事件通道*/
if ( unlikely(chn1->consumer_is_xen) )
{
rc = -EINVAL;
goto out;
}
switch ( chn1->state )
{
case ECS_FREE:
case ECS_RESERVED:
rc = -EINVAL;
goto out;
case ECS_UNBOUND:
break;
case ECS_PIRQ:
//先要取消物理中斷綁定
if ( (rc = pirq_guest_unbind(d1, chn1->u.pirq)) == 0 )
d1->pirq_to_evtchn[chn1->u.pirq] = 0;
break;
case ECS_VIRQ:
for_each_vcpu ( d1, v )
if ( v->virq_to_evtchn[chn1->u.virq] == port1 )
v->virq_to_evtchn[chn1->u.virq] = 0;
break;
case ECS_IPI:
break;
case ECS_INTERDOMAIN:
if ( d2 == NULL )
{
d2 = chn1->u.interdomain.remote_dom;
if ( unlikely(!get_domain(d2)) )
{
d2 = NULL;
goto out;
}
if ( d1 < d2 )
{
spin_lock(&d2->evtchn_lock);
}
else if ( d1 != d2 )
{
spin_unlock(&d1->evtchn_lock);
spin_lock(&d2->evtchn_lock);
goto again;
}
}
else if ( d2 != chn1->u.interdomain.remote_dom )
{
BUG_ON(d1 != current->domain);
rc = -EINVAL;
goto out;
}
/*設置另一個事件通道爲未綁定狀態*/
port2 = chn1->u.interdomain.remote_port;
BUG_ON(!port_is_valid(d2, port2));
chn2 = evtchn_from_port(d2, port2);
BUG_ON(chn2->state != ECS_INTERDOMAIN);
BUG_ON(chn2->u.interdomain.remote_dom != d1);
chn2->state = ECS_UNBOUND;
chn2->u.unbound.remote_domid = d1->domain_id;
break;
default:
BUG();
}
/*設置事件通道狀態爲ECS_FREE */
chn1->state = ECS_FREE;
chn1->notify_vcpu_id = 0;
out:
if ( d2 != NULL )
{
if ( d1 != d2 )
spin_unlock(&d2->evtchn_lock);
put_domain(d2);
}
spin_unlock(&d1->evtchn_lock);
return rc;
}
3.5.2 重置事件通道
事件通道的重置操作只與Dom相關,即操作將關閉該Dom所有有效的事件通道。事件通道重置操作(EVTCHNOP_reset)對應的結構體參數爲結構體evtchn_reset,其中輸入參數爲需要關閉事件通道的Dom(dom)。
//xen/include/public/event_channel.h 225-230
#define EVTCHNOP_reset 10
struct evtchn_reset {
/* 入參 */
domid_t dom;
};
typedef struct evtchn_reset evtchn_reset_t;
特權域Dom0可以通過操作重置其它Dom事件通道,而非特權域只能操作自己的事件通道,即dom的值爲DOMID_SELF。在覈心處理函數evtchn_reset()中,重置操作實際是通過循環調用事件通道關閉操作__evtchn_close()完成。
//xen/include/public/event_channel.c 744-766
static long evtchn_reset(evtchn_reset_t *r)
{
domid_t dom = r->dom;
struct domain *d;
int i;
/*特權域Dom0可以重置其它Dom事件通道*/
if ( dom == DOMID_SELF )
dom = current->domain->domain_id;
else if ( !IS_PRIV(current->domain) )
return -EPERM;
if ( (d = rcu_lock_domain_by_id(dom)) == NULL )
return -ESRCH;
for ( i = 0; port_is_valid(d, i); i++ )
(void)__evtchn_close(d, i);
rcu_unlock_domain(d);
return 0;
}