成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux中斷虛擬化之二

云計算 虛擬化 Linux
計算機系統有很多的外設需要服務,顯然,CPU采用輪詢的方式逐個詢問外設是否需要服務,是非常浪費CPU的計算的,尤其是對那些并不是頻繁需要服務的設備。

[[437706]]

 PIC虛擬化

計算機系統有很多的外設需要服務,顯然,CPU采用輪詢的方式逐個詢問外設是否需要服務,是非常浪費CPU的計算的,尤其是對那些并不是頻繁需要服務的設備。因此,計算機科學家們設計了外設主動向CPU發起服務請求的方式,這種方式就是中斷。采用中斷方式后,在沒有外設請求時,CPU就可以繼續其他計算任務,而不是進行很多不必要的輪詢,極大地提高了系統的吞吐[1] 在每個指令周期結束后,如果CPU關中斷標識(IF)沒有被設置,那么其會去檢查是否有中斷請求,如果有中斷請求,則運行對應的中斷服務程序,然后返回被中斷的計算任務繼續執行。

CPU不可能為每個硬件都設計專門的管腳接收中斷,管腳數量的限制、電路的復雜度、靈活度等方方面面都不現實,因此,需要設計一個專門管理中斷的單元。由中斷管理單元接受來自外圍設備的請求,確定請求的優先級,并向CPU發出中斷。1981年IBM推出的第1代個人電腦PC/XT使用了一個獨立的8259A作為中斷控制器,自此,8259A就成為了單核時代中斷芯片事實上的標準。因為可以通過軟件編程對其進行控制,比如當管腳收到設備信號時,可以編程控制其發出的中斷向量號,因此,中斷控制器又稱為可編程中斷控制器(programmable interrupt controller),簡稱PIC。單片8259A可以連接8個外設的中斷信號線,可以多片級聯支持更多外設。

8259A和CPU的連接如圖5所示。

圖5 8259A和CPU連接

片選和地址譯碼器相連,當CPU準備訪問8259A前,需要向地址總線發送8259A對應的地址,經過譯碼器后,譯碼器發現是8259A對應的地址,因此會拉低與8259A的CS連接的管腳的電平,從而選中8259A,通知8259A,CPU準備與其交換數據了。

8259A的D0~7管腳與CPU的數據總線相連。從CPU向8259A發送ICW和OCW,從8259A向CPU傳送8259A的狀態以及中斷向量號,都是通過數據總線傳遞的。

當CPU向8259A發送ICW、OCW時,當把數據送上數據總線后,需要通知8259A讀數據,CPU通過拉低WR管腳的電平的方式通知8259A,當8259A的WR管腳收到低電平后,讀取數據總線的數據。類似的,CPU準備好讀取8259A的狀態時,拉低RD管腳通知8259A。

8259A和CPU之間的中斷信號的通知使用專用的連線,8259A的管腳INTR(interrupt request)和INTA(interrupt acknowledge)分別與處理器的INTR和INTA管腳相連。8259A通過管腳INTR向CPU發送中斷請求,CPU通過管腳INTA向PIC發送中斷確認,告訴PIC其收到中斷并且開始處理了。8259A與CPU之間的具體中斷過程如下:

1)8259A的IR0~7管腳高電平有效,所以當中斷源請求服務時,拉高連接IR0~7的管腳,產生中斷請求。

2)8259A需要將這些信號記錄下來,因此其內部有個寄存器IRR(Interrupt Request Register),負責記錄這個中斷請求,針對這個例子,IRR的bit 0將被設置為1。

3)有的時候,我們會屏蔽掉某個設備的中斷。換句話說,就是的當這個中斷源向8259A發送信號后,8259A并不將這個中斷信號發送給CPU。讀者不要將屏蔽和CPU通過cli命令關中斷混淆,CPU關中斷時,中斷還會發送給CPU,只是在關中斷期間CPU不處理中斷。8259A中的寄存器IMR(Interrupt Mask Register)負責記錄某個中斷源是否被屏蔽,比如0號中斷源被設備了屏蔽,那么IMR的bit 0將被設置。那么這個IMR是誰設置的呢?當然是CPU中的操作系統。因此這一步,8259A將會檢查收到的中斷請求是否被屏蔽。

