五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

虛擬化中的中斷機(jī)制:X86與PIC 8259A探索(上)

2023-08-05 15:15 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

本系列深入探討虛擬化中斷技術(shù),從X86架構(gòu)和PIC 8259A的基礎(chǔ),到IOAPIC和MSI的編程,再到MSIX技術(shù)與Broiler設(shè)備的實(shí)戰(zhàn)應(yīng)用,全面剖析中斷虛擬化的前沿進(jìn)展。

X86 中斷機(jī)制

在計算機(jī)架構(gòu)中,CPU 運(yùn)行的速度遠(yuǎn)遠(yuǎn)大于外設(shè)運(yùn)行的速度,在早期程序設(shè)計時,計算機(jī)如果要獲得外部設(shè)備完成 IO 情況,計算機(jī)就不得不通過輪詢來查詢外設(shè)完成情況,因此往往做了很多無用的外設(shè),從而導(dǎo)致計算機(jī)性能低下。為了解決這個問題引入了中斷機(jī)制,中斷?是為了解決外部設(shè)備完成某些工作之后通知 CPU 的一種機(jī)制,中斷大大解放了 CPU, IO 操作效率大增. 在 X86 架構(gòu)中,中斷是一種電信號,由硬件外設(shè)產(chǎn)生,并直接送入中斷控制器的輸入引腳,然后由中斷控制器下處理器發(fā)送相應(yīng)的信號。處理器一旦檢查到該信號,便會中斷當(dāng)前處理的工作轉(zhuǎn)而處理中斷。

X86 架構(gòu)的 CPU 為中斷提供了兩條外接引腳:?NMI?和?INTR. 其中 NMI 是不可屏蔽中斷,通常用于電源掉電和物理存儲器奇偶校驗(yàn); INTR 是可屏蔽中斷,可以通過設(shè)置中斷屏蔽位來進(jìn)行中斷屏蔽,主要用于接受外部硬件的中斷信號,這些信號由中斷控制器傳遞給 CPU。常見的中斷控制器有兩種: 8259A PIC 和 APIC.

PIC 8259A

X86 架構(gòu)中,傳統(tǒng)的 PIC(Programmable Interrupt Controller) 可編程中斷控制器由兩片 8259A 外部芯片以級聯(lián)的方式連接在一起,每個芯片可處理多達(dá) 8 個不同的 IRQ,Slave PIC 的 INT 輸出線連接到 Master PIC 的 IRQ2 引腳,所以可用的 IRQ 線的個數(shù)達(dá)到 15 個. 中斷引腳具有優(yōu)先級,其中 IR0 優(yōu)先級最高,IR7 優(yōu)先級最低。PIC 內(nèi)部具有三個重要的寄存器:

IRR(Interrupt Request Register) 中斷請求寄存器, 一共 8 bit 對應(yīng) IR0 ~ IR7 8 個中斷引腳. 某位置位代表收到對應(yīng)引腳收到中斷但還沒有提交給 CPU.?ISR(In Service Register) 服務(wù)中寄存器,一共 8 bit,某位置位代表對應(yīng)引腳的中斷已提交給 CPU 處理,但 CPU 還沒有處理完.?IMR(Interrupt Mask Register) 中斷屏蔽寄存器,一個 8 bit,某位置位代表對應(yīng)的引腳被屏蔽. 除此之外,PIC 還有一個 EOI 位,當(dāng) CPU 處理完一個中斷時,通過寫該 bit 告知 PIC 中斷處理完畢。PIC 向 CPU 遞交中斷的流程如下:

1. 一個或多個 IR 引腳產(chǎn)生電平信號,若中斷對應(yīng)的 IRR bit 沒有置位.

2. PIC 拉高 INT 引腳通知 CPU 中斷發(fā)生

3. CPU?通過 INTA 引腳應(yīng)答 PIC 表示中斷請求收到

4. PIC?收到 INTA 應(yīng)答之后,將 IRR 中具有高優(yōu)先級的位清零,并置位 ISR 對應(yīng)位

5. CPU 通過 INTA 引腳第二次發(fā)出脈沖,PIC 將最高優(yōu)先級 Vector?送到數(shù)據(jù)線上.

6. 等待 CPU 寫 EOI.

7. 收到 EIO 后,ISR 中最高優(yōu)先級位清零.

APIC: Local APIC and I/O APIC

PIC 可以在 UP(單處理器)平臺上工作,但無法用于 MP(多處理)平臺,為此?APIC(Advanced Programmable Interrupt Controller) 應(yīng)運(yùn)而生。APIC 由位于 CPU 中的本地高級可編程中斷控制器?LAPIC(Local Advanced Programmable Interrupt Controller) 和位于主板南橋中 I/O 高級可編程中斷控制器?I/O APIC(I/O Advanced Programmable Interrupt Controller) 兩部分構(gòu)成,他們的關(guān)系如上圖.

每個 Logical Processor 邏輯處理器都有自己的 Local APIC,每個 local APIC 包含了一組 Local APIC 寄存器,用于控制 kicak 和 external 中斷的產(chǎn)生、發(fā)送和接受等,也用于產(chǎn)生和發(fā)送 IPI。Local APIC 寄存器組以 MMIO 形式映射到系統(tǒng)的存儲域空間,因此可以像操作物理內(nèi)存一樣訪問。Local APIC 寄存器在存儲域的起始物理地址為 0xFEE0000; 在 x2APIC 模式的 Local APIC 寄存器映射到 MSR 寄存器組來代替,因此可以使用 RDMSR 和 WRMSR 指令來訪問 Local APIC 寄存器。

Local APIC 由一組 LVT(Local vector table) 寄存器用來產(chǎn)生和接口 Local interrupt source. 由 LVT 的 LINT0 和 LINT1 寄存器對應(yīng)著處理器 LINT0 和 LINT1 Pin, 它們可以直接接受外部 I/O 設(shè)備或連接 8259A 兼容類的外部中斷控制器. 典型的 LINT0 作為處理器的 INTR Pin 接著外部的 8259 類的中斷控制器的 INTR 輸出端,LINT1 作為處理器的 NMI Pin 接外部設(shè)備的 NMI 請求.

