浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

OpenAnolis小助手發表於2022-11-25

編者按:將物理裝置透過 vfio 透傳給虛擬機器是虛擬化常用的技術,但當為虛擬機器分配比較大的記憶體時,虛擬機器的啟動時間會明顯變慢,可能由十幾秒延長至數分鐘,嚴重影響使用者使用體驗。本文整理自 龍蜥大講堂 51 期,浪潮資訊作業系統研發工程師參與技術分享,介紹了裝置透傳虛擬機器啟動慢的原因及最佳化方法,以下為此次分享內容:

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

技術背景:大記憶體虛擬機器裝置透傳啟動延遲

虛擬機器裝置透傳時存在問題,比如將網路卡或者 GPU 這些 pci 裝置透過宿主機的 vfio 透傳到虛擬機器,並且又為虛擬機器分配了比較大的記憶體時,虛擬機器的啟動時間會明顯變慢。特別是分配了幾百 G,甚至上 TB 的記憶體時,就會有比較明顯的啟動延遲,可能由十幾秒延遲到數分鐘。由於只有第一次啟動時,也就是 qemu 程式啟動時才會影響,所以一般來說使用者可以接受這個啟動延遲,但如果使用者有頻繁建立銷燬虛擬機器的需求時就會有比較差的使用體驗。

把裝置透傳到虛擬機器內部之後將會使用虛擬機器內部的驅動程式對裝置進行操作,這裡有一個重要的問題是在虛擬機器內部該如何進行 DMA 操作,因為 DMA 是不會經過 CPU 的,所以使用的是實體地址,但虛擬機器內部看到的實體地址實際上只是宿主機上 qemu 程式申請的虛擬地址,直接用來做 DMA 肯定是不行的。這裡就需要藉助 IOMMU 來實現,虛擬機器內 DMA 訪問的 GPA,經過 iommu 對映到宿主機上的實體地址 HPA2,另外虛擬機器內的驅動程式也可以經過 MMU/EPT,把 GVA 對映為 HPA1,最終需要 HPA1 和 HPA2 這兩個實體地址是同一個才能讓裝置正常執行。(如下圖)

為了實現 HPA1 等於 HPA2,需要 GPA 到 HPA 的對映是固定的並進行 iommu 表的建立,vfio 是透過 VFIO_IOMMU_MAP_DMA 命令實現。

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

後面使用到了 free page reporting 機制,這裡介紹下 free page reporting 機制的原理,首先該機制提供了回撥函式的註冊,每隔 2 秒會遍歷每個 zone 中的空閒頁並呼叫回撥函式進行處理,但只處理 buddy 中高階記憶體頁,就是 pageblock_order 階及以上的記憶體,在 X86 架構下就是處理 9 階和 10 階的記憶體頁,也就是 2M 和 4M 的記憶體頁,這樣也是保證能夠處理 2M 的透明大頁。

另一方面在處理時會保證每個 zone 的水線要比最低水線高 64M,因為如果 zone 的水線本身已經夠低了,還從 zone 中申請記憶體,可能就會觸發記憶體回收,這就會影響一些程式的效能。

通記憶體預清零加速 qemu 程式的記憶體初始化過程

下圖是一個虛擬機器啟動時的火焰圖,這裡的虛擬機器啟動是指 qemu 程式開始執行到虛擬機器內部出現 BIOS 介面為止,不包括虛擬機器內部的核心載入、服務執行過程,從火焰圖可以看到最耗時的函式是 clear_subpage 函式,根據函式呼叫關係可以看到是從 qemu 端呼叫了 ioctl 系統呼叫,這裡就是要將虛擬機器記憶體大小的虛擬記憶體執行 iommu 對映工作,將虛擬地址對映到指定的實體地址上。這其中就需要先將虛擬地址執行 page fault,並將虛擬機器地址和實體地址的關係固定下來,再執行 iommu 對映操作。

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

應用程式執行 page fault 時最終都會執行 clear_user_highpage 對記憶體進行清零操作,這個函式相對來說耗時較長,是虛擬機器啟動時間慢的重要原因,當然我們可以選擇不進行記憶體清零,但這會把宿主機上的一些敏感資訊傳遞給虛擬機器,造成安全影響,所以一般我們需要對記憶體進行清零。

為了解決記憶體頁清零耗時比較長的問題,我們可以基於 free page reporting 機制實現記憶體頁的預清零,過程比較簡單,這裡簡單說下原理。首先透過 free page reporting 介面註冊自己的 hook 函式,然後這個 hook 函式會週期性的被呼叫,每次呼叫會將 buddy 中的 2M/4M 空閒頁執行清零操作,並在 page 的 flag 上設定清零 flag,最後在應用程式申請的記憶體觸發缺頁異常並需要對記憶體進行清零時,會首先判斷該頁是否已經設定了清零 flag,如果已設定則跳過耗時較長的清零操作。還有一點就是記憶體頁的清零屬於耗時但不緊急的操作,如果當前 CPU 有其它的事情要做,則優先執行別的工作,只有在 CPU 空閒時才執行記憶體清零。

透過大塊記憶體 pin 降低 qemu 的記憶體 pin 時間

