自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux中斷虛擬化之二

云計(jì)算 虛擬化 Linux
計(jì)算機(jī)系統(tǒng)有很多的外設(shè)需要服務(wù),顯然,CPU采用輪詢的方式逐個(gè)詢問(wèn)外設(shè)是否需要服務(wù),是非常浪費(fèi)CPU的計(jì)算的,尤其是對(duì)那些并不是頻繁需要服務(wù)的設(shè)備。

[[437706]]

 PIC虛擬化

計(jì)算機(jī)系統(tǒng)有很多的外設(shè)需要服務(wù),顯然,CPU采用輪詢的方式逐個(gè)詢問(wèn)外設(shè)是否需要服務(wù),是非常浪費(fèi)CPU的計(jì)算的,尤其是對(duì)那些并不是頻繁需要服務(wù)的設(shè)備。因此,計(jì)算機(jī)科學(xué)家們?cè)O(shè)計(jì)了外設(shè)主動(dòng)向CPU發(fā)起服務(wù)請(qǐng)求的方式,這種方式就是中斷。采用中斷方式后,在沒(méi)有外設(shè)請(qǐng)求時(shí),CPU就可以繼續(xù)其他計(jì)算任務(wù),而不是進(jìn)行很多不必要的輪詢,極大地提高了系統(tǒng)的吞吐[1] 在每個(gè)指令周期結(jié)束后,如果CPU關(guān)中斷標(biāo)識(shí)(IF)沒(méi)有被設(shè)置,那么其會(huì)去檢查是否有中斷請(qǐng)求,如果有中斷請(qǐng)求,則運(yùn)行對(duì)應(yīng)的中斷服務(wù)程序,然后返回被中斷的計(jì)算任務(wù)繼續(xù)執(zhí)行。

CPU不可能為每個(gè)硬件都設(shè)計(jì)專門的管腳接收中斷,管腳數(shù)量的限制、電路的復(fù)雜度、靈活度等方方面面都不現(xiàn)實(shí),因此,需要設(shè)計(jì)一個(gè)專門管理中斷的單元。由中斷管理單元接受來(lái)自外圍設(shè)備的請(qǐng)求,確定請(qǐng)求的優(yōu)先級(jí),并向CPU發(fā)出中斷。1981年IBM推出的第1代個(gè)人電腦PC/XT使用了一個(gè)獨(dú)立的8259A作為中斷控制器,自此,8259A就成為了單核時(shí)代中斷芯片事實(shí)上的標(biāo)準(zhǔn)。因?yàn)榭梢酝ㄟ^(guò)軟件編程對(duì)其進(jìn)行控制,比如當(dāng)管腳收到設(shè)備信號(hào)時(shí),可以編程控制其發(fā)出的中斷向量號(hào),因此,中斷控制器又稱為可編程中斷控制器(programmable interrupt controller),簡(jiǎn)稱PIC。單片8259A可以連接8個(gè)外設(shè)的中斷信號(hào)線,可以多片級(jí)聯(lián)支持更多外設(shè)。

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

圖5 8259A和CPU連接

片選和地址譯碼器相連,當(dāng)CPU準(zhǔn)備訪問(wèn)8259A前,需要向地址總線發(fā)送8259A對(duì)應(yīng)的地址,經(jīng)過(guò)譯碼器后,譯碼器發(fā)現(xiàn)是8259A對(duì)應(yīng)的地址,因此會(huì)拉低與8259A的CS連接的管腳的電平,從而選中8259A,通知8259A,CPU準(zhǔn)備與其交換數(shù)據(jù)了。

8259A的D0~7管腳與CPU的數(shù)據(jù)總線相連。從CPU向8259A發(fā)送ICW和OCW,從8259A向CPU傳送8259A的狀態(tài)以及中斷向量號(hào),都是通過(guò)數(shù)據(jù)總線傳遞的。