IO APIC 通常有 24 個不具有優(yōu)先級的引腳用于連接外部設(shè)備,當(dāng)收到某個引腳的中斷信號之后,IO APIC 根據(jù)軟件設(shè)定的 PRT(Programmable Redirection Table) 表查找對應(yīng)引腳的 RTE(Redirection Table Entry). 通過 PTE 的各個字段,格式化出一條包含該中斷所有信息的中斷信息,再由系統(tǒng)總線發(fā)送給特定的 CPU 的 Local APIC,Local APIC 收到該信息之后擇機(jī)將中斷遞交給 CPU 處理. IO APIC 也有自己的寄存器,同樣也是通過 MMIO 映射到存儲域空間。在 APIC 系統(tǒng)中,中斷發(fā)起大致流程如下:

1. IO APIC 收到某個引腳產(chǎn)生的中斷信號

2. 查找 PRT 表獲得引腳對應(yīng)的 RTE

3. 根據(jù) RTE 個字段格式化出一條中斷信息,并確定發(fā)送給哪個 CPU 的 LAPIC

4. 通過系統(tǒng)總線發(fā)送中斷信息

5. Local APIC 收到中斷信息,判斷是否由自己接受

6. Local APIC 確認(rèn)接受,將 IRR 中對應(yīng)的位置位,同時確認(rèn)是否由 CPU 處理

7. 確認(rèn)由 CPU 處理中斷,從 IRR 中獲得最高優(yōu)先級中斷,將 ISR 中對應(yīng)的位置位,提交中斷。對于邊緣觸發(fā)的中斷,IRR 中對應(yīng)的位此時清零.

8. CPU 處理完中斷,軟件寫 EOI 寄存器告知中斷處理完成. 對于電平觸發(fā)中斷,IRR 中對應(yīng)的位清零. Local APIC 提交下一個中斷.

在 MP(多處理器) 平臺上,多個 CPU 要協(xié)同工作,處理器間中斷 (Inter-processor Interrupt,?IPI) 提供 CPU 之間相互通信的手段。CPU 可以通過 Local APIC 的 ICR(Interrupt Command Register, 中斷命令寄存器) 向指定的一個/多個 CPU 發(fā)送中斷. OS 通常使用 IPI 來完成諸如進(jìn)程轉(zhuǎn)移、中斷平衡和 TLB 刷新等任務(wù).

中斷重要概念

中斷可分為同步中斷(Synchronous Interrupt) 和異步中斷(Asynchronous Interrupt)。同步中斷是當(dāng)指令執(zhí)行時由 CPU 控制單元產(chǎn)生,之所以稱為同步,是因?yàn)橹挥性谝粭l指令執(zhí)行完畢后 CPU 才會發(fā)出中斷,發(fā)生中斷之后 CPU 立即處理,而不是發(fā)生在代碼指令執(zhí)行期間,比如系統(tǒng)調(diào)用; 異步中斷是指由其他硬件設(shè)備依照 CPU 時鐘信號隨機(jī)產(chǎn)生,即意味著中斷能夠在指令之間發(fā)生,中斷產(chǎn)生之后不能立即被 CPU 執(zhí)行.

在 Intel X86 架構(gòu)中,同步中斷稱為異常(exception), 異步中斷稱為中斷(Interrupt), 中斷可以分為可屏蔽中斷(Maskable Interrupt) 和不可屏蔽中斷(Nomaskable Interrupt). 異??煞譃??故障(Fault)、陷阱(Trap)和終止(abort)三類. 在 X86 架構(gòu)中每個中斷被賦予一個唯一的編號或者向量Vector(8 位無符號整數(shù)).

在 X86 架構(gòu)保護(hù)模式下,系統(tǒng)使用中斷描述表(Interrupt Descriptor Table, IDT) 表示中斷向量表,總共 256 個描述符,IDT 的索引稱為中斷向量 Vector. IDT 表實(shí)際就是一個大數(shù)組,IDTR 寄存器指明了 IDT 在物理內(nèi)存的位置以及長度,用于存放各種門(中斷門、陷阱門、任務(wù)門),這些門是中斷和異常通往各自處理函數(shù)的入口。

PIN/IRQ/GSI/VECTOR

PIN、IRQ、GSI 和 Vector 這幾個概念容易攪渾,IRQ?是 PIC 時代的產(chǎn)物,由于 ISA 設(shè)備通常是連接到固定的 PIC 引腳,所以說一個設(shè)備的 IRQ 實(shí)際就是指它連接的 PIC 引腳號。IRQ 暗示著中斷優(yōu)先級,例如 IRQ0 比 IRQ1 有著更高的優(yōu)先級。當(dāng)進(jìn)入到 APIC 時代,為了向前兼容,習(xí)慣用 IRQ 表示一個設(shè)備的中斷號,但對于 16 以下的 IRQ,可能不再與 IOAPIC 的引腳對應(yīng).?Pin?是引腳號,表示 IOAPIC 的引腳,PIC 時代類似的是 IRQ。Pin 的最大值受 IOAPIC 的引腳數(shù)限制,目前取值范圍是 [0, 23].?GSI(Global System Interrupt) 是 APIC 時代引入的概念,它為系統(tǒng)中的每個中斷源指定唯一的中斷號.

