vfio-mdev邏輯空間分析【轉】

轉自:https://zhuanlan.zhihu.com/p/28111201

最近評審了一個基於vfio-mdev的解決方案,發現該作者對這個邏輯空間的理解有問題,我通過本文來解釋一下整個vfio邏輯空間是什麼樣的。

先快速對vfio的概念進行掃盲。這個掃盲的目的不是詳細介紹什麼是VFIO,而是給對沒有VFIO的讀者一個入門的指引,或者一個暫時理解本文上下文的空間;也是給已經有經驗的讀者一個能和我對齊的基礎表述。

VFIO是Linux Kernel UIO特性的升級版本。UIO的作用是把一個設備的IO和中斷能力暴露給用戶態,從而實現在用戶態對硬件的直接訪問。它的基本實現方法是,當我們probe一個設備的時候,通過uio_register_device()註冊爲一個字符設備/dev/uioN,用戶程序通過對這個設備mmap訪問它的IO空間,通過read/select等接口等待中斷。

UIO的缺點在於,用戶態的虛擬地址無法直接用於做設備的DMA地址(因爲在用戶態無法知道DMA內存的物理地址),這樣限制了UIO的使用範圍。基本上UIO現在只能用於做工控卡這種IO量不大,可以直接把內存地址拷貝到IO空間的場景(相當於不做DMA)。我們有人通過UIO設備自己的ioctl來提供求物理地址的機制,從而實現DMA,但這種方案是有風險的,因爲你做ioctl求得的物理地址,可能因爲swap而被放棄,就算你做gup,但gup只保證物理內存不被釋放,不能保證vma還指向這個物理頁,要保證後者需要vm_pin這樣的解決方案,但vm_pin根本就沒有能夠上傳主線。

這裏提到的UIO的缺點,基本上拒絕了大流量IO設備使用該機制提供用戶空間訪問的能力了。

VFIO通過IOMMU的能力來解決這個問題。IOMMU可以爲設備直接翻譯虛擬地址,這樣我們在提供虛擬地址給設備前,把地址映射提供給VFIO,VFIO就可以爲這個設備提供頁表映射,從而實現用戶程序的DMA操作。

揹負提供DMA操作這個使命,VFIO要解決一個更大的問題,就是要把設備隔離掉。在Linux的概念中,內核是可信任的,用戶程序是不可信任的,如果我們允許用戶程序對設備做DMA,那麼設備也是不可信任的,我們不能允許設備訪問程序的全部地址空間(這會包括內核),所以,每個設備,針對每個應用,必須有獨立的頁表。這個頁表,通過iommu_group承載(iommu_group.domain),和進程的頁表相互獨立。進程必須主動做DMA映射,才能把對應的地址映射寫進去。

所以VFIO的概念空間是container和group,前者代表設備iommu的格式,後者代表一個獨立的iommu_group(vfio中用vfio_group代表),我們先創建container,然後把物理的iommu_group綁定到container上,讓container解釋group,之後我們基於group訪問設備(IO,中斷,DMA等等)即可。

這個邏輯空間其實是有破綻的,iommu_group是基於設備來創建的,一個設備有一個iommu_group(或者如果這個設備和其他設備共享同一個IOMMU硬件,是幾個設備纔有一個iommu_group),那如果我兩個進程要一起使用同一個設備呢?基於現在的架構,你只能通過比如VF(Virtual Function,虛擬設備),在物理上先把一個設備拆成多個,然後還是一個進程使用一個設備。這用於虛擬機還可以,但如果用於其他功能,基本上是沒戲了。

再說,VF功能基本都依賴SR-IOV這樣的實現,也不是你想用就能用的。

這我們就要引出VFIO-mdev(以下簡稱mdev)了。mdev本質上是在VFIO層面實現VF功能。在mdev的模型中,通過mdev_register_device()註冊到mdev中的設備稱爲父設備(parent_dev),但你用的時候不使用父設備,而是通過父設備提供的機制(在sysfs中,後面會詳細談這個)創建一個mdev,這個mdev自帶一個iommu_group,這樣,你有多個進程要訪問這個父設備的功能,每個都可以有獨立的設備頁表,而且互相不受影響。