4)在某一個時刻,可能有多個中斷請求,或者是之前存在IRR中的中斷并沒有被處理,8259A中積累了一些中斷。某一個時刻,8259A只能向CPU發送一個中斷請求,因此,當存在多個中斷請求時,8259A需要判斷一下中斷優先級,這個單元叫做priority resolver,priority resolver將在IRR中選出優先級最高的中斷。

5)選出最高優先級的中斷后,8259A拉高管腳INTR的電平,向CPU發出信號。

6)當CPU執行完當前指令周期后,其將檢查寄存器FLAGS的中斷使能位IF(Interrupt enable flag),如果允許中斷,那么將檢查INTR是否有中斷,如果有中斷,那么將通過管腳INTR通知8259A處理器將開始處理中斷。

7)8259A收到CPU發來的INTA信號后,將置位最高優先級的中斷在ISR(In-Service Register)中對應的位,并清空IRR中對應的位。

8)通常,x86 CPU會發送第2次INTA,在收到第2次INTA后,8259A會將中斷向量號(vector)送上數據總線D0~D7。

9)如果8259A設置為AEOI(Automatic End Of Interrupt)模式,那么8259A復位ISR中對應的bit,否則ISR中對應的bit一直保持到收到系統的中斷服務程序發來的EOI命令。

我們知道,中斷服務程序保存在一個數組中,數組中的每一項對應一個中斷服務程序。在實模式下,這個數組稱為IVT(interrupt vector table);在保護模式下,這個數組稱為IDT(Interrupt descriptor table)。

這個數組中保存的服務程序,并不是全部都是外部中斷,還有處理CPU內部異常的以及軟中斷服務程序。x86CPU前32個中斷號(0-31)留給處理器異常的,比如第0個中斷號,是處理器出現除0(Divide by Zero)異常的,不能被占用。因此,假設我們計劃IVT數組中第32個元素存放管腳IR0對應的ISR,那么我們初始化8259A時,通過ICW,設置起始的irq base為32,那么當8259A發出管腳IR0的中斷請求時,則發出的值是32,管腳IR1對應的是33,依此類推。這個32、33就是所謂的中斷向量(vector)。換句話說,中斷向量就是中斷服務程序在IVT/IDT中的索引。下面就是設置irq_base的代碼,在初始化時,通過第2個初始化命令字(ICW2)設置:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. static void pic_ioport_write(void *opaque, u32addr, u32 val) 
  5.   … 
  6.     switch(s->init_state) { 
  7.     … 
  8.     case 1: 
  9.       s->irq_base = val & 0xf8; 
  10.     … 
  11.     } 

后來,隨著APIC和MSI的出現,中斷向量設置的更為靈活,可以為每個PCI設置其中斷向量,這個中斷向量存儲在PCI設備的配置空間中。

內核中抽象了一個結構體kvm_kpic_state來記錄每個8259A的狀態:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. struct kvm_kpic_state { 
  4.   u8last_irr;  /* edge detection */ 
  5.   u8 irr;  /* interrupt request register */ 
  6.   u8imr;   /* interrupt mask register */ 
  7.   u8isr;   /* interrupt service register */ 
  8.   … 
  9. }; 
  10.   
  11. struct kvm_pic { 
  12.   structkvm_kpic_state pics[2]; /* 0 is master pic, 1 is slave pic*/ 
  13.  irq_request_func *irq_request; 
  14.   void*irq_request_opaque; 
  15.   intoutput;   /* intr from master PIC */ 
  16.   structkvm_io_device dev; 
  17. }; 

1片8259A只能連接最多8個外設,如果需要支持更多外設,需要多片8259A級聯。在結構體kvm_pic中,我們看到有2片8259A:pic[0]和pic[1]。KVM定義了結構體kvm_kpic_state記錄8259A的狀態,其中包括我們之前提到的IRR、IMR、ISR等等。

1 虛擬設備向PIC發送中斷請求