上圖中有 3 個 I/O APIC, IO-APIC0 具有 24 個引腳,其中 GSI Base 為 0, 每個 Pin 的?GSI=GSI_Base + Pin?, 故 IO-APIC0 的 GSI 范圍為 [0: 23]. IO-APIC1 具有 16 個引腳,GSI base 為 24,GSI 范圍為 [24, 39], 以此類推。APIC 要求 ISA 的 16 個 IRQ 應(yīng)該被 Identify map 到 GSI [0, 15].?Vector?是中斷中 IDT 表中的索引,是一個 CPU 概念,每個 IRQ (或 GSI) 都對應(yīng)一個 Vector。在 PIC 模式下,IRQ 對應(yīng)的?vector = Start_Vector + IRQ; 在 APIC 模式下, IRQ/GSI 的 vector 由操作系統(tǒng)分配.

操作系統(tǒng)對中斷/異常的處理流程

雖然各種操作系統(tǒng)對中斷/異常處理的實(shí)現(xiàn)不同,但基本流程遵循如下順序: 一個中斷或異常發(fā)生,打斷當(dāng)前正在執(zhí)行的任務(wù)

1. CPU 通過 vector 索引 IDT 表得到對應(yīng)的門,并獲得其處理函數(shù)的入口地址

2. 保存被打斷任務(wù)的上下文,并跳轉(zhuǎn)到中斷處理函數(shù)進(jìn)行執(zhí)行

3. 如果是中斷,處理完成后需要寫 EOI 寄存器應(yīng)答,異常不需要

4. 恢復(fù)被打斷任務(wù)的上下文,準(zhǔn)備返回

5. 從中斷/異常的處理函數(shù)返回,恢復(fù)被打斷的任務(wù),使其繼續(xù)執(zhí)行



【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?



X86 中斷虛擬化

在虛擬化場景中,VMM 也需要為 Guest OS 展現(xiàn)一個與物理中斷架構(gòu)類似的虛擬中斷架構(gòu)。如上圖展示虛擬機(jī)的中斷架構(gòu),和物理平臺一樣,每個 VCPU 都對應(yīng)一個虛擬 Local APIC 用于接收中斷. 虛擬平臺也包含了虛擬 I/O APIC 或者虛擬 PIC 用于發(fā)送中斷。和 VCPU 一樣,虛擬 Local APIC、虛擬 I/O APIC 和 虛擬 PIC 都是由 VMM 維護(hù). 當(dāng)虛擬設(shè)備需要發(fā)送中斷時,虛擬設(shè)備會調(diào)用虛擬 I/O APIC 的接口發(fā)送中斷,虛擬 I/O APIC 根據(jù)中斷請求,挑選出相應(yīng)的虛擬 Local APIC, 調(diào)用其接口發(fā)出中斷請求,虛擬 Local APIC 進(jìn)一步利用 VT-x 的事件注入機(jī)制將中斷注入到相應(yīng)的 VCPU. 由此可見中斷虛擬化主要任務(wù)就是實(shí)現(xiàn)虛擬 PIC、虛擬 I/O APIC 和虛擬 Local APIC,并且實(shí)現(xiàn)虛擬中斷的生成、采集和注入的過程。

PIC 8259A 虛擬化

IOAPIC 虛擬化

在 PCI/PCIe 設(shè)備上不僅支持?Line-Based PCI Interrupt Routing, 也支持更為現(xiàn)代的?PCI Message-Signalled Interrupt, 讓設(shè)備支持超過 IOAPIC/PIC 更多的中斷,MSI/MSIX 更好的服務(wù) PCI Function,使中斷直接送到指定的 LAPIC.

MSI 中斷虛擬化

MSIX 中斷虛擬化

當(dāng) Broiler 觸發(fā)了 PIC/IOAPIC 中斷,需要讓虛擬機(jī) VM-EXIT 之后再 VM-ENTRY,將需要注入的中斷寫入到 VMCS 的 VM_ENTRY_INTR_INFO_FIELD 域中,VM-ENTRY 的時候會檢查該域是否有中斷需要注入,如果有 VM-ENTRY 之后理解觸發(fā)對應(yīng)的中斷, 當(dāng) Guest OS 處理完中斷之后,需要寫入 EOI,那么同樣導(dǎo)致 VM-EXIT. 如果 Broiler 提出注入中斷的請求之后,虛擬機(jī)正處于休眠時,那么 KVM 會模擬發(fā)送 IPI 中斷,讓虛擬機(jī)發(fā)送 VM-EXIT. 隨機(jī)硬件功能的不斷完善,開發(fā)者在考慮是否可以借助硬件,在不 VM-EXIT 的情況下進(jìn)行中斷注入,APICv 的映入很好的解決了這個問題,并且使用 Posted Interrupt 方式進(jìn)行中斷注入,使虛擬機(jī)在不發(fā)生 VM-EXIT 的請求下完成中斷的注入和 EOI.

VM_ENTRY_INTR_INFO_FIELD 中斷注入(TODO)

IPI 虛擬化(TODO)

APIv 虛擬化(TODO)

Posted Interrupt(TODO)

PIC 8259A 虛擬化

虛擬 PIC 本質(zhì)上是一個虛擬設(shè)備,因此可以放在用戶空間側(cè)模擬(QEMU/Broiler),也可以放在內(nèi)核側(cè) KVM 來模擬。根據(jù) PIC 硬件規(guī)范,在軟件上模擬出虛擬 PIC 與物理 PIC 一樣的接口。Broiler 將 PIC 的設(shè)備模擬放到了 KVM 里實(shí)現(xiàn),因此 vPIC 是一個 In-Kernel 設(shè)備,vPIC 虛擬了 8259A 對中斷的基本處理模擬,另外還包括 Broiler 向 vPIC 提交一個中斷,vPIC 根據(jù)中斷優(yōu)先級選擇合適的中斷進(jìn)行注入,注入的過程會促使 VM 發(fā)送 VM-EXIT,并將需要注入的中斷寫入到虛擬機(jī) VMCS 指定的域中,當(dāng)虛擬機(jī)再次 VM-ENTRY 時檢查到有中斷注入,那么虛擬機(jī)運(yùn)行時觸發(fā)中斷。本節(jié)用于全面介紹 vPIC 中斷虛擬過程,并分作以下幾個章節(jié)進(jìn)行講解:

  • 8259A 中斷控制器編程

  • vPIC 創(chuàng)建

  • Broiler vPIC 中斷配置

  • Broiler 設(shè)備使用 vPIC 中斷

  • vPIC 中斷注入