所以,整個mdev框架包括兩個基本概念,一個是pdev(父設備),一個是mdev(注意,我們這裏mdev有時指整個vfio-mdev的框架,有時指基於一個pdev的device,請注意區分上下文)。前者提供設備硬件支持,後者支持針對一個獨立地址空間的請求。

兩者都是device(struct device),前者的總線是真實的物理總線,後者屬於虛擬總線mdev,mdev上只有一個驅動vfio_mdev,當你通過pdev創建一個mdev的時候,這個mdev和vfio_mdev驅動匹配,從而給用戶態暴露一個普通vfio設備的接口(比如platform_device或者pci_device)的接口。

換句話說,如果一個設備需要給多個進程提供用戶態驅動的訪問能力,這個設備在probe的時候可以註冊到mdev框架中,成爲一個mdev框架的pdev。之後,用戶程序可以通過sysfs創建這個pdev的mdev。

pdev註冊需要提供如下參數:

struct mdev_parent_ops {
        struct module   *owner;
        const struct attribute_group **dev_attr_groups;
        const struct attribute_group **mdev_attr_groups;
        struct attribute_group **supported_type_groups;
        int     (*create)(struct kobject *kobj, struct mdev_device *mdev); 
        int     (*remove)(struct mdev_device *mdev); 
        int     (*open)(struct mdev_device *mdev); 
        void    (*release)(struct mdev_device *mdev);
        ssize_t (*read)(struct mdev_device *mdev, char __user *buf,
                        size_t count, loff_t *ppos); 
        ssize_t (*write)(struct mdev_device *mdev, const char __user *buf,
                         size_t count, loff_t *ppos);
        long    (*ioctl)(struct mdev_device *mdev, unsigned int cmd, 
                         unsigned long arg);
        int     (*mmap)(struct mdev_device *mdev, struct vm_area_struct *vma);
};

其中三個attribute_group都用於在sysfs中增加一組屬性。device本身根據它的bus_type,就會產生一個sysfs的屬性組(所謂屬性組就是sysfs中的一個目錄,裏面每個文件就是一個“屬性”,文件名就是屬性名,內容就是屬性的值),假設你的pdev是/sys/bus/platform/devices/abc.0,那麼這三個attribute_group產生的屬性分別在:

dev_attr_groups:/sys/bus/platform/devices/abc.0下

mdev_attr_groups:/sys/bus/platform/devices/abc.0/<mdev_uuid>下,/sys/bus/mdev/devices中有這個設備的鏈接

supported_type_groups:/sys/bus/platform/devices/abc.0/mdev_supported_types/下,裏面有什麼屬性是框架規定的,包括:

1. name:設備名稱

2. available_instances:還可以創建多少個實例

3. device_api:設備對外的接口API標識

這些參數支持具體用戶態驅動如何訪問這個設備,pdev的驅動當然可以增加更多。mdev框架在這個目錄中還增加如下屬性:

1. devices:這是一個目錄,鏈接向所有被創建的mdev

2. create:向這個文件中寫入一個uuid就可以創建一個新的mdev,實際上產生對mdev_parent_ops.create()的回調

mdev_parent_ops的其他回調,都是支持被pdev創建的mdev設備本身的文件訪問的。對它的read/write/mmap本質上是對設備IO空間的訪問,如果你要模擬一個platform設備,就要支持這個platform設備的io_resource空間的訪問(這個通過container的ioctl來獲得),如果你要模擬一個pci設備,則可以直接模擬一個配置空間的訪問,用這個配置空間返回BAR空間的地址,之後支持對BAR空間的訪問即可。如果你執行DMA,則通過mdev下的vfio_group找到它對應的vfio_group,然後用一般vfio的方法來做DMA映射即可。

mdev的類型和pdev的類型是完全無關的,甚至可以不是任何設備類型(如果你不需要用於qemu一類的虛擬化方案的話),所以,沒有必要把這個設備類型和pdev本身的類型關聯在一起。

mdev這個模型建得最不好的地方是,create的時候只能傳進去一個uuid,不能傳進去參數,這樣如果我創建的設備需要參數怎麼辦呢?那就只能創建以後再設置了,這增加了“創建以後沒有足夠資源提供”的可能性),不過看起來,大部分情況我們是可以接受這個限制的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章