如同物理外設請求中斷時拉高與8259A連接的管腳的電壓,虛擬設備請求中斷的方式是通過一個API告訴虛擬的8259A芯片中斷請求,以kvmtool中的virtio blk虛擬設備為例:

  1. commit 4155ba8cda055b7831489e4c4a412b073493115b 
  2. kvm: Fix virtio block device support some more 
  3. kvmtool.git/blk-virtio.c 
  4. static bool blk_virtio_out(…) 
  5.   … 
  6.   caseVIRTIO_PCI_QUEUE_NOTIFY: { 
  7.     … 
  8.     while(queue->vring.avail->idx != queue->last_avail_idx) { 
  9.       if(!blk_virtio_read(self, queue)) 
  10.        return false
  11.     } 
  12.    kvm__irq_line(self, VIRTIO_BLK_IRQ, 1); 
  13.   
  14.     break; 
  15.   } 
  16.   … 

當Guest內核的塊設備驅動發出I/O通知VIRTIO_PCI_QUEUE_NOTIFY時,將觸發CPU從Guest模式切換到Host模式,KVM中的塊模擬設備開始I/O操作,比如訪問保存Guest文件系統的鏡像文件。virtio blk這個提交,塊設備的I/O處理是同步的,也就是說,一直要等到文件操作完成,才會向Guest發送中斷,返回Guest。當然同步阻塞在這里是不合理的,而是應該馬上返回Guest,這樣Guest可以執行其他的任務,虛擬設備完成I/O操作后,再通知Guest,這是kvmtool初期的實現,后來已經改進為異步的方式。代碼中在一個while循環處理完設備驅動的I/O請求后,調用了函數kvm__irq_line,irq_line對應8259A的管腳IR0~7,其代碼如下:

  1. commit 4155ba8cda055b7831489e4c4a412b073493115b 
  2. kvm: Fix virtio block device support some more 
  3. kvmtool.git/kvm.c 
  4. void kvm__irq_line(struct kvm *self, int irq, intlevel) 
  5.   structkvm_irq_level irq_level; 
  6.   
  7.   irq_level= (struct kvm_irq_level) { 
  8.     { 
  9.      .irq    = irq, 
  10.     }, 
  11.    .level    = level
  12.   }; 
  13.   
  14.   if(ioctl(self->vm_fd, KVM_IRQ_LINE, &irq_level) < 0) 
  15.    die_perror("KVM_IRQ_LINE failed"); 
  16. 函數kvm__irq_line將irq number和管腳電平信息,這里是1,表示拉高電平了,封裝到結構體kvm_irq_level中,傳遞給內核中的KVM模塊: 
  17. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  18. KVM: Add support for in-kernel PIC emulation 
  19. linux.git/drivers/kvm/kvm_main.c 
  20. static long kvm_vm_ioctl(…) 
  21.   … 
  22.   caseKVM_IRQ_LINE: { 
  23.     … 
  24.         kvm_pic_set_irq(pic_irqchip(kvm), 
  25.          irq_event.irq, 
  26.          irq_event.level); 
  27.     … 
  28.     break; 
  29.   } 
  30.   … 

KVM模塊將kvmtool中組織的中斷信息從用戶空間復制到內核空間中,然后調用虛擬8259A的模塊中提供的API kvm_pic_set_irq,向8259A發出中斷請求。

2 記錄中斷到IRR

中斷處理需要一個過程,從外設發出請求,一直到ISR處理完成發出EOI。而且可能中斷來了并不能馬上處理,或者之前已經累加了一些中斷,大家需要排隊依次請求CPU處理,等等,因此,需要一些寄存器來記錄這些狀態。當外設中斷請求到來時,8259A首先需要將他們記錄下來,這個寄存器就是IRR(Interrupt Request Register),8259A用他來記錄有哪些pending的中斷需要處理。

