轉自 http://element-ui.cn/news/show-44900.aspx
文章目錄
- 1.IOMMU
- 1.1 IOMMU功能簡介
- 1.2 IOMMU作用
- 1.3 IOMMU工作原理
- 1.4 Source Identifier
- 2.VFIO
- 2.1 概念介紹
- 2.2 使用示例
- 3.設備透傳分析
- 3.1 虛機地址映射
- 3.2 設備透傳實現
1.IOMMU
1.1 IOMMU功能簡介
IOMMU主要功能包括DMA Remapping和Interrupt Remapping,這里主要講解DMA Remapping,Interrupt Remapping會獨立講解。對於DMA Remapping,IOMMU與MMU類似。IOMMU可以將一個設備訪問地址轉換為存儲器地址,下圖針對有無IOMMU情況說明IOMMU作用。
在沒有IOMMU的情況下,網卡接收數據時地址轉換流程,RC會將網卡請求寫入地址addr1直接發送到DDR控制器,然后訪問DRAM上的addr1地址,這里的RC對網卡請求地址不做任何轉換,網卡訪問的地址必須是物理地址。
對於有IOMMU的情況,網卡請求寫入地址addr1會被IOMMU轉換為addr2,然后發送到DDR控制器,最終訪問的是DRAM上addr2地址,網卡訪問的地址addr1會被IOMMU轉換成真正的物理地址addr2,這里可以將addr1理解為虛機地址。
左圖是沒有IOMMU的情況,對於虛機無法實現設備的透傳,原因主要有兩個:
- 一是因為在沒有IOMMU的情況下,設備必須訪問真實的物理地址HPA,而虛機可見的是GPA;
- 二是如果讓虛機填入真正的HPA,那樣的話相當於虛機可以直接訪問物理地址,會有安全隱患。
所以針對沒有IOMMU的情況,不能用透傳的方式,對於設備的直接訪問都會有VMM接管,這樣就不會對虛機暴露HPA。
右圖是有IOMMU的情況,虛機可以將GPA直接寫入到設備,當設備進行DMA傳輸時,設備請求地址GPA由IOMMU轉換為HPA(硬件自動完成),進而DMA操作真實的物理空間。IOMMU的映射關系是由VMM維護的,HPA對虛機不可見,保障了安全問題,利用IOMMU可實現設備的透傳。這里先留一個問題,既然IOMMU可以將設備訪問地址映射成真實的物理地址,那么對於右圖中的Device A和Device B,IOMMU必須保證兩個設備映射后的物理空間不能存在交集,否則兩個虛機可以相互干擾,這和IOMMU的映射原理有關,后面會詳細介紹。
1.2 IOMMU作用
根據上一節內容,總結IOMMU主要作用如下:
- 屏蔽物理地址,起到保護作用。典型應用包括兩個:一是實現用戶態驅動,由於IOMMU的映射功能,使HPA對用戶空間不可見,在vfio部分還會舉例。二是將設備透傳給虛機,使HPA對虛機不可見,並將GPA映射為HPA.
- IOMMU可以將連續的虛擬地址映射到不連續的多個物理內存片段,這部分功能於MMU類似,對於沒有IOMMU的情況,設備訪問的物理空間必須是連續的,IOMMU可有效的解決這個問題
1.3 IOMMU工作原理
前面簡單介紹了IOMMU的映射功能,下面講述IOMMU到底如何實現映射的,為便於分析,這里先不考慮虛擬化的場景,以下圖為例,闡述工作原理。
IOMMU的主要功能就是完成映射,類比MMU利用頁表實現VA->PA的映射,IOMMU也需要用到頁表,那么下一個問題就是如何找到頁表。在設備發起DMA請求時,會將自己的Source Identifier(包含Bus、Device、Func)包含在請求中,IOMMU根據這個標識,以RTADDR_REG指向空間為基地址,然后利用Bus、Device、Func在Context Table中找到對應的Context Entry,即頁表首地址,然后利用頁表即可將設備請求的虛擬地址翻譯成物理地址。這里做以下說明:
- 圖中紅線的部門,是兩個Context Entry指向了同一個頁表。這種情況在虛擬化場景中的典型用法就是這兩個Context Entry對應的不同PCIe設備屬於同一個虛機,那樣IOMMU在將GPA->HPA過程中要遵循同一規則 ?????
- 由圖中可知,每個具有Source Identifier(包含Bus、Device、Func)的設備都會具有一個Context Entry。如果不這樣做,所有設備共用同一個頁表,隸屬於不同虛機的不同GPA就會翻譯成相同HPA,會產生問題,
有了頁表之后,就可以按照MMU那樣進行地址映射工作了,這里也支持不同頁大小的映射,包括4KB、2MB、1GB,不同頁大小對應的級數也不同,下圖以4KB頁大小為例說明,映射過程和MMU類似,不再詳細闡述。
1.4 Source Identifier
在講述IOMMU的工作原理時,講到了設備利用自己的Source Identifier(包含Bus、Device、Func)來找到頁表項來完成地址映射,不過存在下面幾個特殊情況需要考慮。
- 對於由PCIe switch擴展出的PCI橋及橋下設備,在發送DMA請求時,Source Identifier是PCIe switch的,這樣的話該PCI橋及橋下所有設備都會使用PCIe switch的Source Identifier去定位Context Entry,找到的頁表也是同一個,如果將這個PCI橋下的不同設備分給不同虛機,由於會使用同一份頁表,這樣會產生問題,針對這種情況,當前PCI橋及橋下的所有設備必須分配給同一個虛機,這就是VFIO中組的概念,下面會再講到。
- 對於SRIO-V,之前介紹過VF的Bus及devfn的計算方法,所以不同VF會有不同的Source Identifier,映射到不同虛機也是沒有問題的
2.VFIO
#########Virtual Function I/O (VFIO) 是一種現代化的設備直通方案,它充分利用了VT-d/AMD-Vi技術提供的DMA Remapping和Interrupt Remapping特性, 在保證直通設備的DMA安全性同時可以達到接近物理設備的I/O的性能。 用戶態進程可以直接使用VFIO驅動直接訪問硬件,並且由於整個過程是在IOMMU的保護下進行因此十分安全, 而且非特權用戶也是可以直接使用。 換句話說,VFIO是一套完整的用戶態驅動(userspace driver)方案,因為它可以安全地把設備I/O、中斷、DMA等能力呈現給用戶空間。
為了達到最高的IO性能,虛擬機就需要VFIO這種設備直通方式,因為它具有低延時、高帶寬的特點,並且guest也能夠直接使用設備的原生驅動。 這些優異的特點得益於VFIO對VT-d/AMD-Vi所提供的DMA Remapping和Interrupt Remapping機制的應用。 VFIO使用DMA Remapping為每個Domain建立獨立的IOMMU Page Table將直通設備的DMA訪問限制在Domain的地址空間之內保證了用戶態DMA的安全性, 使用Interrupt Remapping來完成中斷重映射和Interrupt Posting來達到中斷隔離和中斷直接投遞的目的。
2.1. VFIO 框架簡介
整個VFIO框架設計十分簡潔清晰,可以用下面的一幅圖描述:
+--------------------------------------------------------------+
| VFIO interface |
+--------------------------------------------------------------+
| vfio_iommu | vfio_pci |
+--------------------------------------------------------------+
| iommu driver | pci_bus driver |
+--------------------------------------------------------------+
最上層VFIO Interface Layer,它負責向用戶態提供統一訪問的接口,用戶態通過約定的ioctl設置和調用VFIO的各種能力。
中間層分別是vfio_iommu和vfio_pci:
vfio_iommu是VFIO對iommu層的統一封裝主要用來實現DMA Remapping的功能,即管理IOMMU頁表的能力。
vfio_pci是VFIO對pci設備驅動的統一封裝,它和用戶態進程一起配合完成設備訪問直接訪問,具體包括PCI配置空間模擬、PCI Bar空間重定向,Interrupt Remapping等。
最下面的一層則是硬件驅動調用層:
iommu driver是與硬件平台相關的實現,例如它可能是intel iommu driver或amd iommu driver或者ppc iommu driver或者arm SMMU driver;
pci_bus driver: 而同時vfio_pci會調用到host上的pci_bus driver來實現設備的注冊和反注冊等操作。
在了解VFIO之前需要了解3個基本概念:device, group, container,它們在邏輯上的關系如上圖所示。
- Group 是IOMMU能夠進行DMA隔離的最小硬件單元,一個group內可能只有一個device,也可能有多個device,這取決於物理平台上硬件的IOMMU拓撲結構。 設備直通的時候一個group里面的設備必須都直通給一個虛擬機。 不能夠讓一個group里的多個device分別從屬於2個不同的VM,也不允許部分device在host上而另一部分被分配到guest里, 因為就這樣一個guest中的device可以利用DMA攻擊獲取另外一個guest里的數據,就無法做到物理上的DMA隔離。 另外,VFIO中的group和iommu group可以認為是同一個概念。
- Device 指的是我們要操作的硬件設備,不過這里的“設備”需要從IOMMU拓撲的角度去理解。如果該設備是一個硬件拓撲上獨立的設備,那么它自己就構成一個iommu group。 如果這里是一個multi-function設備,那么它和其他的function一起組成一個iommu group,因為多個function設備在物理硬件上就是互聯的, 他們可以互相訪問對方的數據,所以必須放到一個group里隔離起來。值得一提的是,對於支持PCIe ACS特性的硬件設備,我們可以認為他們在物理上是互相隔離的。
- Container 是一個和地址空間相關聯的概念,這里可以簡單把它理解為一個VM Domain的物理內存空間。對於用戶態驅動,Container可以是多個Group的集合。
從上圖可以看出,一個或多個device從屬於某個group,而一個或多個group又從屬於一個container。 如果要將一個device直通給VM,那么先要找到這個設備從屬的iommu group,然后將整個group加入到container中即可。關於如何使用VFIO可以參考內核文檔:vfio.txt
上圖中PCIe-PCI橋下的兩個設備,在發送DMA請求時,PCIe-PCI橋會為下面兩個設備生成Source Identifier,其中Bus域為紅色總線號bus,device和func域為0。這樣的話,PCIe-PCI橋下的兩個設備會找到同一個Context Entry和同一份頁表,所以這兩個設備不能分別給兩個虛機使用,這兩個設備就屬於一個Group。
2.2 VFIO 數據結構關系
Linux內核設備驅動充分利用了“一切皆文件”的思想,VFIO驅動也不例外,VFIO中為了方便操作device, group, container等對象將它們和對應的設備文件進行綁定。 VFIO驅動在加載的時候會創建一個名為/dev/vfio/vfio
的文件,而這個文件的句柄關聯到了vfio_container上,用戶態進程打開這個文件就可以初始化和訪問vfio_container。 當我們把一個設備直通給虛擬機時,首先要做的就是將這個設備從host上進行解綁,即解除host上此設備的驅動,然后將設備驅動綁定為“vfio-pci”, 在完成綁定后會新增一個/dev/vfio/$groupid
的文件,其中$groupid為此PCI設備的iommu group id, 這個id號是在操作系統加載iommu driver遍歷掃描host上的PCI設備的時候就已經分配好的,可以使用readlink -f /sys/bus/pci/devices/$bdf/iommu_group
來查詢。 類似的,/dev/vfio/$groupid
這個文件的句柄被關聯到vfio_group上,用戶態進程打開這個文件就可以管理這個iommu group里的設備。 然而VFIO中並沒有為每個device單獨創建一個文件,而是通過VFIO_GROUP_GET_DEVICE_FD這個ioctl來獲取device的句柄,然后再通過這個句柄來管理設備。
VFIO框架中很重要的一部分是要完成DMA Remapping,即為Domain創建對應的IOMMU頁表,這個部分是由vfio_iommu_driver來完成的。 vfio_container包含一個指針記錄vfio_iommu_driver的信息,在x86上vfio_iommu_driver的具體實現是由vfio_iommu_type1來完成的。 其中包含了vfio_iommu, vfio_domain, vfio_group, vfio_dma等關鍵數據結構(注意這里是iommu里面的),
- vfio_iommu可以認為是和container概念相對應的iommu數據結構,在虛擬化場景下每個虛擬機的物理地址空間映射到一個vfio_iommu上。
- vfio_group可以認為是和group概念對應的iommu數據結構,它指向一個iommu_group對象,記錄了着iommu_group的信息。
- vfio_domain這個概念尤其需要注意,這里絕不能把它理解成一個虛擬機domain,它是一個與DRHD(即IOMMU硬件)相關的概念, 它的出現就是為了應對多IOMMU硬件的場景,我們知道在大規格服務器上可能會有多個IOMMU硬件,不同的IOMMU硬件有可能存在差異, 例如IOMMU 0支持IOMMU_CACHE而IOMMU 1不支持IOMMU_CACHE(當然這種情況少見,大部分平台上硬件功能是具備一致性的),這時候我們不能直接將分別屬於不同IOMMU硬件管理的設備直接加入到一個container中, 因為它們的IOMMU頁表SNP bit是不一致的。 因此,一種合理的解決辦法就是把一個container划分多個vfio_domain,當然在大多數情況下我們只需要一個vfio_domain就足夠了。 處在同一個vfio_domain中的設備共享IOMMU頁表區域,不同的vfio_domain的頁表屬性又可以不一致,這樣我們就可以支持跨IOMMU硬件的設備直通的混合場景。
經過上面的介紹和分析,我們可以把VFIO各個組件直接的關系用下圖表示(點擊看完整圖片),讀者可以按照圖中的關系去閱讀相關代碼實現。
VFIO就是內核針對IOMMU提供的軟件框架,支持DMA Remapping和Interrupt Remapping,這里只講DMA Remapping。VFIO利用IOMMU這個特性,可以屏蔽物理地址對上層的可見性,可以用來開發用戶態驅動,也可以實現設備透傳。
2.3 使用示例
這里先以簡單的用戶態驅動為例,在設備透傳小節中,在分析如何利用vfio實現透傳。
1 int container, group, device, i; 2 struct vfio_group_status group_status = 3 { .argsz = sizeof(group_status) }; 4 struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) }; 5 struct vfio_iommu_type1_dma_map dma_map = { .argsz = sizeof(dma_map) }; 6 struct vfio_device_info device_info = { .argsz = sizeof(device_info) }; 7 8 /* Create a new container */ 9 container = open("/dev/vfio/vfio", O_RDWR); 10 11 if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION) 12 /* Unknown API version */ 13 14 if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU)) 15 /* Doesn't support the IOMMU driver we want. */ 16 17 /* Open the group */ 18 group = open("/dev/vfio/26", O_RDWR); 19 20 /* Test the group is viable and available */ 21 ioctl(group, VFIO_GROUP_GET_STATUS, &group_status); 22 23 if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE)) 24 /* Group is not viable (ie, not all devices bound for vfio) */ 25 26 /* Add the group to the container */ 27 ioctl(group, VFIO_GROUP_SET_CONTAINER, &container); 28 29 /* Enable the IOMMU model we want */ // type 1 open | attatch 30 ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); 31 32 /* Get addition IOMMU info */ 33 ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info); 34 35 /* Allocate some space and setup a DMA mapping */ 36 dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE, 37 MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);//這里的0是什么意思?不一般是文件描述符嗎 fd=open(...)?? 38 dma_map.size = 1024 * 1024; 39 dma_map.iova = 0; /* 1MB starting at 0x0 from device view */ 40 dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE; 41 42 ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);//把iova地址轉換到vaddr對應的物理地址?? 43 44 /* Get a file descriptor for the device */ 45 device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0"); 46 47 /* Test and setup the device */ 48 ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
對於dev下Group就是按照上一節介紹的Group划分規則產生的,上述代碼描述了如何使用VFIO實現映射,對於Group和Container的相關操作這里不做過多解釋,主要關注如何完成映射,下圖解釋具體工作流程。
首先,利用mmap映射出1MB字節的虛擬空間,因為物理地址對於用戶態不可見,只能通過虛擬地址訪問物理空間。然后執行ioctl的VFIO_IOMMU_MAP_DMA命令,傳入參數主要包含vaddr及iova,其中iova代表的是設備發起DMA請求時要訪問的地址,也就是IOMMU映射前的地址,vaddr就是mmap的地址。VFIO_IOMMU_MAP_DMA命令會為虛擬地址vaddr找到物理頁並pin住(因為設備DMA是異步的,隨時可能發生,物理頁面不能交換出去),然后找到Group對應的Contex Entry,建立頁表項,頁表項能夠將iova地址映射成上面pin住的物理頁對應的物理地址上去,這樣對用戶態程序完全屏蔽了物理地址,實現了用戶空間驅動。IOVA地址的00x100000對應DRAM地址0x100000000x10100000,size為1024 * 1024。一句話概述,VFIO_IOMMU_MAP_DMA這個命令就是將iova通過IOMMU映射到vaddr對應的物理地址上去。
3.設備透傳分析
設備透傳就是由虛機直接接管設備,虛機可以直接訪問MMIO空間,VMM配置好IOMMU之后,設備DMA讀寫請求也無需VMM借入,需要注意的是設備的配置空間沒有透傳,因為VMM已經配置好了BAR空間,如果將這部分空間也透傳給虛機,虛機會對BAR空間再次配置,會導致設備無法正常工作。
3.1 虛機地址映射
在介紹透傳之前,先看下虛機的GPA與HVA和HPA的關系,以及虛機是如何訪問到真實的物理地址的,過程如下圖。
一旦頁表建立好后,整個映射過程都是硬件自動完成的,對於上圖有如下幾點說明:
- 對於虛機內的頁表,完成GVA到GPA的映射,雖然整個過程都是硬件自動完成,但有一點要注意下,在虛機的中各級頁表也是存儲在HPA中的,而CR3及各級頁表中裝的地址都是GPA,所以在訪問頁表時也需要借助EPT(extension page table?),上圖中以虛線表示這個過程
- 利用虛機頁表完成GVA到GPA的映射后,此時借助EPT實現GPA到HPA的映射,這里沒有什么特殊的,就是一層層頁表映射
- 看完上圖,有沒有發現少了點啥,是不是沒有HVA。單從上圖整個虛機尋址的映射過程來看,是不需要HVA借助的,硬件會自動完成GVA->GPA->HPA映射,那么HVA有什么用呢?這里從下面兩方面來分析:
1)Qemu利用iotcl控制KVM實現EPT的映射,映射的過程中必然要申請物理頁面。Qemu是應用程序,唯一可見的只是HVA,這時候又需要借助mmap了,Qemu會根據虛機的ram大小,即GPA大小范圍,然后mmap出與之對應的大小,即HVA。通過KVM_SET_USER_MEMORY_REGION命令控制KVM,與這個命令一起傳入的參數主要包括兩個值,guest_phys_addr代表虛機GPA地址起始,userspace_addr代表上面mmap得到的首地址(HVA)。傳入進去后,KVM就會為當前虛機GPA建立EPT映射表實現GPA->HPA,同時會為VMM建立HVA->HPA映射。
2)當vm_exit發生時,VMM需要對異常進行處理,異常發生時VMM能夠獲取到GPA,有時VMM需要訪問虛機GPA對應的HPA,VMM的映射和虛機的映射方式不同,是通過VMM完成HVA->HPA,且只能通過HVA才能訪問HPA,這就需要VMM將GPA及HVA的對應關系維護起來,這個關系是Qemu維護的,這里先不管Qemu的具體實現(后面會有專門文檔介紹),當前只需要知道給定一個虛機的GPA,虛機就能獲取到GPA對應的HVA。下圖描述VMM與VM的地址映射關系。
3.2 設備透傳實現
在前面介紹VFIO的使用實例時,核心思想就是IOVA經過IOMMU映射出的物理地址與HVA經過MMU映射出的物理地址是同一個。對於設備透傳的情況,先上圖,然后看圖說話。
先來分析一下設備的DMA透傳的工作流程,一旦設備透傳給了虛機,虛機在配置設備DMA時直接使用GPA。此時GPA經由EPT會映射成HPA1,GPA經由IOMMU映射的地址為HPA2,此時的HPA1和HPA2必須相等,設備的透傳才有意義。下面介紹在配置IOMMU時如何保證HPA1和HPA2相等,在VFIO章節講到了VFIO_IOMMU_MAP_DMA這個命令就是將iova通過IOMMU映射到vaddr對應的物理地址上去。對於IOMMU來講,此時的GPA就是iova,我們知道GPA經由EPT會映射為HPA1,對於VMM來講,這個HPA1對應的虛機地址為HVA,那樣的話在傳入VFIO_IOMMU_MAP_DMA命令時講hva作為vaddr,IOMMU就會將GPA映射為HVA對應的物理地址及HPA1,即HPA1和HPA2相等。上述流程幫助理清整個映射關系,實際映射IOMMU的操作很簡單,前面提到了qemu維護了GPA和HVA的關系,在映射IOMMU的時候也可以派上用場。注:IOMMU的映射在虛機啟動時就已經建立好了,映射要涵蓋整個GPA地址范圍,同時虛機的HPA對應的物理頁都不會交換出去(設備DMA交換是異步的)。
本文鏈接http://element-ui.cn/news/show-44900.aspx