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
VFIO就是內核針對IOMMU提供的軟件框架,支持DMA Remapping和Interrupt Remapping,這里只講DMA Remapping。VFIO利用IOMMU這個特性,可以屏蔽物理地址對上層的可見性,可以用來開發用戶態驅動,也可以實現設備透傳。
2.1 概念介紹
先介紹VFIO中的幾個重要概念,主要包括Group和Container。
Group:group 是IOMMU能夠進行DMA隔離的最小硬件單元,一個group內可能只有一個device,也可能有多個device,這取決于物理平臺上硬件的IOMMU拓撲結構。設備直通的時候一個group里面的設備必須都直通給一個虛擬機。不能夠讓一個group里的多個device分別從屬于2個不同的VM,也不允許部分device在host上而另一部分被分配到guest里, 因為就這樣一個guest中的device可以利用DMA攻擊獲取另外一個guest里的數據,就無法做到物理上的DMA隔離。
Container:對于虛機,Container 這里可以簡單理解為一個VM Domain的物理內存空間。對于用戶態驅動,Container可以是多個Group的集合。
上圖中PCIe-PCI橋下的兩個設備,在發送DMA請求時,PCIe-PCI橋會為下面兩個設備生成Source Identifier,其中Bus域為紅色總線號bus,device和func域為0。這樣的話,PCIe-PCI橋下的兩個設備會找到同一個Context Entry和同一份頁表,所以這兩個設備不能分別給兩個虛機使用,這兩個設備就屬于一個Group。
2.2 使用示例
這里先以簡單的用戶態驅動為例,在設備透傳小節中,在分析如何利用vfio實現透傳。
int container, group, device, i; struct vfio_group_status group_status = { .argsz = sizeof(group_status) }; struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) }; struct vfio_iommu_type1_dma_map dma_map = { .argsz = sizeof(dma_map) }; struct vfio_device_info device_info = { .argsz = sizeof(device_info) }; /* Create a new container */ container = open("/dev/vfio/vfio", O_RDWR); if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION) /* Unknown API version */ if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU)) /* Doesn't support the IOMMU driver we want. */ /* Open the group */ group = open("/dev/vfio/26", O_RDWR); /* Test the group is viable and available */ ioctl(group, VFIO_GROUP_GET_STATUS, &group_status); if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE)) /* Group is not viable (ie, not all devices bound for vfio) */ /* Add the group to the container */ ioctl(group, VFIO_GROUP_SET_CONTAINER, &container); /* Enable the IOMMU model we want */ // type 1 open | attatch ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); /* Get addition IOMMU info */ ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info); /* Allocate some space and setup a DMA mapping */ dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); dma_map.size = 1024 * 1024; dma_map.iova = 0; /* 1MB starting at 0x0 from device view */ dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE; ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map); /* Get a file descriptor for the device */ device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "00000d.0"); /* Test and setup the device */ 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,上圖中以虛線表示這個過程
利用虛機頁表完成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交換是異步的)。
-
控制器
+關注
關注
112文章
16214瀏覽量
177482 -
dma
+關注
關注
3文章
559瀏覽量
100447 -
gpa
+關注
關注
0文章
7瀏覽量
4682
原文標題:VFIO概述
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論