下圖是最佳化了記憶體頁預清零之後的火焰圖,可以看到 clear_user_page 函式的執行時間已經非常短了,總體執行時間比較長的幾個函式是 follow_page_mask、handle_mm_fault、find_extern_vma,下面就從程式碼角度來看哪個函式還可以進行最佳化。

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

vfio_pin_map_dma(…)
{
    while(size)
    {
    npage = vfio_pin_pages_remote(dma,start_vaddr,size,&pfn,limit)
    vfio_iommu_map(iommu,start_vaddr,pfn,npage,true)
    }將實體地址連續的記憶體執行iommu map
}

IOMMU_MAP_DMA 的 ioctl 進入核心空間會執行到 vfio_pin_map_dma 函式,在這個函式里會對虛擬機器記憶體大小的虛擬記憶體進行 iommu map 操作,首先呼叫 vfio_pin_pages_remote 進行記憶體 pin 操作,這個函式的返回值 npage 是實體地址連續的N個記憶體頁,然後呼叫 vfio_iommu_map 執行 iommu map 操作,比如對於 500G 記憶體的虛擬機器來說會執行很多次這兩個函式。

vfio_pin_pages_remote(…)
{
    for (…)
    {    
    pin_user_pages_remote(NULL, mm, vaddr, 1, flags | FOLL_LONGTERM, page, NULL, NULL);
    pfn = page_to_pfn(page[0]);
    if(pfn != *pfn_base + pinned)
    break;
    }每次只pin一個page,然後判斷和上一個page是否物理連續
}

vfio_pin_pages_remote 這個函式會返回物理連續的多個記憶體頁,為了實現這功能,每次呼叫 pin_user_pages_remote 獲取一個頁,然後判斷和上一個頁是否物理連續。

get_user_pages(…)
{
  while(npages)
  {
    if(!vma ||  start >= vma->vm_end)//相鄰的兩個虛擬記憶體頁是否屬於同一個vma
    {
      vma = find_extern_vma(…)
    }
    page = follow_page_mask(vma,start,…);
    if(!page)
      faultin_page(…)
  }
}

前面說的 pin_user_pages_remote 函式執行了很多次,這個函式里主要是呼叫了 get_user_pages,根據需要 pin 的 page 個數,首先判斷相鄰的兩個虛擬記憶體頁是否屬於同一個 vma,如果不是則呼叫 find_extern_vma 函式獲取 vma,這個函式從火焰圖來看也是耗時比較長的,下面就是執行 follow_page_mask 獲取頁表,如果還未分配物理頁則呼叫 page fault。

後面的兩個函式執行是無法避免的,現在就看 find_extern_vma 函式,因為虛擬機器的實體記憶體是 qemu mmap 的連續虛擬記憶體,屬於同一個 vma,所以每次如果 pin 多個 page 可以跳過頻繁執行的 find_extern_vma。

下面看一下核心社群的最佳化方案,5.12 核心合入主線,透過修改 vfio 程式碼實現,commit 說明有 8% 的效能提升。

最佳化原理是在 pin_user_pages_remote 函式里每次獲取 512 個 page,當然這些page可能是不連續的,然後將這 512 個 page 中連續的部分識別出來分別執行iommu,因為我們系統中是開啟透明大頁的,所以一般來說這 512 個 page 都是物理連續的,只需要執行一次 iommu 就可以。

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

測試結果

測試環境為虛擬機器分配了 512G 的記憶體,做了網路卡的裝置透傳,虛擬機器的啟動時間計算方法為從 qemu 程式啟動開始到出現 grub 介面為止,還有就是記憶體預清零在最理想的情況下,即當前系統上所有的 free page 已經清零完畢。

最終的測試結果:預設情況下(即開啟透明大頁),如果只開啟大塊記憶體 pin,啟動時間從 80 秒降低至 60 秒,如果再開啟記憶體頁預清零,啟動時間將降低至 12 秒。再看下虛擬機器記憶體使用大頁記憶體的情況下的測試結果,在使用 2M 或 1G 大頁時開啟大塊記憶體 pin 功能時,虛擬機器的啟動時間基本從 36 秒降低至 18 秒,就算在開啟記憶體頁預清零,啟動時間也沒有變化,因為記憶體預清零不能作用於標準大頁。

浪潮資訊工程師:帶你瞭解裝置透傳虛擬機器的快速啟動技術最佳化方案 | 龍蜥技術

  • 虛擬機器分配 512G 記憶體。

  • 時間計算方法:time vrish start testvm,時間為從 qemu 啟動到出現 grub 介面為止。

  • 記憶體預清零在最理想情況下,即當前系統上所有 free page 已經清零完畢。

關於直播課件及影片回放獲取方式:

【PPT 課件獲取】:關注微信公眾號(OpenAnolis),回覆“龍蜥課件” 即可獲取。有任何疑問請隨時諮詢龍蜥助手—小龍(微信:openanolis_assis)。

【影片回放】:影片回放可前往龍蜥官網檢視。

—— 完 ——


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70004278/viewspace-2925205/,如需轉載,請註明出處,否則將追究法律責任。

相關文章