當(dāng)CPU向8259A發(fā)送ICW、OCW時(shí),當(dāng)把數(shù)據(jù)送上數(shù)據(jù)總線后,需要通知8259A讀數(shù)據(jù),CPU通過(guò)拉低WR管腳的電平的方式通知8259A,當(dāng)8259A的WR管腳收到低電平后,讀取數(shù)據(jù)總線的數(shù)據(jù)。類似的,CPU準(zhǔn)備好讀取8259A的狀態(tài)時(shí),拉低RD管腳通知8259A。

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

1)8259A的IR0~7管腳高電平有效,所以當(dāng)中斷源請(qǐng)求服務(wù)時(shí),拉高連接IR0~7的管腳,產(chǎn)生中斷請(qǐng)求。

2)8259A需要將這些信號(hào)記錄下來(lái),因此其內(nèi)部有個(gè)寄存器IRR(Interrupt Request Register),負(fù)責(zé)記錄這個(gè)中斷請(qǐng)求,針對(duì)這個(gè)例子,IRR的bit 0將被設(shè)置為1。

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

4)在某一個(gè)時(shí)刻,可能有多個(gè)中斷請(qǐng)求,或者是之前存在IRR中的中斷并沒(méi)有被處理,8259A中積累了一些中斷。某一個(gè)時(shí)刻,8259A只能向CPU發(fā)送一個(gè)中斷請(qǐng)求,因此,當(dāng)存在多個(gè)中斷請(qǐng)求時(shí),8259A需要判斷一下中斷優(yōu)先級(jí),這個(gè)單元叫做priority resolver,priority resolver將在IRR中選出優(yōu)先級(jí)最高的中斷。

5)選出最高優(yōu)先級(jí)的中斷后,8259A拉高管腳INTR的電平,向CPU發(fā)出信號(hào)。

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

7)8259A收到CPU發(fā)來(lái)的INTA信號(hào)后,將置位最高優(yōu)先級(jí)的中斷在ISR(In-Service Register)中對(duì)應(yīng)的位,并清空IRR中對(duì)應(yīng)的位。

8)通常,x86 CPU會(huì)發(fā)送第2次INTA,在收到第2次INTA后,8259A會(huì)將中斷向量號(hào)(vector)送上數(shù)據(jù)總線D0~D7。

9)如果8259A設(shè)置為AEOI(Automatic End Of Interrupt)模式,那么8259A復(fù)位ISR中對(duì)應(yīng)的bit,否則ISR中對(duì)應(yīng)的bit一直保持到收到系統(tǒng)的中斷服務(wù)程序發(fā)來(lái)的EOI命令。

我們知道,中斷服務(wù)程序保存在一個(gè)數(shù)組中,數(shù)組中的每一項(xiàng)對(duì)應(yīng)一個(gè)中斷服務(wù)程序。在實(shí)模式下,這個(gè)數(shù)組稱為IVT(interrupt vector table);在保護(hù)模式下,這個(gè)數(shù)組稱為IDT(Interrupt descriptor table)。

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

  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.     } 

后來(lái),隨著APIC和MSI的出現(xiàn),中斷向量設(shè)置的更為靈活,可以為每個(gè)PCI設(shè)置其中斷向量,這個(gè)中斷向量存儲(chǔ)在PCI設(shè)備的配置空間中。

內(nèi)核中抽象了一個(gè)結(jié)構(gòu)體kvm_kpic_state來(lái)記錄每個(gè)8259A的狀態(tài):

  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個(gè)外設(shè),如果需要支持更多外設(shè),需要多片8259A級(jí)聯(lián)。在結(jié)構(gòu)體kvm_pic中,我們看到有2片8259A:pic[0]和pic[1]。KVM定義了結(jié)構(gòu)體kvm_kpic_state記錄8259A的狀態(tài),其中包括我們之前提到的IRR、IMR、ISR等等。