當KVM模塊收到外設的請求,調用虛擬8259A的API kvm_pic_set_irq是,其第1件事就是將中斷記錄到IRR寄存器中:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. void kvm_pic_set_irq(void *opaque, int irq, intlevel) 
  5.   structkvm_pic *s = opaque; 
  6.   
  7.  pic_set_irq1(&s->pics[irq >> 3], irq & 7, level); 
  8.   …… 
  9.   
  10. static inline void pic_set_irq1(structkvm_kpic_state *s, 
  11. int irq, int level
  12.   int mask; 
  13.   mask = 1<< irq; 
  14.   if(s->elcr & mask) /* level triggered */ 
  15.     … 
  16.   else  /* edge triggered */ 
  17.     if(level) { 
  18.       if((s->last_irr & mask) == 0) 
  19.        s->irr |= mask; 
  20.      s->last_irr |= mask; 
  21.     } else 
  22.      s->last_irr &= ~mask; 

信號有邊緣觸發和水平觸發,在物理上可以理解為,8329A在前一個周期檢測到管腳信號是0,當前周前檢測到管腳信號是1,如果是上升沿觸發模式,那么8259A就認為外設有請求了,這種觸發模式就是邊緣觸發。對于水平觸發,以高電平觸發為例,當8259A檢測到管腳處于高電平,則認為外設來請求了。

在虛擬8259A的結構體kvm_kpic_state中,寄存器elcr就是用來記錄8259A被設置的觸發模式的。參數level即相當于硬件層面的電信號,0表示低電平,1表示高電平。以邊緣觸發為例,當管腳收到一個低電平時,即level的值為0,代碼進入else分支,結構體kvm_kpic_state中的字段last_irr中會清除該IRQ對應IRR的位,即相當于設置該中斷管腳為低電平狀態。當管腳收到高電平時,即level的值為1,代碼進入if分支,此時8259A將判斷之前該管腳的狀態,也就是判斷結構體kvm_kpic_state中的字段last_irr中該IRQ對應IRR的位,如果為低電平,那么則認為中斷源有中斷請求,將其記錄到IRR中。當然,同時需要在字段last_irr記錄下當前該管腳的狀態。

3 設置中斷標識

當8259A將中斷請求記錄到IRR中后,下一步就是開啟一個中斷評估(evaluate)過程了,包括中斷是否被屏蔽,多個中斷請求的優先級等等,最后將通過管腳INTA通知CPU處理外部中斷。我們看到這里是8259A主動發起中斷過程,但是虛擬中斷有些不同,中斷的發起的時機不再是虛擬中斷芯片主動發起,而是在每次準備切入Guest時,KVM查詢中斷芯片,如果有pending的中斷,則執行中斷注入。模擬8259A在收到中斷請求后,在記錄到IRR后,設置一個變量,后面在切入Guest前KVM會查詢這個變量:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. void kvm_pic_set_irq(void *opaque, int irq, intlevel) 
  5.   structkvm_pic *s = opaque; 
  6.   
  7.  pic_set_irq1(&s->pics[irq >> 3], irq & 7, level); 
  8.  pic_update_irq(s); 
  9.   
  10. static void pic_update_irq(struct kvm_pic *s) 
  11.   … 
  12.   irq =pic_get_irq(&s->pics[0]); 
  13.   if (irq>= 0) 
  14.    s->irq_request(s->irq_request_opaque, 1); 
  15.   else 
  16.    s->irq_request(s->irq_request_opaque, 0); 
  17.   
  18. static void pic_irq_request(void *opaque, intlevel) 
  19.   struct kvm*kvm = opaque; 
  20.   
  21.  pic_irqchip(kvm)->output = level

在函數vmx_vcpu_run中,在準備切入Guest之前將調用函數vmx_intr_assist去檢查虛擬中斷芯片是否有等待處理的中斷,相關代碼如下:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/vmx.c 
  4. static int vmx_vcpu_run(…) 
  5.   … 
  6.    vmx_intr_assist(vcpu); 
  7.   … 
  8.   
  9. static void vmx_intr_assist(struct kvm_vcpu*vcpu) 
  10.   … 
  11.   has_ext_irq= kvm_cpu_has_interrupt(vcpu); 
  12.   … 
  13.   if(!has_ext_irq) 
  14.     return
  15.  interrupt_window_open = 
  16.    ((vmcs_readl(GUEST_RFLAGS) & X86_EFLAGS_IF) && 
  17.     (vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) & 3) == 0); 
  18.   if(interrupt_window_open) 
  19.    vmx_inject_irq(vcpu, kvm_cpu_get_interrupt(vcpu)); 
  20.   … 

其中函數kvm_cpu_has_interrupt查詢8259A設置的變量output:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/irq.c 
  4. int kvm_cpu_has_interrupt(struct kvm_vcpu *v) 
  5.   structkvm_pic *s = pic_irqchip(v->kvm); 
  6.   
  7.   if(s->output)  /* PIC */ 
  8.     return1; 
  9.   return 0; 