8259A 中斷控制器編程

x86 架構(gòu)中 vPIC 通過模擬 8259A 中斷控制器的邏輯來實(shí)現(xiàn)中斷模擬,并側(cè)重模擬 8259A 內(nèi)部的寄存器處理邏輯,而 8259A 中斷控制器編程側(cè)重從操作系統(tǒng)對 8259A 的使用進(jìn)行描述,那么本節(jié)用于介紹 8259A 的編程邏輯,這對 vPIC 的模擬有一定的幫助。根據(jù) PIC 硬件規(guī)范,PIC 主要為軟件提供了以下幾個接口用于操作 PIC:

  • 4 個初始化命令字(Initialization Command Words): ICW1/ICW2/ICW3/ICW4

  • 3 個操作命令字(Operation Command Words): OCW1/OCW2/OCW3

ICW1 寄存器用于初始化 8259A 的連接方式和中斷觸發(fā)方式,其中 BIT0 用于指明 ICW4 是否啟用,在 X86 架構(gòu)該 BIT 必須置位. BIT1 用于指明系統(tǒng)中是單片 8259A 還是級聯(lián)兩塊 8259A,置位表示單片,清零則表示級聯(lián) 2 塊 8259A. BIT3 用于指明中斷的觸發(fā)方式,置位表示電平,清零則表示邊緣觸發(fā)。BIT4 在 x86 架構(gòu)下必須置位. ICW1 需要寫入主 8259A 的 0x20 端口和從 8259A 的 0xA0 端口.

ICW2 寄存器用于設(shè)置初始中斷向量,其中 BIT0-BIT0 用于指明中斷號。ICW2 需要寫入主 8259A 的 0x21 端口和從 8259A 的 0xA1 端口.

ICW3 寄存器用于指定主從 8259A 的級聯(lián)引腳,在 x86 架構(gòu)中,從 8259A 級聯(lián)到主 8259A 的 IRQ2 引腳上,因此主 8259A 的 ICW3 寄存器值為 0x04, 從 8259A 的 ICW3 寄存器值為 0x02. ICW3 需要寫入主 8259A 的 0x21 端口和從 8259A 的 0xa1 端口.

ICW4 寄存器用于初始化 8259A 數(shù)據(jù)連接方式和中斷觸發(fā)方式。在 x86 架構(gòu)中 BIT0 uPM 必須置位; BIT1 位 AEOA,如果置位中斷自動結(jié)束 AUTO EOI,如果清零則 8259A 需要收到 EOI 才算中斷處理完成; BIT2-BIT3 用于指明緩存模式,BIT3 清零,那么非緩存模式,BIT3 置位則緩存模式; BIT2 用于指明主從 8259A 的緩存模式,置位表示主 8259A,清零則表示從 8259A. BIT4 用于設(shè)置嵌套模式,如果置位則表示特殊全嵌套模式,清零則全嵌套模式。ICW4 寄存器需要寫入主 8259A 的 0x21 端口和從 8259A 的 0xA1 端口.

上圖是簡單的代碼演示主從 8259A 的初始化.

OCW1 寄存器用于屏蔽連接在 8259A 上的中斷源,某位置位則屏蔽對應(yīng)的中斷源,某個清零則不屏蔽對應(yīng)的中斷源. OCW1 寄存器需要寫入主 8259A 的 0x20 端口和從 8259A 的 0xA0 端口.

OCW2 寄存器用于設(shè)置中斷結(jié)束方式和優(yōu)先級模式。BIT0-BIT2 用于指明中斷優(yōu)先級。BIT3-BIT4 在 X86 架構(gòu)必須為 0. BIT5 EOI 手動結(jié)束中斷時有意義,該位置位表示中斷結(jié)束就清除 ISR 相應(yīng)位, 改位清零則無意義. BIT6 SL 位,該位置位使用 BIT0-BIT2 指定需要結(jié)束的中斷, 該位清零則結(jié)束正在處理的中斷,并將 ISR 優(yōu)先級最高的位清零. BIT7 R 位,該位置位則采用循環(huán)優(yōu)先級方式,每個 IR 接口優(yōu)先級在 0-7 循環(huán),該位清零則采用固定優(yōu)先級,IR 接口號越低優(yōu)先級越高. OCW2 寄存器需要寫入主 8259A 的 0x20 端口和從 8259A 的 0xA0 端口.

OCW3 寄存器用于設(shè)置特殊屏蔽方式以及查詢方式。BIT0 RIS 標(biāo)志位,該位置位則讀取 ISR 寄存器的值,該位清零則讀取 IRR 寄存器. BIT1 RR 標(biāo)志位,該位置位則讀取寄存器,該位清零則無意義。BIT2 P 標(biāo)志位,該位置位則表示中斷查詢命令查詢當(dāng)前最高中斷優(yōu)先級,該位清零則無意義; BIT3-BIT4 在 X86 架構(gòu)固定為?0x01;?BIT5 SMM 標(biāo)志位,該位置位表示工作于特殊屏蔽模式,該位清零則未工作于特殊屏蔽模式; BIT6 ESMM 標(biāo)志位,該位置位則表示啟用特殊屏蔽模擬, 該位清零則表示關(guān)閉特殊屏蔽模式. OCW3 需要寫入主 8259A 0x20 端口和從 8259A 的 0xA0 端口.

vPIC 創(chuàng)建