1 虛擬設(shè)備向PIC發(fā)送中斷請(qǐng)求

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

  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.   … 

當(dāng)Guest內(nèi)核的塊設(shè)備驅(qū)動(dòng)發(fā)出I/O通知VIRTIO_PCI_QUEUE_NOTIFY時(shí),將觸發(fā)CPU從Guest模式切換到Host模式,KVM中的塊模擬設(shè)備開(kāi)始I/O操作,比如訪問(wèn)保存Guest文件系統(tǒng)的鏡像文件。virtio blk這個(gè)提交,塊設(shè)備的I/O處理是同步的,也就是說(shuō),一直要等到文件操作完成,才會(huì)向Guest發(fā)送中斷,返回Guest。當(dāng)然同步阻塞在這里是不合理的,而是應(yīng)該馬上返回Guest,這樣Guest可以執(zhí)行其他的任務(wù),虛擬設(shè)備完成I/O操作后,再通知Guest,這是kvmtool初期的實(shí)現(xiàn),后來(lái)已經(jīng)改進(jìn)為異步的方式。代碼中在一個(gè)while循環(huán)處理完設(shè)備驅(qū)動(dòng)的I/O請(qǐng)求后,調(diào)用了函數(shù)kvm__irq_line,irq_line對(duì)應(yīng)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. 函數(shù)kvm__irq_line將irq number和管腳電平信息,這里是1,表示拉高電平了,封裝到結(jié)構(gòu)體kvm_irq_level中,傳遞給內(nèi)核中的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中組織的中斷信息從用戶空間復(fù)制到內(nèi)核空間中,然后調(diào)用虛擬8259A的模塊中提供的API kvm_pic_set_irq,向8259A發(fā)出中斷請(qǐng)求。

2 記錄中斷到IRR

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

當(dāng)KVM模塊收到外設(shè)的請(qǐng)求,調(diào)用虛擬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; 

信號(hào)有邊緣觸發(fā)和水平觸發(fā),在物理上可以理解為,8329A在前一個(gè)周期檢測(cè)到管腳信號(hào)是0,當(dāng)前周前檢測(cè)到管腳信號(hào)是1,如果是上升沿觸發(fā)模式,那么8259A就認(rèn)為外設(shè)有請(qǐng)求了,這種觸發(fā)模式就是邊緣觸發(fā)。對(duì)于水平觸發(fā),以高電平觸發(fā)為例,當(dāng)8259A檢測(cè)到管腳處于高電平,則認(rèn)為外設(shè)來(lái)請(qǐng)求了。

在虛擬8259A的結(jié)構(gòu)體kvm_kpic_state中,寄存器elcr就是用來(lái)記錄8259A被設(shè)置的觸發(fā)模式的。參數(shù)level即相當(dāng)于硬件層面的電信號(hào),0表示低電平,1表示高電平。以邊緣觸發(fā)為例,當(dāng)管腳收到一個(gè)低電平時(shí),即level的值為0,代碼進(jìn)入else分支,結(jié)構(gòu)體kvm_kpic_state中的字段last_irr中會(huì)清除該IRQ對(duì)應(yīng)IRR的位,即相當(dāng)于設(shè)置該中斷管腳為低電平狀態(tài)。當(dāng)管腳收到高電平時(shí),即level的值為1,代碼進(jìn)入if分支,此時(shí)8259A將判斷之前該管腳的狀態(tài),也就是判斷結(jié)構(gòu)體kvm_kpic_state中的字段last_irr中該IRQ對(duì)應(yīng)IRR的位,如果為低電平,那么則認(rèn)為中斷源有中斷請(qǐng)求,將其記錄到IRR中。當(dāng)然,同時(shí)需要在字段last_irr記錄下當(dāng)前該管腳的狀態(tài)。

3 設(shè)置中斷標(biāo)識(shí)

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

  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