如果有output設置了,就說明有外部中斷等待處理,然后接著判斷Guest是否可以被中斷,包括Guest是否中斷了,Guest是否正在執行一些不能被中斷的指令,如果可以注入,則調用vmx_inject_irq完成中斷的注入。其中,傳遞給函數vmx_inject_irq的第2個參數是函數kvm_cpu_get_interrupt返回的結果,該函數獲取需要注入的中斷,這個過程就是中斷評估過程,我們下一節討論。

4 中斷評估

在上一節我們看到在執行注入前,vmx_inject_irq調用函數kvm_cpu_get_interrupt獲取需要注入的中斷。函數kvm_cpu_get_interrupt的核心邏輯就是中斷評估(evaluate),包括:這個pending的中斷有沒有被屏蔽?pending的中斷的優先級是否比CPU正在處理的中斷優先級高?代碼如下:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/irq.c 
  4. int kvm_cpu_get_interrupt(struct kvm_vcpu *v) 
  5.   …… 
  6.   vector =kvm_pic_read_irq(s); 
  7.   if (vector!= -1) 
  8.     returnvector; 
  9.   … 
  10.   
  11. linux.git/drivers/kvm/i8259.c 
  12. int kvm_pic_read_irq(struct kvm_pic *s) 
  13.   int irq,irq2, intno; 
  14.   
  15.   irq =pic_get_irq(&s->pics[0]); 
  16.   if (irq>= 0) { 
  17.     … 
  18.       intno = s->pics[0].irq_base + irq; 
  19.   } else { 
  20.   … 
  21.   returnintno; 

kvm_pic_read_irq調用函數pic_get_irq獲取評估后的中斷,如上面黑體標識的,我們可以清楚的看到中斷向量和中斷管腳的關系,疊加了一個irq_base,這個irq_base就是通過初始化8259A時通過ICW設置的,完成從IRn到中斷向量的轉換。

一個中斷芯片通常連接有多個外設,所以在某一個時刻,可能會有多個設備請求到來,這時就有一個優先處理哪個請求的問題,因此,中斷就有了優先級的概念。以8259A為例,典型的有2種中斷優先級模式:

1)固定優先級(Fixedpriority),即優先級是固定的,從IR0到IR7依次降低,IR0的優先級永遠最高,IR7的優先級永遠最低。

2)循環優先級(rotatingpriority),即當前處理完的IRn其優先級調整為最低,當前處理的優先級下個,即IRn+1,調整為優先級最高。比如,當前處理的中斷是irq 2,那么緊接著irq3的優先級設置為是最高,然后依次是irq4、irq5、irq6、irq7、irq1、irq2、irq3。假設此時irq5和irq2同時來了中斷,那么irq5顯然會被優先處理。然后irq6被設置為優先級最高,接下來依次是irq7、irq1、irq2、irq3、irq4、irq5。

理解了循環優先級算法后,從8259A中獲取最高優先級請求的代碼就很容易理解了:

  1. commit85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. static int pic_get_irq(struct kvm_kpic_state *s) 
  5.   int mask,cur_priority, priority; 
  6.   
  7.   mask =s->irr & ~s->imr; 
  8.   priority =get_priority(s, mask); 
  9.   if(priority == 8) 
  10.     return-1; 
  11.   … 
  12.   mask =s->isr; 
  13.   … 
  14.  cur_priority = get_priority(s, mask); 
  15.   if(priority < cur_priority) 
  16.     /* 
  17.      *higher priority found: an irq should be generated 
  18.      */ 
  19.     return(priority + s->priority_add) & 7; 
  20.   else 
  21.     return-1; 
  22.   
  23. static inline int get_priority(structkvm_kpic_state *s, int mask) 
  24.   intpriority; 
  25.   if (mask== 0) 
  26.     return8; 
  27.   priority =0; 
  28.   while((mask & (1 << ((priority + s->priority_add) & 7))) == 0) 
  29.    priority++; 
  30.   returnpriority; 