vPIC 的創(chuàng)建主要分作兩部分,第一個部分是在內(nèi)核空間虛擬一個 vPIC 設(shè)備,其二是對默認(rèn)引腳的模擬. Broiler 在初始化階段通過調(diào)用 kvm_init() 函數(shù),并使用 iotcl 方式向 KVM 傳入命令 KVM_CREATE_IRQCHIP 用于創(chuàng)建 vPIC. KVM 的 kvm_arch_vm_ioctl() 函數(shù)收到了 Broiler 傳遞下來的命令,然后找到對應(yīng)的處理分支 KVM_CREATE_IRQCHIP, 分支里有兩個主要的函數(shù)分支,其中 kvm_pic_init() 函數(shù)用于在 KVM 內(nèi)虛擬一個 vPIC 設(shè)備,用于模擬 vPIC 的 IO 端口; 另外一個函數(shù)是 kvm_setup_default_irq_routing() 函數(shù),該函數(shù)用于對 vPIC 默認(rèn)引腳的模擬.

KVM 使用 struct kvm_pic 數(shù)據(jù)結(jié)構(gòu)描述一個 vPIC 設(shè)備,pics[] 成員表示 x86 架構(gòu)中存在兩塊 8259A 芯片,其中 pics[0] 作為主 PIC,而 pics[1] 作為從 PIC,pics[] 使用 struct kvm_kpic_state 數(shù)據(jù)結(jié)構(gòu)進(jìn)行描述,該數(shù)據(jù)結(jié)構(gòu)的成員用于模擬 PIC 設(shè)備內(nèi)部的寄存器,例如 irr 對應(yīng) PIC 的 Interrupt request Register, imr 對應(yīng) PIC 的 Interrupt Mask Register, 以及 isr 對應(yīng) PIC 的 In Service Register 等. output 成員表示 vPIC 芯片是否有新中斷需要注入,dev_master/dev_slave/dev_eclr 則是 KVM 模擬的三個設(shè)備,前兩個分別模擬主 PIC 設(shè)備和從 PIC 設(shè)備。irq_states[] 數(shù)組則是對 vPIC 每個引腳狀態(tài)的模擬.

回到 KVM 對 vPIC 設(shè)備模擬的調(diào)用邏輯,KVM 調(diào)用 kvm_pic_init() 函數(shù)對 vPIC 設(shè)備進(jìn)行模擬,其通過調(diào)用 kvm_iodevce_init() 函數(shù)對主 vPIC 設(shè)備、從 vPIC 設(shè)備和 eclr 設(shè)備進(jìn)行模擬,并為這些設(shè)備提供了 IO 端口讀寫進(jìn)行模擬,IO 端口讀操作最終通過 pcidev_read() 函數(shù)實(shí)現(xiàn),IO 端口寫操作最終通過 pcidev_write() 函數(shù)實(shí)現(xiàn). 函數(shù)通過調(diào)用 kvm_io_bus_register_dev() 函數(shù)將 0x20/0x21/0xa0/0xa1 注冊到 KVM,只要 Guest OS 訪問這幾個 IO 端口就會因?yàn)?EXIT_REASON_IO_INSTRUCTION 原因 VM-EXIT.

根據(jù) 8259A 芯片的邏輯,picdev_read() 函數(shù)實(shí)現(xiàn)了對 ICW/OCW 寄存器讀模擬,可以看到對端口 0x20/0x21/0xa0/0xa1 的讀取是根據(jù)地址推算出主從 vPIC,然后通過 pic_ioport_read() 函數(shù)從其對應(yīng)的 struct kvm_kpic_state 數(shù)據(jù)接口中讀取指定成員, 以此完成 IO 端口讀模擬.

同理根據(jù) 8259A 芯片的邏輯,picdev_write() 函數(shù)實(shí)現(xiàn)了對 ICW/OCW 寄存器寫模擬,可以看到對端口 0x20/0x21/0xa0/0xa1 的寫是根據(jù)地址推算出主從 vPIC,然后通過 pic_ioport_write() 函數(shù)從其對應(yīng)的 struct kvm_kpic_state 數(shù)據(jù)接口中寫入指定成員,以此完成 IO 端口寫模擬.

vPIC 創(chuàng)建的最后一步是對默認(rèn)引腳的模擬,通過之前的分析可以知道 x86 架構(gòu)存在兩片級聯(lián)的 8259A 芯片,其中一片做主片另外一片做從片。每個 8259A 包含 8 個引腳,其中主片的 Pin2 連接從片的 INT 引腳。KVM 對引腳的模擬通過調(diào)用 kvm_set_default_irq_routing() 函數(shù)實(shí)現(xiàn),該函數(shù)包含了公共路徑代碼,對 vPIC 引腳的模擬主要在 setup_routing_entry() 函數(shù)中實(shí)現(xiàn),其通過調(diào)用函數(shù) kvm_set_routing_entry() 函數(shù)將 vPIC 對應(yīng)的引腳中斷模擬函數(shù)設(shè)置為 kvm_set_pic_irq() 函數(shù),另外將模擬好的引腳加入到 hash 鏈表上。

KVM 使用 struct kvm_irq_routing_table 數(shù)據(jù)結(jié)構(gòu)維護(hù) vPIC 和 vIOAPIC 的引腳,其中 chip[] 數(shù)組存儲了所有引腳信息,nr_rt_entries 成員則表示引腳的總數(shù),另外使用 map[] 哈希鏈表維護(hù)了引腳和 Chip 的映射關(guān)系,即引腳是屬于 vPIC 還是 vIOAPIC. KVM 使用 struct kvm_kernel_irq_routing_entry 數(shù)據(jù)結(jié)構(gòu)描述一個 GSI,GSI 可以對應(yīng) vPIC 的一個引腳,也可以描述 vIOAPIC 的一個引腳,其成員 gsi 表示引腳的 GSI 號,set 回調(diào)函數(shù)則表示引腳的中斷模擬,irqchip 成員則表示引腳對應(yīng)的 CHIP 信息和引腳信息,最后 link 成員用于將模擬的引腳統(tǒng)一維護(hù)在 KVM struct kvm_irq_routing_table 數(shù)組結(jié)構(gòu) map[] 哈希鏈表上.