在函數(shù)vmx_vcpu_run中,在準(zhǔn)備切入Guest之前將調(diào)用函數(shù)vmx_intr_assist去檢查虛擬中斷芯片是否有等待處理的中斷,相關(guān)代碼如下:

  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.   … 

其中函數(shù)kvm_cpu_has_interrupt查詢8259A設(shè)置的變量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設(shè)置了,就說(shuō)明有外部中斷等待處理,然后接著判斷Guest是否可以被中斷,包括Guest是否中斷了,Guest是否正在執(zhí)行一些不能被中斷的指令,如果可以注入,則調(diào)用vmx_inject_irq完成中斷的注入。其中,傳遞給函數(shù)vmx_inject_irq的第2個(gè)參數(shù)是函數(shù)kvm_cpu_get_interrupt返回的結(jié)果,該函數(shù)獲取需要注入的中斷,這個(gè)過(guò)程就是中斷評(píng)估過(guò)程,我們下一節(jié)討論。

4 中斷評(píng)估

在上一節(jié)我們看到在執(zhí)行注入前,vmx_inject_irq調(diào)用函數(shù)kvm_cpu_get_interrupt獲取需要注入的中斷。函數(shù)kvm_cpu_get_interrupt的核心邏輯就是中斷評(píng)估(evaluate),包括:這個(gè)pending的中斷有沒(méi)有被屏蔽?pending的中斷的優(yōu)先級(jí)是否比CPU正在處理的中斷優(yōu)先級(jí)高?代碼如下:

  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調(diào)用函數(shù)pic_get_irq獲取評(píng)估后的中斷,如上面黑體標(biāo)識(shí)的,我們可以清楚的看到中斷向量和中斷管腳的關(guān)系,疊加了一個(gè)irq_base,這個(gè)irq_base就是通過(guò)初始化8259A時(shí)通過(guò)ICW設(shè)置的,完成從IRn到中斷向量的轉(zhuǎn)換。

一個(gè)中斷芯片通常連接有多個(gè)外設(shè),所以在某一個(gè)時(shí)刻,可能會(huì)有多個(gè)設(shè)備請(qǐng)求到來(lái),這時(shí)就有一個(gè)優(yōu)先處理哪個(gè)請(qǐng)求的問(wèn)題,因此,中斷就有了優(yōu)先級(jí)的概念。以8259A為例,典型的有2種中斷優(yōu)先級(jí)模式:

1)固定優(yōu)先級(jí)(Fixedpriority),即優(yōu)先級(jí)是固定的,從IR0到IR7依次降低,IR0的優(yōu)先級(jí)永遠(yuǎn)最高,IR7的優(yōu)先級(jí)永遠(yuǎn)最低。

2)循環(huán)優(yōu)先級(jí)(rotatingpriority),即當(dāng)前處理完的IRn其優(yōu)先級(jí)調(diào)整為最低,當(dāng)前處理的優(yōu)先級(jí)下個(gè),即IRn+1,調(diào)整為優(yōu)先級(jí)最高。比如,當(dāng)前處理的中斷是irq 2,那么緊接著irq3的優(yōu)先級(jí)設(shè)置為是最高,然后依次是irq4、irq5、irq6、irq7、irq1、irq2、irq3。假設(shè)此時(shí)irq5和irq2同時(shí)來(lái)了中斷,那么irq5顯然會(huì)被優(yōu)先處理。然后irq6被設(shè)置為優(yōu)先級(jí)最高,接下來(lái)依次是irq7、irq1、irq2、irq3、irq4、irq5。

理解了循環(huán)優(yōu)先級(jí)算法后,從8259A中獲取最高優(yōu)先級(jí)請(qǐng)求的代碼就很容易理解了:

  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; 