函數pic_get_irq分成2部分,第1部分是從當前pending的中斷中取得最高優先級的中斷,當前之前需要濾掉被被屏蔽的中斷。第2部分是獲取正在被CPU處理的中斷的優先級的中斷的優先級,通過這里,讀者更能具體的理解了8259A為什么需要這些寄存器記錄中斷的狀態。然后比較2個中斷的優先級,如果pending的優先級高,那么就通過拉低INTR管腳電壓,向CPU發出中斷請求。

再來看一下計算優先級的函數get_priority。其中變量priority_add記錄的是當前最高優先級的管腳,所以邏輯上就是從當前最高的優先級管腳開始,從高向低依次檢查是否有pending的中斷。比如當前IR4最高,那么priority_add的值就是4。while循環,從管腳IR(4+0)開始,依次檢查管腳IR(4+1)、IR(4+2),依此類推。

5 中斷ACK

在物理上,CPU在準備處理一個中斷請求后,將通過管腳INTA向8259A發出確認脈沖。同樣,軟件模擬上,也需要類似處理。在完成中斷評估后,準備注入Guest前,需要向虛擬8259A執行確認狀態的操作,代碼如下:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. int kvm_pic_read_irq(struct kvm_pic *s) 
  5.   int irq,irq2, intno; 
  6.   
  7.   irq =pic_get_irq(&s->pics[0]); 
  8.   if (irq>= 0) { 
  9.    pic_intack(&s->pics[0], irq); 
  10.   … 
  11.   
  12. static inline void pic_intack(structkvm_kpic_state *s, int irq) 
  13.   if(s->auto_eoi) { 
  14.     … 
  15.   } else 
  16.    s->isr |= (1 << irq); 
  17.   /* 
  18.    * Wedon't clear a level sensitive interrupt here 
  19.    */ 
  20.   if(!(s->elcr & (1 << irq))) 
  21.    s->irr &= ~(1 << irq); 

在中斷評估中,在調用函數kvm_pic_read_irq獲取評估的中斷結果后,馬上就調用函數pic_intack完成了中斷確認的動作。在收到CPU發來的中斷確認后,8259A需要更新自己的狀態,包括,因為中斷已經開始得到服務了,所以從IRR中清除等待服務請求。另外,需要設置ISR位,記錄正在被服務的中斷,但是這里稍微有一點點復雜。

設置ISR表示正在服務的位,表示處理器正在處理中斷。設置ISR的一個典型作用是中斷的作用是當ISR處理完中斷,向8259A發送EOI時,8259A知道正在處理的IRn,比如說如果8259A使用的是循環優先級,那么需要最高優先級為當前處理的IRn的下一個。

如果8259A是AEOI模式,那么就無須設置ISR了,因為中斷服務程序執行完畢后不會發送EOI命令。所以在AEOI模式下(上面代碼的if分支),需要將收到EOI時8259A需要處理的邏輯完成,這部分內容我們在下一節會討論。

對于邊緣觸發的,進入到ISR階段后,需要復位對于IRR。對于level trigger的,在收到中斷請求后8259A處理,不過多討論了,如果讀者有興趣,可以閱讀函數pic_set_irq1中關于水平觸發部分的邏輯。

6 關于EOI的處理

中斷服務程序執行完成后,會向8259A發送EOI,告知8259A中斷處理完成。8259A在收到這個EOI時,復位ISR,如果是采用的循環優先級,還需要設置變量priority_add,使其指向當前處理IRn的下一個:

  1. commit85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. linux.git/drivers/kvm/i8259.c 
  4. static void pic_ioport_write(void *opaque, u32addr, u32 val) 
  5.   … 
  6.       case1: /* end of interrupt */ 
  7.       case5: 
  8.        priority = get_priority(s, s->isr); 
  9.         if(priority != 8) { 
  10.          irq = (priority + s->priority_add) & 7; 
  11.          s->isr &= ~(1 << irq); 
  12.           if(cmd == 5) 
  13.            s->priority_add = (irq + 1) & 7; 
  14.          pic_update_irq(s->pics_state); 
  15.         } 
  16.        break; 
  17.   … 

如果8259A被設置為AEOI模式,不會再收到后續中斷服務程序的EOI命令,那么8259A在收到CPU的ACK后,就必須把收到EOI命令時執行的邏輯現在處理,前面看到AEOI模式不必設置ISR,所以這里也無需復位ISR,只需要調整變量priority_add,記錄最高優先級位置即可:

  1. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  2. KVM: Add support for in-kernel PIC emulation 
  3. static inline void pic_intack(structkvm_kpic_state *s, int irq) 
  4.   if(s->auto_eoi) { 
  5.     if(s->rotate_on_auto_eoi) 
  6.      s->priority_add = (irq + 1) & 7;    
  7.   } else 
  8.   … 

7 中斷注入

對于外部中斷,每個CPU在指令周期結束后,將會去檢查INTR是否有中斷請求。那么對于處于Guest模式的CPU,其如何知道有中斷請求呢?Intel在VMCS中設置了一個字段:VM-entry interruption-information,在VM entry時CPU將會檢查這個字段,這個字段格式表3-1所示。

表3-1 VM-entry interruption-information格式(部分)

內容

7:0

中斷或異常向量

10:8

中斷類型:

0: External  interrupt

1: Reserved

2: Non-maskable  interrupt (NMI)

3: Hardware  exception

4: Software  interrupt

5: Privileged  software exception

6: Software  exception

7: Other event

31

是否有效[2] 

在VM entry前,KVM模塊檢查虛擬8259A中如果有pending中斷需要處理,則將需要處理的中斷信息寫入到VMCS中的這個字段VM-entry 

  1. interruption-information: 
  2. commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 
  3. KVM: Add support for in-kernel PIC emulation 
  4. linux.git/drivers/kvm/vmx.c 
  5. static void vmx_inject_irq(struct kvm_vcpu *vcpu,int irq) 
  6.   … 
  7.  vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, 
  8.       irq |INTR_TYPE_EXT_INTR | INTR_INFO_VALID_MASK); 

