KVM中斷注入機制

前言

  • X86平臺內核對QEMU下發的中斷處理大致分三部分:查路由表,遞交IO到中斷控制器直至LAPIC,寄存器注入。第一部分路由中斷在前一章已經介紹,中斷向量的傳遞涉及到8259中斷控制器的模擬,IOAPIC中斷控制器模擬和LAPIC控制器的模擬,本文首先根據控制器手冊分析其工作原理,然後介紹其軟件模擬的實現,和中斷流程的模擬

中斷信號處理路徑

  • X86平臺處理中斷信號分爲四個階段:
  1. 根據中斷的gsi號查詢中斷路由表,找到對應的表項,取出handler,投遞中斷到IOAPIC
  2. IOAPIC根據輸入的中斷引腳索引查詢重定向表,取出中斷信號的目的cpu id和vector,投遞中斷到LAPIC
  3. LAPIC判斷中斷類型和向量號,將其寫入vcpu中的IRR(Interrupt Request Register)寄存器
  4. 下一次VM-Entry進入時,檢查IRR寄存器是否有中斷請求,如果有,將IRR對應的bit轉化爲vector,寫入vmcs區域的VM-Entry interrupt information區域
    在這裏插入圖片描述

IOAPIC遞交中斷

IOAPIC簡介

物理位置

  • IOAPIC屬於外設,不在CPU核內,接收來自ISA設備或者其它pci設備的中斷信號,通過它和LAPIC模塊之間專用的APIC bus將中斷信息傳遞給LAPIC。
    在這裏插入圖片描述

原理簡介

  • IOAPIC有24箇中斷信號輸入引腳,因此可以處理24箇中斷信號並將其重定向到目標CPU,對每個引腳的輸入信號,IOAPIC都有一個重定向的寄存器與之對應,這個寄存器是可寫的,軟件可編程,重定向寄存器決定了其對應引腳的中斷可以發到哪個CPU。24個重定向寄存器組成的就是所爲的IOAPIC重定向表。格式如下
    在這裏插入圖片描述

KVM模擬IOAPIC

在這裏插入圖片描述

中斷流程處理

  • IOAPIC的中斷流程處理,開始於kvm_set_ioapic_irq,整個流程如下
kvm_set_ioapic_irq
	kvm_ioapic_set_irq
		ioapic_set_irq
			ioapic_service
				kvm_irq_delivery_to_apic
					kvm_apic_set_irq
  • 中斷信息最終要傳遞lapic,這中間就是ioapic的處理流程,其中主要的工作在ioapic_service完成
