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()完成具體的物理中斷綁定操作。只有函數成功返回,物理中斷的綁定操作纔算完成。