前面我們看到,中斷注入是在每次VM entry時,KVM模塊檢查8259A是否有pending的中斷等待處理。這樣就有可能給中斷帶來一定的延遲,典型如下面2類情況:

(1)CPU可能正處在Guest模式,那么就需要等待下一次VM exit 和VM entry。

(2)VCPU這個線程也許正在睡眠,比如Guest VCPU運行hlt指令時,就會切換回Host模式,線程掛起。

對于第1種情況,是多處理器系統下的一個典型情況,目標CPU的正在運行Guest。KVM需要想辦法觸發Guest發生一次VM exit,切換到Host。我們知道,當處于Guest模式的CPU收到外部中斷時,會觸發VM exit,由Host來處理這次中斷。所以,KVM可以向目標CPU發送一個IPI中斷,觸發目標CPU發生一次VM exit。

對于第2種情況,首先需要喚醒睡眠的VCPU線程,使其進入CPU就緒隊列,準備接受調度。對于多處理器系統,然后再向目標CPU發送一個“重新調度”的IPI中斷,那么被喚醒的VCPU線程很快就會被調度,執行切入Guest的過程,從而完成中斷注入。

所以當有中斷請求時,虛擬中斷芯片將主動“kick”一下目標CPU,這個“踢”的函數就是kvm_vcpu_kick:

  1. commit b6958ce44a11a9e9425d2b67a653b1ca2a27796f 
  2. KVM: Emulate hlt in the kernel 
  3. linux.git/drivers/kvm/i8259.c 
  4. static void pic_irq_request(void *opaque, intlevel) 
  5.   … 
  6.  pic_irqchip(kvm)->output = level
  7.   if (vcpu) 
  8.    kvm_vcpu_kick(vcpu); 

如果虛擬CPU線程在睡眠,則“踢醒”他。如果目標CPU運行在Guest模式,則將其從Guest模式“踢”到Host模式,在VM entry時完成中斷注入,kick的手段就是我們剛剛提到的IPI,代碼如下:

  1. commit b6958ce44a11a9e9425d2b67a653b1ca2a27796f 
  2. KVM: Emulate hlt in the kernel 
  3. linux.git/drivers/kvm/irq.c 
  4. void kvm_vcpu_kick(struct kvm_vcpu *vcpu) 
  5.   intipi_pcpu = vcpu->cpu; 
  6.   
  7.   if(waitqueue_active(&vcpu->wq)) { 
  8.    wake_up_interruptible(&vcpu->wq); 
  9.    ++vcpu->stat.halt_wakeup; 
  10.   } 
  11.   if(vcpu->guest_mode) 
  12.    smp_call_function_single(ipi_pcpu, vcpu_kick_intr, 
  13. vcpu, 0, 0); 