ioapic_service
	kvm_ioapic_redirect_entry *entry = &ioapic->redirtbl[irq]
	irqe.dest_id = entry->fields.dest_id;
    irqe.vector = entry->fields.vector;
	kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe, NULL)
		kvm_for_each_vcpu(i, vcpu, kvm) {
        	if (!kvm_apic_match_dest(vcpu, src, irq->shorthand,  irq->dest_id, irq->dest_mode))
            	continue;     
            if (!kvm_lowest_prio_delivery(irq)) {
            	if (r < 0)
                	r = 0;
            	r += kvm_apic_set_irq(vcpu, irq, dest_map)
            		__apic_accept_irq

首先將中斷向量對應的重定向表項取出,重要的兩個數據是目的cpu的id和向量號。然後調用繼續傳遞中斷信息,kvm_irq_delivery_to_apic中遍歷每個vcpu,查看其apic id和重定向表中取出的id是否一樣,如果一樣,調用kvm_apic_set_irq繼續傳遞中斷信息,走到__apic_accept_irq,就是lapic負責的流程了

LAPIC處理

LAPIC簡介

物理位置

  • LAPIC位於CPU核心,不屬於外設,它接收IOAPIC經Host橋發送來的中斷信息,對其進行處理。物理位置如下,截圖自Intel手冊vol3-10.1
    在這裏插入圖片描述

工作流程

  • IOAPIC發送的中斷消息包含兩個關鍵信息:中斷要發到哪個CPU(APIC ID),中斷向量是多少(VECTOR)。兩個信息在IOAPIC中斷重定向表項中可以設置,其低8位[0-7]存放中斷向量(0-255),[56-63]存放APIC ID(0-255),截圖自820933AA IOAPIC datasheet第3章
    在這裏插入圖片描述
    在這裏插入圖片描述
  • LAPIC接收中斷信息後的處理流程如下,截圖自Intel手冊vol3-10.8.1
    在這裏插入圖片描述
  1. 解析中斷信息中的APIC ID字段,和本身的ID(Local APIC ID Register)比較,確認自己關聯的CPU是否爲中斷的目的CPU
  2. 如果確認中斷目的地是本CPU,解析該中斷信息的類型,NMI,SMI,INIT,ExtINT,SIPI這5類中斷不需要經APIC處理,直接deliver到CPU
  3. 如果確認中斷目的地是本CPU,中斷類型不是以上5類,是固定向量的中斷,取出其Vector的值,將其在IRR(Interrupt Request Register)上對應的bit設置爲1,請求CPU進行處理。IRR共256bit(32字節),每1位對應一箇中斷向量。IRR中包含的是LAPIC已接受,但還沒有被CPU處理的中斷
  4. LAPIC檢查當前IRR寄存器中優先級最高的中斷,如果該中斷對應的ISR爲0,表示處理器空閒,該向量的上一次中斷已經被CPU處理,LAPIC將IRR對應bit清0,ISR對應bit置1。CPU檢查到ISR置1後就處理該中斷(CPU只認ISR)
  5. CPU執行中斷處理例程(Interrupt Service Routine)完成後,會寫EOI(end-of-interrupt)Register標記當前中斷處理已,LAPIC檢查到EOI標記後將ISR對應bit清0,此時IRR和ISR都爲0,LAPIC再接收到IOAPIC發來的中斷,重複之前的步驟,設置IRR,清0 IRR並設置ISR,清0 ISR

優先級

  • 中斷向量被寫入IRR後,LAPIC還會對中斷優先級進行判斷,當中斷優先級滿足條件時,LAPIC纔將其寫入ISR並調度CPU進行中斷處理。中斷向量號是判斷中斷優先級的基礎,Intel將中斷向量的優先級分級,分別是1-15,向量號的高4位[7:4]表示中斷處於哪一級,每一級有16箇中斷向量,向量的級數越高優先級越高,同級內向量號越大優先級越高。
  • TPR(Task-Priority Register),控制當前CPU需要處理的中斷向量的閾值,當向量的優先級小於該TPR指定的優先級,當前CPU不會對中斷進行處理。
  • PPR(Processor-Priority Register),指示當前CPU正在處理的中斷向量的優先級,被LAPIC用來比較新接收的中斷向量和CPU正在處理的中斷向量的優先級,當新接收的中斷向量優先級高於當前正在處理的中斷向量優先級,LAPIC會立即調度CPU執行中斷處理程序,可以實現中斷的嵌套調用
  • 假設操作系統設置CPU 0 的TPR寄存器的值爲51,下面是CPU 0的LAPIC處理中斷的分析
    在這裏插入圖片描述
  1. IOAPIC發送25號中斷到CPU 0,LAPIC接收中斷信息後首先判斷自己是否是中斷的目的CPU,確認本CPU是中斷信號的目的CPU後,檢查中斷類型是固定向量號的中斷,將其發到IRR中
  2. 檢查當前CPU的PPR,由於沒有正在處理的中斷,PPR就是用戶設置的TPR,對比IRR中存儲的中斷向量優先級,PPR指示CPU處理優先級的閾值是3級,中斷向量優先級屬於2級,因此LAPIC判斷中斷向量優先級低於處理器能夠處理的中斷的閾值,25號中斷向量不會被處理
  3. IOAPIC發送129號中斷到CPU 0,LAPIC檢查PPR閾值爲3級,中斷向量優先級爲8級,高於PPR,129號中斷會被處理,LAPIC將129號向量在ISR中對應位置1,ISRV表示在ISR中優先級最高的向量,即當前正被CPU處理的最高優先級的中斷向量,此時ISRV爲129。PPR爲128,PPR的取值來自ISRV或TPR,計算方法可以查看Intel手冊vol3-10.8.3.1
  4. 在129號向量被CPU處理過程中,IOAPIC發來了156號向量,LAPIC處理後寫入IRR,比較中斷向量和PPR的優先級,新來中斷比正在處理的中斷優先級要高,需要優先被處理,CPU放下當前正在處理的中斷,跳轉到新中斷(156)對應的處理例程執行中斷處理,完成之後,CPU再回去執行(129)之前沒有處理完的中斷例程

寄存器

  • LAPIC處理中斷的過程中涉及到具體的寄存器,下面一一介紹
  1. LAPIC接收到中斷信息後,首先取出其目的字段的APIC ID,將其於LAPIC 中Local APIC ID Regsiter中記錄的ID值比較,如果相同,表示中斷信號的目的是本CPU,下面是Local APIC ID Regsiter的格式介紹,不同的CPU型號其格式不同,P6和Pentium處理器APIC ID佔4bit,最多表示16個CPU,因此P6能支持的最大CPU個數也就16個,Xeon服務器CPU的APIC ID佔8bit,最多表示256個CPU,因此Xeon最大能支持256個CPU
    Screenshot at 2019-12-08 15-22-40
  2. LAPIC接收中斷信號後,將其放入IRR中請求CPU處理,LAPIC判斷中斷向量的優先級達到條件並且ISR對應位爲0,沒有正在被CPU處理的該向量的中斷,將ISR置1,並清0 IRR,CPU開始處理該中斷,每個中斷可以有邊沿觸發和水平觸發兩種方式,TMR用來指示當前申請的中斷是哪種觸發方式。下圖就是這三個寄存器,每個寄存器的寬度都是256bit,可以表示256箇中斷向量
    在這裏插入圖片描述
  3. CPU中斷服務例程執行完後,向EOI寄存器中寫入值表示中斷處理完成,可以繼續處理下一個中斷,這裏有一個EOI寄存器,其長度4字節32bit
  4. LAPIC在設置ISR前,先讀取PPR的值,將其優先級和中斷向量的優先級比較,當中斷向量優先級高於PPR優先級,設置ISR,設置ISR之後,LAPIC會更新PPR,它的值來源有兩個,一個是ISRV(ISR寄存器中中斷優先級最高的向量),一個是TPR(用戶軟件設置的CPU處理的中斷閾值),PPR的高4位去ISRV和TPR中的大值,低4位爲0或取自TPR的低4位。算法和涉及的寄存器如下
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

KVM模擬LAPIC

  • Intel平臺每個cpu都包含一個lapic,KVM按照相同的情況進行模擬,其數據結構如下
    在這裏插入圖片描述
    從上面看出,一個通用cpu是所有平臺cpu實現的共性抽象,各平臺的cpu具體實現由arch成員指向,x86平臺上,cpu上包含各種寄存器,也包括lapic模塊,lapic的regs成員存放lapic的所有寄存器的值

中斷處理流程

Make Request

  • KVM LAPIC處理中斷信號的流程開始於__apic_accept_irq,該函數收到來自其它CPU或IOAPIC發來的中斷向量。我們討論普通的情況,收到來自IOAPIC的中斷
static int __apic_accept_irq(struct kvm_lapic *apic, int delivery_mode,
                 int vector, int level, int trig_mode,
                 struct dest_map *dest_map)
	switch (delivery_mode) 
	case APIC_DM_FIXED:
		if (apic_test_vector(vector, apic->regs + APIC_TMR) != !!trig_mode) {
            if (trig_mode)
                kvm_lapic_set_vector(vector, apic->regs + APIC_TMR);
            else
                apic_clear_vector(vector, apic->regs + APIC_TMR);
        }

        if (vcpu->arch.apicv_active)
            kvm_x86_ops->deliver_posted_interrupt(vcpu, vector);
        else {
            kvm_lapic_set_irr(vector, apic);

            kvm_make_request(KVM_REQ_EVENT, vcpu);
            kvm_vcpu_kick(vcpu);
        }
  • lapic遞交中斷信號的主要動作就是往它模擬的lapic的寄存器地址上,IRR對應的地方寫1,之後標記vcpu上的request的對應bit,這裏是event。注意,除了這種方式,如果硬件支持posted-interrupt方式deliver中斷,需要優先考慮使用。lapic將內存中IRR上中斷向量對應bit置1後,make request就完成了。之後就是喚醒vcpu或者將其從guest態踢出,讓其再次進入guest態前能夠處理中斷,這個動作被稱爲Kick
    在這裏插入圖片描述

Kick vCPU

  • 被Kick的vCPU進程可能處於以下兩種狀態,Kick要分別進行處理:
  1. vCPU進程在睡眠狀態,Kick函數需要將它喚醒,投入到vCPU所在物理CPU的運行隊列中,等待調度,當vCPU被調度運行後進入guest態,響應注入的中斷
  2. vCPU進程在運行狀態,正在guest態運行,Kick函數發送核間中斷觸發VM-Exit,使vCPU退出guest態,同樣被投入到運行隊列中,等待調度,後面的流程同上
  • 第二種情況需要利用LAPIC發送核間中斷,這是LAPIC的另一個功能,這個功能主要是向其它cpu或自己發送中斷,這種中斷稱爲核間中斷——IPI(Interprocessor Interrupt)
  • 接下來介紹核間中斷的工作方式,然後是如何Kick一個睡眠的vCPU進程,最後是如何Kick一個正在guest態運行的vCPU進程

核間中斷

  • 核間中斷的發起最重要的一個寄存器叫ICR(interrupt command register),軟件按照寄存器的使用規則往該寄存器中寫信息,就可以發出IPI,ICR格式如下,截圖來自Intel手冊vol3-10.6
    在這裏插入圖片描述
    ICR中有幾個重要的字段如下:
  1. Vector,IPI要發送的中斷向量號
  2. Delivery Mode,描述發送中斷的類型,以下中斷類型可供選擇
    000 (Fixed):固定向量號的中斷,這種就是普通的中斷
    001 (Lowest Priority):固定向量號的中斷,和Fixed一樣,但這種中斷被硬件控制到優先級最低
    010 (SMI):System Managment Interrupt,發送系統管理中斷,這種中斷要求Vector字段必須位0
    100 (NMI):None-Maskable Interrupt,不可屏蔽中斷,不可屏蔽中斷的向量號是2,因此這裏的Vector會被忽略
    110 (Start Up):“Start-up” IPI,一種特殊的核間中斷。該中斷對應的ISR在BIOS啓動的時候就被創建好了,這種投遞方式硬件不保證信號準確送達到目的CPU,失敗後不會重試,需要靠軟件保證,kick vcpu通過這種方式實現
  3. Destination:存放中斷要發往目的CPU或者CPU組,共8bit可以發往所有256個處理器
  • 當ICR內容準備好之後,往其低16個字節寫入內容,就會觸發發送IPI的動作。

Kick Sleep vCPU

/*          
 * Kick a sleeping VCPU, or a guest VCPU in guest mode, into host kernel mode.
 */   
void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
{     
    int me;
    int cpu = vcpu->cpu;
    /* vcpu線程處於睡眠狀態,喚醒 */
    if (kvm_vcpu_wake_up(vcpu))
        return;
    /* 獲取當前進程所在cpu的id,啓動階段由操作系統從lapic 的ID寄存器中讀取 */ 
    me = get_cpu();
    /* 目標cpu不是當前進程所在cpu並且在線,滿足條件後執行kick動作 */
    if (cpu != me && (unsigned)cpu < nr_cpu_ids && cpu_online(cpu))
        if (kvm_arch_vcpu_should_kick(vcpu))
            smp_send_reschedule(cpu);
    put_cpu();
}
  • kvm_vcpu_kick的核心功能就是讓睡眠的,或者處在Guest態的vCPU進程,重新投入到運行隊列中等待調度,如果目標vCPU在睡眠,那麼它必然處在內核態,因此將其喚醒之後直接返回,喚醒函數的主要流程如下:
bool kvm_vcpu_wake_up(struct kvm_vcpu *vcpu)
{
	......
    wqp = kvm_arch_vcpu_wq(vcpu);			/* 1 */
 	swake_up(wqp);							/* 2 */
	......
}
1. 獲取當前vcpu的等待隊列
2. 如果等待隊列中有正在睡眠的進程,將隊列中的第一個task喚醒
  • 喚醒一個進程的實質就是將它的狀態設置爲TASK_RUNNING並投入到運行隊列中,等待調度器的調度。swake_up最終調用到wake_up_process函數,如下:
int wake_up_process(struct task_struct *p)
{   
    return try_to_wake_up(p, TASK_NORMAL, 0);	/* 3 */
}
3. 將處於TASK_NORMAL狀態的進程喚醒,TASK_NORMAL狀態包括了可中斷睡眠和不可中斷睡眠
  • 分析try_to_wake_up的實現:
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
	......
	cpu = task_cpu(p);													/* 4 */
	cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);	/* 5 */
	if (task_cpu(p) != cpu) {											/* 6 */
        wake_flags |= WF_MIGRATED;
        set_task_cpu(p, cpu);
    }
    ......
4. 獲取進程上一次運行所在的cpu id
5. 選擇進程要投入到哪個cpu上的運行隊列,這裏會調用具體的調度器select_task_rq方法進行選擇,完成後,進程在運行隊列中等待調度
6. 將上一次進程運行的cpu與這一次選擇的cpu做比較,不同表示進程發生了遷移,標記以下,最後更新進程運行的cpu
  • 到這裏,原來處於睡眠中的vCPU進程就被放到了運行隊列中,接下來就是等待調度時機的出現,將vCPU投入運行了,整個Kick過程結束

Kick in-guest vCPU

  • 除了Kick睡眠的vCPU,還有一種情況是Kick運行的vCPU。整個過程由smp_send_reschedule函數實現,其中會涉及到核間中斷的發送,所以是體系結構相關的,每個體系結構定義了一套多核操作,X86上smp_send_reschedulenative_smp_send_reschedule函數,如下:
static inline void smp_send_reschedule(int cpu)
{
    smp_ops.smp_send_reschedule(cpu);
}

struct smp_ops smp_ops = {
    ......
    .smp_send_reschedule    = native_smp_send_reschedule,
    ......
};
  • 看名字,這個操作是通用接口,讓目標物理CPU重新調度進程,爲什麼這個接口就能實現Kick guest vCPU的功能?看看裏面的具體實現:
/*
 * this function sends a 'reschedule' IPI to another CPU.			
 * it goes straight through and wastes no time serializing
 * anything. Worst case is that we lose a reschedule ...
 */
static void native_smp_send_reschedule(int cpu)
{
	......
    apic->send_IPI_mask(cpumask_of(cpu), RESCHEDULE_VECTOR);		/* 1 */
    ......
}
註釋:該函數向其它CPU發送一個'重調度'的核間中斷,這個中斷不保證串行化,就是說,如果在這個中斷到達目標CPU之前,已經有一箇中斷存在,這個中斷可能會丟失
1. 調用本地APIC接口,發送'重調度'核間中斷,它的向量號是RESCHEDULE_VECTOR(253)。說明,核間中斷內核和IOAPIC發送的中斷內容一樣,有兩個基本的組成:目標CPU的ID,中斷向量號。當這個IPI成功發送到目標CPU之後,硬件會跳轉到中斷向量表,轉入中斷處理流程
  • 本地APIC接口是一個靜態的全局變量,它是一組APIC操作的驅動,系統在上電時根據平臺不同進行初始化,操作中定義了發送核間中斷的操作接口,如下:
struct apic {
	......
    /* ipi */
    void (*send_IPI)(int cpu, int vector);
    void (*send_IPI_mask)(const struct cpumask *mask, int vector);
	......
}
  • 一個具體的APIC操作驅動實現如下,我們選取NumaConnect system舉例, numachip_send_IPI_mask最終操作具體的LAPIC硬件寄存器ICR,完成核間中斷的發送,之後就是目標CPU上的中斷處理了
static const struct apic apic_numachip __refconst = {
    .name               = "NumaConnect system",
	.send_IPI_mask          = numachip_send_IPI_mask,
	......
}
  • 內核中針對APIC核間中斷的中斷向量表定義如下:
/*
 * The APIC and SMP idt entries
 */
static const __initconst struct idt_data apic_idts[] = {
	......
    INTG(RESCHEDULE_VECTOR,     reschedule_interrupt),		/* 2 */
    INTG(CALL_FUNCTION_VECTOR,  call_function_interrupt),
	......
};
2. 重調度中斷向量爲RESCHEDULE_VECTOR,中斷處理函數是smp_reschedule_interrupt,它的主要功能就是讓目標CPU上正在運行的進程調度出去,然後讓調度器重新選擇新進程投入運行
  • RESCHEDULE_VECTOR核間中斷實際上被很多內核模塊調用,而Kick函數之所以也用這個接口,是看中了它的兩個功能,一是它發送了一條中斷信息,二是這個中斷的處理流程是讓CPU上的調度器重新調度,首先第一個功能,就能讓guest態的CPU退出,VMCS中的Pin-based VM-execution control字段的最低位可以設置外部中斷請求到達時是否觸發VM-exit,這個字段被稱爲external-interrupt exiting。通常,這個字段會被設置爲1。參考Intel手冊volume 3 24.6.1部分。有了這個硬件機制,就能讓guest CPU退出。再說重調度的第二個功能,它會讓目標CPU上的調度器從運行隊列中重新選擇進程,投入運行,這個進程,可能還是之前剛退出的vCPU進程,也可能是其它進程,這都不重要,我們的目的是讓vCPU進程退出guest後再進入,從而發現被注入的中斷。
  • 最後給出’重調度’中斷處理例程,這裏涉及內核調度器的機制,有空繼續分析
  • TODO
/*
 * Reschedule call back. KVM uses this interrupt to force a cpu out of
 * guest mode 
 */
__visible void __irq_entry smp_reschedule_interrupt(struct pt_regs *regs)
{
    ack_APIC_irq();
    inc_irq_stat(irq_resched_count);    
    kvm_set_cpu_l1tf_flush_l1d();

    if (trace_resched_ipi_enabled()) {
        /*
         * scheduler_ipi() might call irq_enter() as well, but
         * nested calls are fine.
         */
        irq_enter();
        trace_reschedule_entry(RESCHEDULE_VECTOR);
        scheduler_ipi();
        trace_reschedule_exit(RESCHEDULE_VECTOR);
        irq_exit();
        return;
    }
    scheduler_ipi();
}

VM-Entry

硬件基礎

  • 中斷注入最底層要依靠硬件實現,VMCS中VM-entry控制類字段,interruption-information是實現中斷注入的基礎,這個字段32bit,各字段含義如下,截圖來自Intel手冊vol3-24.8.3
    在這裏插入圖片描述
  • CPU進入客戶態時,中斷注入區域VM-entry interruption-information的加載,是在guest state區域加載之後——跳轉到guest state中RIP指向的地址之前。實際上,中斷注入的本質就是在硬件上deliver中斷信息到CPU,而deliver的時機必須在CPU VM-entry建立好guest上下文,但還沒開始運行的時間窗口裏,這個時候deliver的中斷信息,硬件查到的IDT就是guest的IDT,因爲IDTR的加載是在guest state區域加載過程中完成的。

Request檢查

  • qemu通過vm ioctl命令字創建vcpu之後,獲得了vcpu的fops,通過下發vcpu ioctl的KVM_RUN命令字,讓內核運行該vcpu
kvm_vm_ioctl
    switch (ioctl) {
    case KVM_CREATE_VCPU:
    	kvm_vm_ioctl_create_vcpu(kvm, arg)
    		static struct file_operations kvm_vcpu_fops = {
    			.release        = kvm_vcpu_release,
    			.unlocked_ioctl = kvm_vcpu_ioctl,
				......
			}
				kvm_vcpu_ioctl
					switch (ioctl) {
    				case KVM_RUN:
    					kvm_arch_vcpu_ioctl_run(vcpu, vcpu->run)
    						vcpu_run(vcpu)
    							for (;;) {
        							if (kvm_vcpu_running(vcpu)) {
            							r = vcpu_enter_guest(vcpu);
            						......
            					}

上面是一個vcpu從創建到使用的過程,進入vcpu_run之後,調用vcpu_enter_guest進入客戶態,如果沒有特殊的處理,vcpu就會一直在客戶態待下去

  • vcpu_enter_guest在進入客戶態前,會對當前的vcpu->requests做檢查,看是否在上一輪進入客戶態的時候,有別的cpu向自己發出了request,如果有,需要做相應的處理,vcpu_enter_guest中有這麼一段代碼
    if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) {
        ++vcpu->stat.req_event;
        kvm_apic_accept_events(vcpu);
        if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
            r = 1;
            goto out;
        }
		/* 將request中等待的中斷注入 */
        if (inject_pending_event(vcpu, req_int_win) != 0)

硬件注入

在inject_pending_event中有一個vm_cpu_has_injectable_intr判斷當前是否有可注入的中斷,裏面通過kvm_apic_has_interrupt檢查當前vcpu上的irr中最高優先級的中斷向量,判斷是否可以注入

inject_pending_event
	kvm_apic_has_interrupt
		__apic_update_ppr
		apic_has_interrupt_for_ppr
			highest_irr = apic_find_highest_irr(apic)

__apic_update_ppr先根據TPR和ISRV更新PPR,apic_has_interrupt_for_ppr中尋找優先級最高的IRR,如果能找到,表示有中斷需要注入,調用中斷注入的實現

if (kvm_x86_ops->interrupt_allowed(vcpu)) {
	kvm_queue_interrupt(vcpu, kvm_cpu_get_interrupt(vcpu), false);
    kvm_x86_ops->set_irq(vcpu);
}
	vmx_inject_irq
		irq = vcpu->arch.interrupt.nr
		intr = irq | INTR_INFO_VALID_MASK
		intr |= INTR_TYPE_EXT_INTR
		vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, intr)

最終將中斷信息寫入vmcs對應區域,中斷信息需要按照前面介紹的格式來組裝,將interruption-information區域的32bit內容填滿,首先是最高位的有效位,必須置1,然後是中斷的類型寫到[10-8],這裏是外部中斷,最後是中斷向量本身。組裝完成後,就可以往interruption-information區域寫入了。VM_ENTRY_INTR_INFO_FIELDinterruption-information區域在VMCS整個區域的偏移

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章