struct kvm_irq_routing_table 數(shù)據(jù)架構(gòu)維護(hù)了 vPIC 和 vIOAPIC 引腳與 GSI 的映射關(guān)系,通過這個關(guān)系可以從設(shè)備的引腳推出 GSI 號,而 map[] 哈希鏈表則維護(hù)了 GSI 與引腳的關(guān)系,在 KVM 中模擬的 vPIC 和 vIOAPIC 的引腳是可以重疊的,因此一個 GSI 號可能對應(yīng) vPIC 或者 vIOAPIC 的引腳,或者同時對應(yīng) vPIC 和 vIOAPIC 的引腳,那么使用一個 map[] hash 鏈表,并使用 GSI 作為 key, struct kvm_kernel_irq_routing_entry 作為 hash 鏈表的成員, 最終形成了上圖的邏輯結(jié)構(gòu).

KVM 默認(rèn)支持的中斷設(shè)備的引腳如上圖,從 ROUTING_ENTRY2 宏的定義可以看出 vPIC 的引腳是 [0, 15], vIOAPIC 的引腳范圍是 [0, 23],那么 vPIC 和 vIOAPIC 之間共享了 [0, 15] 的引腳.

Broiler vPIC 中斷配置

KVM 在創(chuàng)建 vPIC 的時候,已經(jīng)默認(rèn)創(chuàng)建了一套 vPIC 和 vIOAPIC 引腳映射邏輯,雖然可以使用,但作為 HypV 軟件,Broiler 還是可以自定義映射 vPIC 的引腳與 GSI 映射邏輯。如上圖是 Broiler 虛擬的中斷引腳連接邏輯,Broielr 規(guī)劃的中斷引腳邏輯是: 主 vPIC 的 8 個引腳獨(dú)占 GSI0-GSI7, 從 vPIC 的前 4 個引腳獨(dú)占 GSI8-GIS11, 后 4 個引腳與 vIOAPIC 前 12-15 引腳共享 GSI12-GSI15, vIOAPC 16-24 引腳獨(dú)占 GSI16-GSI24.

Broiler 在 broiler_irq_init() 函數(shù)中對 GSI 進(jìn)行了重映射,16-20 行用于映射 GSI0-GSI7 給 IRQCHIP_MASTER,33-25 行用于映射 GSI8-GSI15 給 IRQCHIP_SLAVE, 27-29 行用于映射 GSI12-GSI24 個 IRQCHIP_IOAPIC, 可以看出從 vPIC 和 vIOAPIC 共享了 GSI12-GSI15, 最后調(diào)用 ioctl 向 KVM 傳入 KVM_SET_GSI_ROUTING 使映射生效.

vPIC 的模擬位于內(nèi)核是一個 In-Kernel 的設(shè)備,當(dāng) Broiler 對中斷映射有了更改,需要通過 ioctl() 函數(shù)傳入 KVM_SET_GSI_ROUTING 命令來更新 vPIC 的中斷引腳與 GSI 的映射。KVM 的 kvm_vm_ioctl() 函數(shù)收到 KVM_SET_GSI_ROUTING 命令之后,調(diào)用 kvm_set_irq_routing() 函數(shù)進(jìn)行更新,更新核心通過 kvm_set_routing_entry() 函數(shù),其會將主從 vPIC 的引腳的中斷模擬函數(shù)設(shè)置為 kvm_set_pic_irq() 函數(shù).

struct kvm_irq_routing_table 數(shù)據(jù)架構(gòu)維護(hù)了 vPIC 引腳與 GSI 的映射關(guān)系,通過這個關(guān)系可以從設(shè)備的引腳推出 GSI 號,而 map[] 哈希鏈表則維護(hù)了 GSI 與引腳的關(guān)系,在 KVM 中模擬的 vPIC 的引腳是可以重疊的,因此一個 GSI 號可能對應(yīng) vPIC 或者 vIOAPIC 的引腳,或者同時對應(yīng) vPIC 和 vIOAPIC 的引腳,那么使用一個 map[] hash 鏈表,并使用 GSI 作為 key, struct kvm_kernel_irq_routing_entry 作為 hash 鏈表的成員, 映射完畢之后 Broiler 的 vPIC 引腳連接如上.

Broiler 設(shè)備使用 vPIC 中斷

Broiler 配置完 vPIC 中斷之后,那么接下來就是在設(shè)備中使用 vPIC 提供的中斷,Broiler 目前支持的設(shè)備包括 PCI 設(shè)備和 IO 端口設(shè)備。對于 IO 端口設(shè)備 Broiler 提供了 irq_alloc_from_irqchip() 函數(shù)從 vPIC 中分配中斷,而 PIC 設(shè)備如果使用 INTX 中斷的話,Broiler 提供了 pci_assign_irq() 函數(shù)從 vPIC 中分配中斷。對于分配的 vPIC 中斷,中斷的觸發(fā)分為電平觸發(fā)和邊緣觸發(fā),Broiler 模擬的設(shè)備可以使用 broiler_irq_line() 函數(shù)進(jìn)行電平觸發(fā),而使用 broiler_irq_trigger() 函數(shù)進(jìn)行邊緣觸發(fā)。那么接下來以 Broiler 一個 IO 端口設(shè)備使用電平觸發(fā)方式給 Guest OS 前端驅(qū)動發(fā)中斷的案例進(jìn)行講解:

從虛擬機(jī)內(nèi)部看到系統(tǒng) IO 空間中,主 PIC 映射到端口 0x20-0x21,從 PIC 映射到端口 0xa0-0xa1. 接下來在 Broiler 虛擬一個設(shè)備,其包含一個異步 IO,對該 IO 進(jìn)行讀操作會觸發(fā)一個中斷(源碼位置: foodstuff/Broiler-interrup-vPIC.c)