如果VCPU線程睡眠在等待隊列上,則喚醒使其進入CPU的就緒任務隊列。如果是多CPU的情況且目標CPU處于Guest模式,則需要發送核間中斷。如果目標CPU正在執行Guest,那么這個IPI中斷將導致VM exit,從而在下一次進入Guest時,可以注入中斷。

事實上,目標CPU無須執行任何callback,也無須等待IPI返回,所以也無須使用smp_call_function_single,而是直接發送一個請求目標CPU重新調度的IPI即可,因此后來直接調用了函數smp_send_reschedule。函數smp_send_reschedule簡單直接,直接發送了一個RESCHEDULE的IPI:

  1. commit 32f8840064d88cc3f6e85203aec7b6b57bebcb97 
  2. KVM: use smp_send_reschedule in kvm_vcpu_kick 
  3. linux.git/arch/x86/kvm/x86.c 
  4. void kvm_vcpu_kick(struct kvm_vcpu *vcpu) 
  5.   … 
  6.      smp_send_reschedule(cpu); 
  7.   … 
  8.   
  9. linux.git/arch/x86/kernel/smp.c 
  10. static void native_smp_send_reschedule(int cpu) 
  11.   … 
  12.  apic->send_IPI_mask(cpumask_of(cpu), RESCHEDULE_VECTOR); 

本文轉載自微信公眾號「Linux閱碼場」,可以通過以下二維碼關注。轉載本文請聯系Linux閱碼場公眾號。

 

責任編輯:武曉燕 來源: Linux閱碼場
相關推薦

2021-11-30 07:02:10

虛擬化Linux 中斷

2018-03-21 11:00:45

2020-11-23 07:19:15

Linux虛擬化KVM

2013-12-02 10:34:32

虛擬化實戰Cluster

2013-05-27 09:52:26

虛擬化存儲虛擬化應用

2023-09-19 10:00:34

Linux虛擬

2010-05-26 14:42:54

桌面虛擬化

2021-10-26 08:08:34

Node ExporLinux 監控

2009-12-25 10:05:00

Linux虛擬化Xen虛擬化

2019-11-12 14:48:00

Linux桌面虛擬化KVM

2011-03-28 18:25:03

ibmdwLinux

2012-03-18 21:41:40

linux虛擬化

2019-05-13 16:37:35

Linux網絡虛擬化

2019-07-24 10:06:15

Linux網絡虛擬化

2009-10-21 12:49:25

Linux壓縮打包方法

2017-07-01 07:02:41

虛擬化遷移

2021-12-10 08:45:45

Linux GIC Linux 系統

2020-01-02 10:34:32

Linux虛擬化Docker

2009-12-21 10:47:53

Linux虛擬化

2012-08-22 09:41:54

虛擬化
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人国产精品久久久 | 欧美成人精品一区二区三区 | 风间由美一区二区三区在线观看 | 国产ts人妖另类 | 久久国产精品久久久久久 | 国产精彩视频 | 综合精品久久久 | 无码日韩精品一区二区免费 | 国产中文一区二区三区 | 亚洲精品久久久久久久久久久久久 | 亚洲精品欧洲 | 先锋资源站 | 久久精品国产一区二区三区不卡 | 久久最新| 91精品国产91久久久久久不卞 | 国产精品国产三级国产a | 国产视频不卡一区 | 亚洲精品亚洲人成人网 | 亚洲精品自在在线观看 | 一区二区三区视频免费观看 | 一区二区高清 | 日韩欧美日韩在线 | 亚洲在线 | 亚洲精品日韩一区二区电影 | 国产精品美女久久久免费 | 精品久草 | 麻豆天堂| 观看毛片| 日本久久网站 | 成年网站在线观看 | 中日韩毛片 | 亚洲精品日韩在线 | 欧美精品乱码久久久久久按摩 | 在线观看日本高清二区 | 夜夜爽99久久国产综合精品女不卡 | 最新中文字幕 | 久草免费视 | 欧美成人免费在线 | 亚洲精品一区在线观看 | 国产成人网 | 国产精品久久久久久吹潮日韩动画 |