函數(shù)pic_get_irq分成2部分,第1部分是從當(dāng)前pending的中斷中取得最高優(yōu)先級(jí)的中斷,當(dāng)前之前需要濾掉被被屏蔽的中斷。第2部分是獲取正在被CPU處理的中斷的優(yōu)先級(jí)的中斷的優(yōu)先級(jí),通過(guò)這里,讀者更能具體的理解了8259A為什么需要這些寄存器記錄中斷的狀態(tài)。然后比較2個(gè)中斷的優(yōu)先級(jí),如果pending的優(yōu)先級(jí)高,那么就通過(guò)拉低INTR管腳電壓,向CPU發(fā)出中斷請(qǐng)求。

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

5 中斷ACK

在物理上,CPU在準(zhǔn)備處理一個(gè)中斷請(qǐng)求后,將通過(guò)管腳INTA向8259A發(fā)出確認(rèn)脈沖。同樣,軟件模擬上,也需要類似處理。在完成中斷評(píng)估后,準(zhǔn)備注入Guest前,需要向虛擬8259A執(zhí)行確認(rèn)狀態(tài)的操作,代碼如下:

  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); 

在中斷評(píng)估中,在調(diào)用函數(shù)kvm_pic_read_irq獲取評(píng)估的中斷結(jié)果后,馬上就調(diào)用函數(shù)pic_intack完成了中斷確認(rèn)的動(dòng)作。在收到CPU發(fā)來(lái)的中斷確認(rèn)后,8259A需要更新自己的狀態(tài),包括,因?yàn)橹袛嘁呀?jīng)開(kāi)始得到服務(wù)了,所以從IRR中清除等待服務(wù)請(qǐng)求。另外,需要設(shè)置ISR位,記錄正在被服務(wù)的中斷,但是這里稍微有一點(diǎn)點(diǎn)復(fù)雜。

設(shè)置ISR表示正在服務(wù)的位,表示處理器正在處理中斷。設(shè)置ISR的一個(gè)典型作用是中斷的作用是當(dāng)ISR處理完中斷,向8259A發(fā)送EOI時(shí),8259A知道正在處理的IRn,比如說(shuō)如果8259A使用的是循環(huán)優(yōu)先級(jí),那么需要最高優(yōu)先級(jí)為當(dāng)前處理的IRn的下一個(gè)。

如果8259A是AEOI模式,那么就無(wú)須設(shè)置ISR了,因?yàn)橹袛喾?wù)程序執(zhí)行完畢后不會(huì)發(fā)送EOI命令。所以在AEOI模式下(上面代碼的if分支),需要將收到EOI時(shí)8259A需要處理的邏輯完成,這部分內(nèi)容我們?cè)谙乱还?jié)會(huì)討論。

對(duì)于邊緣觸發(fā)的,進(jìn)入到ISR階段后,需要復(fù)位對(duì)于IRR。對(duì)于level trigger的,在收到中斷請(qǐng)求后8259A處理,不過(guò)多討論了,如果讀者有興趣,可以閱讀函數(shù)pic_set_irq1中關(guān)于水平觸發(fā)部分的邏輯。

6 關(guān)于EOI的處理

中斷服務(wù)程序執(zhí)行完成后,會(huì)向8259A發(fā)送EOI,告知8259A中斷處理完成。8259A在收到這個(gè)EOI時(shí),復(fù)位ISR,如果是采用的循環(huán)優(yōu)先級(jí),還需要設(shè)置變量priority_add,使其指向當(dāng)前處理IRn的下一個(gè):

  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被設(shè)置為AEOI模式,不會(huì)再收到后續(xù)中斷服務(wù)程序的EOI命令,那么8259A在收到CPU的ACK后,就必須把收到EOI命令時(shí)執(zhí)行的邏輯現(xiàn)在處理,前面看到AEOI模式不必設(shè)置ISR,所以這里也無(wú)需復(fù)位ISR,只需要調(diào)整變量priority_add,記錄最高優(yōu)先級(jí)位置即可:

  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 中斷注入

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

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