Broiler 模擬了一個設(shè)備,該設(shè)備包含了一段 IO 端口,其范圍是 [0x6020, 0x6030], 里面包含了兩個寄存器,第一個寄存器 IRQ_NUM_REG 用于獲得設(shè)置使用的 IRQ,第二個寄存器是一個異步 IO,對該寄存器進(jìn)行寫操作會觸發(fā)中斷。Broiler 在 70 行調(diào)用 irq_alloc_from_irqchip() 函數(shù)從 vPIC 中分配一個 IRQ 號,然后調(diào)用 broiler_irq_line() 函數(shù)將 IRQ 的電平設(shè)置為低低電平。Broiler 在 74-103 行實(shí)現(xiàn)一個異步 IO, 如果 Guest OS 對該 IO 端口進(jìn)行寫操作,那么會喚醒 irq_threads 線程,線程的作用是向 Guest OS 注入一個 IRQ 中斷,可以看到 45 行調(diào)用 broiler_irq_line() 函數(shù)拉高電平以便模擬高電平,進(jìn)而產(chǎn)生中斷,此時 KVM 會收到 ioctl 命令 KVM_IRQ_LINE, 然后 KVM 向 Guest OS 注入中斷. 那么有了 Broiler 側(cè)的模擬設(shè)備,接下來就是 Guest OS 內(nèi)部驅(qū)動來處理中斷,BiscuitOS 已經(jīng)支持該驅(qū)動程序的部署,其部署邏輯如下:


Broiler-vPIC-interrupt-default Gitee @link

Guest OS 驅(qū)動源碼比較簡單,首先通過 request_resource() 向 IO 空間注冊了設(shè)備的 IO 端口,其 IO 端口域?yàn)?[0x6020, 0x6030], 驅(qū)動接著調(diào)用 ioport_map() 函數(shù)將 IO 端口映射到虛擬內(nèi)存,接著在 52 行從設(shè)備的 0x6024 端口獲得設(shè)備使用的中斷號,接著在 53 行調(diào)用 request_irq() 進(jìn)行中斷處理函數(shù)注冊,此時中斷的觸發(fā)方式設(shè)置為高電平觸發(fā),中斷處理函數(shù)是 Broiler_irq_handler(),其內(nèi)部用于在接收到中斷之后打印中斷號。最后驅(qū)動向端口 0x6020 進(jìn)行寫操作,那么這會觸發(fā)設(shè)備向 CPU 發(fā)送一個中斷,接下來在 BiscuitOS 實(shí)踐該案例:

Broiler 啟動 BiscuitOS 系統(tǒng)之后,加載驅(qū)動 Broiler-vPIC-interrupt-default.ko, 可以看到驅(qū)動加載成功,等待 5s 之后中斷處理程序收到設(shè)備發(fā)來的中斷,此時可以看到 IRQ 為 6,接著查看 IO 空間,可以看到端口 0x6020-0x6030 分配給 “Broiler PIO vPIC” 使用。最后可以查看 /proc/interrupts 節(jié)點(diǎn)獲得中斷映射關(guān)系,也就是開篇的圖片可以看到 Broiler-PIO-vPIC 使用的中斷.

vPIC 中斷注入

vPIC 中斷注入流程流程如上圖,分作兩個大的部分,第一部分是 Broiler 通過 ioctl() 函數(shù)向 KVM 傳入 KVM_IRQ_LINE 命令,以此告訴 KVM 進(jìn)行 vPIC 中斷注入,此時 Broiler 和 KVM 是異步運(yùn)行的,此時 Guest OS 在運(yùn)行并沒有 VM_EXIT. 當(dāng) KVM 的 vPIC 通過中斷評估之后決定將某個中斷注入到 Guesst,這時會向 KVM 發(fā)送 KVM_REQ_EVENT 請求,并發(fā)送 IPI 中斷讓虛擬機(jī)發(fā)生 VM-EXIT; 中斷注入的第二階段則是讓虛擬機(jī) VM-EXIT 之后再次 VM_ENTRY, 此時 KVM 會檢查 VM_ENTRY_INTR_INFO_FIELD 域里是否注入了中斷,如果注入那么虛擬機(jī) VM-ENTRY 之后就觸發(fā)中斷,因此第二部分的任務(wù)就是在 VM-ENTRY 之前向 VM_ENTRY_INTR_INFO_FIELD 寫入要注入的中斷,KVM 通過在 vmx_inject_irq() 函數(shù)中調(diào)用 vmcs_write32() 函數(shù)向 VM_ENTRY_INTR_INFO_FIELD 域中寫入了要注入的中斷,最后虛擬機(jī) VM-ENTRY 之后就收到中斷,這個階段 KVM 是同步運(yùn)行的. 那么接下來對流程進(jìn)行細(xì)節(jié)分析.

pic_set_irq1() 函數(shù)用于模擬中斷產(chǎn)生之后,vPIC 的 Interrupt Request Register 收到中斷之后將對應(yīng) bit 置位的模擬,從函數(shù)的實(shí)現(xiàn)可以看出 IRR 寄存器接收電平觸發(fā)和邊緣觸發(fā)的中斷,93-100 行處理電平觸發(fā)的中斷,如果模擬中斷是電平觸發(fā),那么如果 level 為真,即中斷引腳產(chǎn)生了一個高電平,那么 IRR 寄存器就將引腳對應(yīng)的 bit 置位,并將 last_irr 寄存器對應(yīng)的 bit 也置位; 反之如果是一個低電平,那么中斷消除,于是將 IRR 寄存器對應(yīng)的 bit 清零. 如果是邊緣觸發(fā),那么函數(shù)使用 102-110 行進(jìn)行中斷邊緣觸發(fā)模擬,103-108 行如果此時 last_irr 對應(yīng)引腳之前是低電平,那么此時中斷引腳觸發(fā)一個上升沿信號,那么此時 IRR 寄存器將引腳對應(yīng)的 bit 置位; 反之只使用 last_irr 寄存器記錄電平信號; 108-109 行產(chǎn)生一個低電平,并且下降沿不觸發(fā)中斷,那么只使用 last_irr 寄存器記錄電平信息. 函數(shù)在 112 行檢查 Interrupt mask register 寄存器是否已經(jīng)屏蔽該中斷,如果屏蔽直接返回 -1; 反之返回 1.

pic_get_irq() 函數(shù)用于中斷評估,以此決定是否有中斷送往 CPU 進(jìn)行處理. 函數(shù) 137 行的作用是模擬通過 IRR 寄存器和 IMR 寄存器獲得不被屏蔽可以處理的中斷,函數(shù) 138 行則根據(jù)優(yōu)先級策略獲得最高優(yōu)先級。函數(shù)接著在 146 行模擬從 ISR 寄存器獲得當(dāng)前正在處理的中斷,然后獲得對應(yīng)的優(yōu)先級,最后在 150-156 行的優(yōu)先級判斷該 PIC 是否有中斷送往 CPU 處理.

pic_update_irq() 函數(shù)用于判斷主從 vPIC 中是否有中斷產(chǎn)生,如果有就進(jìn)行標(biāo)記,以便虛擬機(jī)再次 VM-ENTRY 時注入中斷。函數(shù)在 167 行判斷從 vPIC 是否有中斷需要 CPU 處理,如果有則進(jìn)行入 168 分支進(jìn)行處理,函數(shù)在該分支將主 vPIC 的 IRQ2 置位,以此模擬從 vPIC 產(chǎn)生中斷并切主 vPIC 的 2 號引腳接受該中斷。函數(shù)接著在 175 行再次調(diào)用 pic_get_irq() 函數(shù)判斷主 vPIC 是否有中斷需要 CPU 處理,如果有則調(diào)用 pic_irq_request() 函數(shù)告訴 KVM 主 vPIC 有中斷需要 CPU 處理.

pic_unlock() 函數(shù)的作用是向 KVM 提出 KVM_REQ_EVENT 需求,并讓虛擬機(jī) VM-EXIT. 函數(shù)在 62 行調(diào)用 kvm_make_request() 函數(shù)向 KVM 提出 KVM_REQ_EVENT 需求,KVM 在 VM-ENTRY 之間如果檢查到有 KVM_REQ_EVENT 需求,那么其會檢查 VM_ENTRY_INTR_INFO_FIELD 域是否有中斷需要注入. 中斷的注入只能在 VM-ENTRY 時才能注入,那么對于沒有 VM-EXIT 的虛擬機(jī),KVM 通過讓 VCPU 發(fā)送 IPI 中斷讓虛擬機(jī) VM-EXIT。至此 vPIC 的中斷主動注入已經(jīng)完成,接下來就是虛擬機(jī)再次 VM-ENTRY 之前將需要注入的中斷寫入 VM_ENTRY_INTR_INFO_FIELD.

虛擬機(jī)再次 VM-ENTRY 之前會調(diào)用 vcpu_enter_guest() 函數(shù)進(jìn)行檢查,此時函數(shù)在 8865 行發(fā)現(xiàn)有 KVM_REQ_EVENT 請求,那函數(shù) 8873 行調(diào)用 inject_pending_event() 函數(shù)進(jìn)行中斷注入.

在 inject_pending_event() 內(nèi)部,函數(shù)在 8310 行通過 kvm_cpu_has_injectable_intr() 函數(shù)知道 vPIC 中斷需要注入,那么函數(shù)進(jìn)入 8311 分支進(jìn)行處理,對于 vPIC 中斷函數(shù)進(jìn)入 8315 分支進(jìn)行處理,函數(shù)首先調(diào)用 kvm_queue_interrupt() 函數(shù)獲得需要注入中斷的 vector,然后調(diào)用 vmx_inject_irq() 函數(shù)向 VMCS 的 VM_ENTRY_INTR_INFO_FIELD 域注入中斷.

vmx_inject_irq() 函數(shù)用于最終的中斷注入操作,從函數(shù)實(shí)現(xiàn)可以看到 4500 行獲得需要注入的中斷號,然后在 4519 行調(diào)用 vmcs_write32() 函數(shù)向 irq 在 VM_ENTRY_INTR_INFO_FIELD 域中對應(yīng)的 bit 置位,至此 vPIC 的中斷注入完結(jié), 虛擬機(jī) VM-ENTRY 檢查到該域有置位,那么虛擬機(jī) RESUME 之后就向虛擬機(jī)注入一個中斷.

VM_ENTRY_INTR_INFO_FIELD 域是一個 32 位域,Vector 字段用于描述注入的中斷向量號, Deliver err Code 域指明是否需要向 Guest 的堆棧中寫入錯誤碼,Valid 域用于表示需要 向 Guest OS 注入中斷,VM-EXIT 時自動清除該域,Interrupt type 指明注入中斷的類型>,具體支持如下:

  • 0: External Interrupt

  • 1: Reserved

  • 2: NMI

  • 3: Hardware exception (e.g. #PF)

  • 4: Software interrupt (INT n)

  • 5: Privileged software exception (INT 1)

  • 6: Software exception (INT 3 or INTO)

  • 7: Other event

原文作者:Linux內(nèi)核之旅



虛擬化中的中斷機(jī)制:X86與PIC 8259A探索(上)的評論 (共 條)

分享到微博請遵守國家法律
襄城县| 彩票| 凤山县| 错那县| 久治县| 祁连县| 镇坪县| 梁平县| 车险| 上思县| 青州市| 滕州市| 湄潭县| 青海省| 确山县| 延吉市| 抚顺市| 文安县| 丰宁| 五华县| 祁阳县| 北流市| 庆安县| 北京市| 崇州市| 孟州市| 土默特右旗| 连山| 靖远县| 咸阳市| 织金县| 旺苍县| 海宁市| 苗栗市| 阿坝| 涪陵区| 英山县| 卢龙县| 贵港市| 黑山县| 巴中市|