內(nèi)容

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中的這個(gè)字段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時(shí),KVM模塊檢查8259A是否有pending的中斷等待處理。這樣就有可能給中斷帶來(lái)一定的延遲,典型如下面2類情況:

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

(2)VCPU這個(gè)線程也許正在睡眠,比如Guest VCPU運(yùn)行hlt指令時(shí),就會(huì)切換回Host模式,線程掛起。

對(duì)于第1種情況,是多處理器系統(tǒng)下的一個(gè)典型情況,目標(biāo)CPU的正在運(yùn)行Guest。KVM需要想辦法觸發(fā)Guest發(fā)生一次VM exit,切換到Host。我們知道,當(dāng)處于Guest模式的CPU收到外部中斷時(shí),會(huì)觸發(fā)VM exit,由Host來(lái)處理這次中斷。所以,KVM可以向目標(biāo)CPU發(fā)送一個(gè)IPI中斷,觸發(fā)目標(biāo)CPU發(fā)生一次VM exit。

對(duì)于第2種情況,首先需要喚醒睡眠的VCPU線程,使其進(jìn)入CPU就緒隊(duì)列,準(zhǔn)備接受調(diào)度。對(duì)于多處理器系統(tǒng),然后再向目標(biāo)CPU發(fā)送一個(gè)“重新調(diào)度”的IPI中斷,那么被喚醒的VCPU線程很快就會(huì)被調(diào)度,執(zhí)行切入Guest的過(guò)程,從而完成中斷注入。

所以當(dāng)有中斷請(qǐng)求時(shí),虛擬中斷芯片將主動(dòng)“kick”一下目標(biāo)CPU,這個(gè)“踢”的函數(shù)就是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線程在睡眠,則“踢醒”他。如果目標(biāo)CPU運(yùn)行在Guest模式,則將其從Guest模式“踢”到Host模式,在VM entry時(shí)完成中斷注入,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線程睡眠在等待隊(duì)列上,則喚醒使其進(jìn)入CPU的就緒任務(wù)隊(duì)列。如果是多CPU的情況且目標(biāo)CPU處于Guest模式,則需要發(fā)送核間中斷。如果目標(biāo)CPU正在執(zhí)行Guest,那么這個(gè)IPI中斷將導(dǎo)致VM exit,從而在下一次進(jìn)入Guest時(shí),可以注入中斷。

事實(shí)上,目標(biāo)CPU無(wú)須執(zhí)行任何callback,也無(wú)須等待IPI返回,所以也無(wú)須使用smp_call_function_single,而是直接發(fā)送一個(gè)請(qǐng)求目標(biāo)CPU重新調(diào)度的IPI即可,因此后來(lái)直接調(diào)用了函數(shù)smp_send_reschedule。函數(shù)smp_send_reschedule簡(jiǎn)單直接,直接發(fā)送了一個(gè)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); 

本文轉(zhuǎn)載自微信公眾號(hào)「Linux閱碼場(chǎng)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux閱碼場(chǎng)公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: Linux閱碼場(chǎng)
相關(guān)推薦

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

虛擬化實(shí)戰(zhàn)Cluster

2013-05-27 09:52:26

虛擬化存儲(chǔ)虛擬化應(yīng)用

2010-05-26 14:42:54

桌面虛擬化

2023-09-19 10:00:34

Linux虛擬

2021-10-26 08:08:34

Node ExporLinux 監(jiān)控

2009-12-25 10:05:00

Linux虛擬化Xen虛擬化

2009-10-21 12:49:25

Linux壓縮打包方法

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網(wǎng)絡(luò)虛擬化

2019-07-24 10:06:15

Linux網(wǎng)絡(luò)虛擬化

2017-07-01 07:02:41

虛擬化遷移

2021-12-10 08:45:45

Linux GIC Linux 系統(tǒng)

2012-08-22 09:41:54

虛擬化

2009-10-19 16:39:19

Linux虛擬化

2020-01-02 10:34:32

Linux虛擬化Docker
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)