Linux SVA特性分析

Linux SVA特性分析

-v0.1 2019.9.15 Sherlock init
-v0.2 2019.9.18 Sherlock update數據結構和動態分析
-v0.3 2019.10.13 Sherlock add性能分析
-v0.4 2019.10.14 Sherlock add數據結構說明
-v0.5 2019.10.31 Sherlock add iommu_sva_set_ops, iommu_sva_get_pasid

簡介: 最近Linux社區在上傳SVA特性(share virtual memory), 簡單講這個特性可以達到
這樣的功能,就是可以是設備直接使用進程裏的虛擬地址。如果把設備操作映射到用戶態,
就可以在用戶態叫設備直接使用進程虛擬地址。設備可以使用進程虛擬地址做DMA,如果
設備功能強大,比如可以執行一段指令,設備甚至可以直接執行相應進程虛擬地址上的
指令。目前SVA特性正處於上傳階段,這個特性的補丁涉及到IOMMU, PCI, 內存管理(MM),
SMMU, VFIO, 虛擬化,DT, ACPI等方面的修改,所以補丁比較分散,不過好在這個特性的
上傳者把相關補丁收集在了一個分支上,即https://jpbrucker.net/sva/ 的sva/current
分支上。想要了解SVA的整體,可以直接查看這個分支上的各個提交。需要說明是,這個
分支的base是5.2-rc7, 在5.3中這個分支上的少量補丁已經合入Linux主線,如果想用最
新內核測試SVA需要做一定的適配工作。本文的分析以base是5.2-rc7的sva/current分支
上的補丁爲基礎。

0. 使用場景介紹


如上,SVA特性可以做到進程虛擬地址在進程和設備之間共享。最直觀的使用場景就是在
用戶態做DMA。

             
    +------------------+     +----------------+
    |   進程           |     |  進程          |       ...
    |                  |     |                |
    | va = malloc();   |     |                |
    | set dma dst: va  |     |                |
    |      +---------+ |     |                |          用戶態
    +------+ mmap io +-+     +----------------+
 <---------+         +------------------------------------------> 
           +---------+
                        內核

 <--------------------------------------------------------------> 
                +--------------+
                |  DMA dst: va |
                |              |
                |  設備        |
                +--------------+

如上圖所示,在SVA的支持下,我們可以在用戶態進程malloc一段內存,然後把得到va
直接配置給設備的DMA,然後啓動DMA向va對應的虛擬地址寫數據,當然也可以從va對應
的虛擬地址上往設備讀數據。這裏我們把設備DMA相關的寄存器先mmap到用戶態,這樣
DMA操作在用戶態就可以完成。

可以注意到,SVA可以支持功能很大一部分取決於設備的功能,SVA就是提供一個進程和
設備一致的虛擬地址空間,其他的設備想怎麼用都可以。如上,如果設備做的足夠強,
設備完全可以執行va上對應的代碼。

可以看到,設備完全可以把自身的資源分配給不同的進程同時使用。

爲了滿足上面的使用場景,SVA特性需要硬件支持IOMMU以及設備發起缺頁。在下一節
先介紹硬件,再基於此分析SVA的軟件實現。

1. 硬件基礎介紹


本文以ARM64體系結構爲基礎分析,在ARM64下,IOMMU指的就是SMMU。對於設備,ARM64
下有平臺設備和PCI設備。整體的硬件示意圖如下,圖中也畫出了硬件工作時相關的內存
裏的數據結構。

               +-----+
               | CPU |
               +--+--+
                  |             
                  v             
               +-----+           +---------------------------------------------+
               | MMU |-----------+------------------------------------+        |
               +--+--+           | DDR                                |        |
                  |              |                                    |        |
                  v              |                                    |        |
  system bus ------------------> |                                    |        |
                  ^              |                                    v        |
                  |   SID/SSID   |      +-----+     +----+      +------------+ |
                  |       +------+----> | STE |---->| CD |----->| page table | |
                  |       |      |      +-----+     +----+      +------------+ |
                  |       |      |       ...        | CD |----->| page table | |
                  |       |      |                  +----+      +------------+ |
          IRQs    |       |      |                  | .. |      | ..         | |
                ^ |^  ^   |      |                  +----+      +------------+ |
                | ||  |   |      |                  | CD |----->| page table | |
      +---------+-++--+---+---+  |                  +----+      +------------+ |
      | SMMU    |  |  |   |   |  |                                             |
      |         |  |  |       |  |     +-------------+                         |
      |  +------+--+-CMD Q ---+--+---->| CMD queue   |                         |
      +--v--+   |  |          |  |     +-------------+                         |
      | PRI |---> PRI Q ------+--+---->| PRI queue   |                         |
      +-----+   |             |  |     +-------------+                         |
      | ATS |  EVENT Q -------+--+---->| EVENT queue |                         |
      +-----+-----------------+  |     +-------------+                         |
        ^ |     ^            ^   +---------------------------------------------+
        | |     |            |
        | v     | BDF/PASID  +--------------+
      +---------+-------------+             | 
      |         RP            |             | 
      +-----------------------+             | 
        ^ |       ^                         | 
        | v       |  BDF/PASID              | 
      +-+----+----+-+---------+  +-----+----+----------+
      | ATC  |      |         |  |     |               |                       
      +------+      |         |  | DMA |               |                       
      | PRI  |      |  EP     |  |     |               |                       
      +------+ DMA  |         |  +-----+  platform dev |                       
      +-------------+         |  |                     |                       
      +-----------------------+  +---------------------+

基於上一節中提到的使用場景, 我們梳理硬件中的邏輯關係。調用malloc後,其實只是
拿到了一個虛擬地址,內核並沒有爲申請的地址空間分配實際的物理內存,直到訪問這
塊地址空間時引發缺頁,內核在缺頁流程裏分配實際的物理內存,然後建立虛擬地址到
物理內存的映射。這個過程需要MMU的參與。設想SVA的場景中,先malloc得到va, 然後
把這個va傳給設備,配置設備DMA去訪問該地址空間,這時內核並沒有爲va分配實際的
物理內存,所以設備一側的訪問流程必然需要進行類似的缺頁請求。支持設備側缺頁
請求的硬件設備就是上面所示的SMMU,其中對於PCI設備,還需要ATS、PRI硬件特性支持。
平臺設備需要SMMU stall mode支持(使用event queue)。PCI設備和平臺設備都需要
PASID特性的支持。

如上圖所示,引入SVA後,MMU和SMMU使用相同的進程頁表, SMMU使用STE表和CD表管理
當前SMMU下設備的頁表,其中STE表用來區分設備,CD表用來區分同一個設備上分配給
不同進程使用的硬件資源所對應的進程頁表。STE表和CD表都需要SMMU驅動預先分配好。

SMMU內部使用command queue,event queue,pri queue做基本的事件管理。當有相應
硬件事件發生時,硬件把相應的描述符寫入event queue或者pri queue, 然後上報中斷。
軟件使用command queue下發相應的命令操作硬件。

PCI設備和平臺設備的硬件缺頁流程有一些差異,下面分別介紹。對於PCI設備,ATS,
PRI和PASID的概念同時存在於PCIe和SMMU規範中。對於ATS的介紹可以參考這裏:
https://blog.csdn.net/scarecrow_byr/article/details/74276940。簡單講,ATS特性
由設備側的ATC和SMMU側的ATS同時支持,其目的是在設備中緩存va對應的pa,設備隨後
使用pa做內存訪問時無需經過SMMU頁錶轉換,可以提高性能。PRI(page request
interface)也是需要設備和SMMU一起工作,PCIe設備可以發出缺頁請求,SMMU硬件在解
析到缺頁請求後可以直接將缺頁請求寫入PRI queueu, 軟件在建立好頁表後,可以通過
CMD queue發送PRI response給PCIe設備。具體的ATS和PRI的實現是硬件相關的,目前
市面上還沒有實現這兩個硬件特性的PCIe設備,但是我們可以設想一下ATS和PRI的硬件
實現,最好的實現應該是軟件透明的,也就是軟件配置給設備DMA的訪問地址是va, 軟件
控制DMA發起後,硬件先發起ATC請求,從SMMU請求該va對應的pa,如果SMMU裏已經有va
到pa的映射,那麼設備可以得到pa,然後設備再用pa發起一次內存訪問,該訪問將直接
訪問對應pa地址,不在SMMU做地址翻譯,如果SMMU沒有va到pa的映射, 那麼設備得到
這個消息後會繼續向SMMU發PRI請求,設備得到從SMMU來的PRI response後發送內存訪問
請求,該請求就可以在SMMU中翻譯得到pa, 最終訪問到物理內存。

PRI請求是基於PCIe協議的, 平臺設備無法用PRI發起缺頁請求。實際上,平臺設備是無法
靠自身發起缺頁請求的,SMMU用stall模式支持平臺設備的缺頁,當一個平臺設備的內存
訪問請求到達SMMU後,如果SMMU裏沒有爲va做到pa的映射,硬件會給SMMU的event queue
裏寫一個信息,SMMU的event queue中斷處理裏可以做缺頁處理,然後SMMU可以回信息給
設備(fix me: 請求設備重發,還是smmu缺頁處理後已經把該訪問翻譯後送到上游總線)。
實際上, SMMU使用event queue來處理各種錯誤異常,這裏的stall模式是借用了event
queue來處理缺頁。

可以注意到PRI和stall模式完成缺頁的區別是,PRI缺頁的時候並不是在IO實際發生的
時候,因爲如果PRI response表示PRI請求失敗,硬件完全可以不發起後續的IO操作。
而stall模式,完全發生在IO請求的途中。所以,他被叫做stall模式。

2. 數據結構


   struct device
           +-> struct iommu_param
                   +-> struct iommu_fault_param
                           +-> handler(struct iommu_fault, void *)
                           +-> faults list
                   +-> struct iopf_device_param
                        +-> struct iopf_queue
                                +-> work queue
                                +-> devices list
                        +-> wait queue
                   +-> struct iommu_sva_param

在引起缺頁的外設的device裏,需要添加缺頁相關的數據結構。handler是缺頁要執行
的函數,具體見下面動態流程分析。iopf_queue在smmu驅動初始化時添加,這裏iopf_queue
可能是eventq對應的,也可能是priq對應的,iopf_queue是smmu裏的概念,所以同一個
iopf_queue會服務同一個smmu下的所有外設, 上面的devices list鏈表就是用來收集這個
smmu下的外設。

  struct iommu_bond
        +-> struct iommu_sva
                +-> device
                +-> struct iommu_sva_ops
                        +-> iommu_mmu_exit_handle ?
        +-> struct io_mm
                +-> pasid
                +-> device list
                +-> mm
                +-> mmu_notifier
                +-> iommu_ops
                        +-> attach
                        +-> dettach
                        +-> invalidat
                        +-> release
        +-> mm list
        +-> device list
        +-> wait queue

下面的圖引用自JPB的補丁,該圖描述的是建立好的靜態數據結構之間的關係,以及IOMMU
(e.g. SMMU的STE和CD表在這種數據結構下的具體配置情況)表格的配置。用這個圖可以
很好說明iommu_bond中的各個數據結構的意義以及之間的關係。

iommu_bond這個結構並不對外, 用下面的iommu_sva_bind_device/unbind接口時,函數
參數都是iommu_sva。使用SVA的設備可以把一個設備的一些資源和一個進程地址空間綁定,
這種綁定關係是靈活的,比如可以一個設備上的不同資源和不用的進程地址空間綁定
(bond 1, bond 2), 還可以同一個設備上的資源都綁定在一個進程的地址空間上(bond 3,
bond 4)。從進程地址空間的角度看,一個進程地址空間可能和多個設備資源綁定。

iommu_bond指的就是一個綁定,io_mm指的是綁定了外設資源的一個進程地址空間。
io_pgtables是指內核dma接口申請內存的頁表。

              ___________________________
             |  IOMMU domain A           |
             |  ________________         |
             | |  IOMMU group   |        +------- io_pgtables
             | |                |        |
             | |   dev 00:00.0 ----+------- bond 1 --- io_mm X
             | |________________|   \    |
             |                       '----- bond 2 ---.
             |___________________________|             \
              ___________________________               \
             |  IOMMU domain B           |             io_mm Y
             |  ________________         |             / /
             | |  IOMMU group   |        |            / /
             | |                |        |           / /
             | |   dev 00:01.0 ------------ bond 3 -' /
             | |   dev 00:01.1 ------------ bond 4 --'
             | |________________|        |
             |                           +------- io_pgtables
             |___________________________|

                                PASID tables
                                 of domain A
                              .->+--------+
                             / 0 |        |-------> io_pgtable
                            /    +--------+
            Device tables  /   1 |        |-------> pgd X
              +--------+  /      +--------+
      00:00.0 |      A |-'     2 |        |--.
              +--------+         +--------+   \
              :        :       3 |        |    \
              +--------+         +--------+     --> pgd Y
      00:01.0 |      B |--.                    /
              +--------+   \                  |
      00:01.1 |      B |----+   PASID tables  |
              +--------+     \   of domain B  |
                              '->+--------+   |
                               0 |        |-- | --> io_pgtable
                                 +--------+   |
                               1 |        |   |
                                 +--------+   |
                               2 |        |---'
                                 +--------+
                               3 |        |
                                 +--------+

相關接口:

    - iommu_dev_enable_feature: 準備和sva相關的smmu中的管理結構, 該接口可以在
                                    設備驅動裏調用,用來使能sva的功能
      +-> arm_smmu_dev_enable_feature
          +-> arm_smmu_dev_enable_sva
            +-> iommu_sva_enable
              +-> iommu_register_device_fault_handler(dev, iommu_queue_iopf, dev)
                  /* 動態部分將執行iommu_queue_iopf */
                  把iommu_queue_iopf賦值給iommu_fault_param裏的handler                  
            +-> iopf_queue_add_device(struct iopf_queue, dev)
                把相應的iopf_queue賦值給iopf_device_param裏的iopf_queue, 這裏有
                pri對應的iopf_queue或者是stall mode對應的iopf_queue。初始化
                iopf_device_param裏的wait queue

                對應iopf_queue的初始化在在smmu驅動probe流程中: e.g.
                arm_smmu_init_queues
                  +-> smmu->prq.iopf = iopf_queue_alloc
                                           +-> alloc_workqueue
                                             分配以及初始化iopf_queue裏的工作隊列
            +-> arm_smmu_enable_pri
                調用PCI函數是能EP設備的PRI功能

    - iommu_sva_bind_device: 將設備和mm綁定, 該接口可以在設備驅動裏調用,把一個
                             設備和mm綁定在一起。返回struct iommu_sva *
      +-> iommu_sva_bind_group
        +-> iommu_group_do_bind_dev
          +-> arm_smmu_sva_bind
            +-> arm_smmu_alloc_shared_cd(mm)
                分配相應的CD表項,並且把CD表項裏的頁表地址指向mm裏保存的進程頁表
                地址。這個函數主要配置SMMU的硬件
            +-> iommu_sva_bind_generic(dev, mm, cd, &arm_smmu_mm_ops, drvdata)
              +-> io_mm_alloc
                  分配io_mm以及初始化其中的數據域段, 向mm註冊io_mm的notifier
                  /* to do: mm發生變化的時候通知io_mm */
                  +-> mmu_notifier_register
              +-> io_mm_attach
                +-> init_waitqueue_head
                    初始化iommu_bond裏的等待隊列mm_exit_wq /* to do: 作用 */
                +-> io_mm->ops->attach(bond->sva.dev, io_mm->pasid, io_mm->ctx)
                    調用e.g.SMMU arm_smmu_mm_ops裏的attach函數
                    +-> arm_smmu_mm_attach
                      +-> __arm_smmu_write_ctx_desc
                        下發SMMU command使能CD配置。可以看到arm_smmu_mm_ops裏的
                        這一組回調函數基本都是下發SMMU命令控制CD/ATC/TLB相關的
                        配置

    - iommu_sva_unbind_device
        +-> iopf_queue_flush_dev
        +-> iommu_unbind_locked(to_iommu_bond(handle))
        這裏的handle是一個struct iommu_sva
- iommu_sva_set_ops(iommu_sva, iommu_sva_ops)

  這個接口把iommu_sva_ops傳遞給iommu_sva, iommu_sva_ops包含mm_exit回調。
  在上面的iommu_sva_bind_generic裏會調用mmu_notifier_register給當前的mm裏
  註冊一個notifier,在mm發生改變的時候,mm子系統可以觸發notifier裏的回調
  函數。當前的代碼裏,是在notifier的release回調裏去調用iommu_sva裏保存的
  mm_exit回調函數。

  SVA特性使得設備可以看到進程虛擬地址空間,這樣在進程虛擬地址空間銷燬的時候
  應該調用設備驅動提供的函數停止設備繼續訪問進程虛擬地址空間。這裏iommu_sva_set_ops
  就是把設備驅動的回調函數註冊給進程mm。

  注意上面的mmu_notifier_register註冊的iommu_mmu_notifier_ops回調裏。release
  只在進程異常時調用到,用戶態進程正常退出時並不會調用。在進程正常退出時,
  如何保證設備停止訪問將要釋放的進程地址空間,這裏還有疑問。

  進程退出的調用鏈是:
  kernel/exit.c:
  do_exit
    +-> exit_mm
  +-> mmput
    +-> exit_mmap
      +-> mmu_notifier_release

- iommu_sva_get_pasid

  這個接口返回返回smmu_sva對應的pasid數值,設備驅動需要把pasid配置給與這個
  smmu_sva相關的硬件資源。

需要使用SVA特性的社區驅動在調用上面的接口後,可以建立起靜態的數據結構。

3. 動態分析


  • 缺頁流程

    當一個PRI或者是一個stall event上報後, 軟件會在缺頁流程裏建立頁表,然後控制
    SMMU給設備返送reponse信息。我們可以從SMMU PRI queue或者是event queue的中斷
    處理流程入手跟蹤: e.g.PRI中斷流程

    devm_request_threaded_irq(..., arm_smmu_priq_thread, ...)
    arm_smmu_priq_thread
      +-> arm_smmu_handle_ppr
        +-> iommu_report_device_fault
          +-> iommu_fault_param->handler
            +-> iommu_queue_iopf /* 初始化參見上面第2部分 */
              +-> iopf_group = kzalloc
              +-> list_add(faults list in group, fault)
              +-> INIT_WORK(&group->work, iopf_handle_group)
              +-> queue_work(iopf_param->queue->wq, &group->work)
              這段代碼創建缺頁的group,並把當前的缺頁請求掛入group裏的鏈表,然後
              創建一個任務,並調度這個任務運行

              在工作隊列線程中:
              +-> iopf_handle_group
                +-> iopf_handle_single
                  +-> handle_mm_fault
                      這裏會最終申請內存並建立頁表

        +-> arm_smmu_page_response
            軟件執行完缺頁流程後,軟件控制SMMU向設備迴響應。
  • Invalid流程

    當軟件釋放申請的內存時,SMMU中關於這些內存的tlb以及設備ATC裏都要Invalid。
    進程mm變動的時候,調用註冊的io_mm notifier完成相關的tlb、atc的invalid。

    (to do: …)

4. 性能分析


以UACCE(https://lkml.org/lkml/2019/10/9/634)和ZIP壓縮解壓縮引擎爲測試case,
在SVA情況下。測試結果爲:

  1. 因爲使用SVA可以做到對用戶態malloc的內存直接訪問,ZIP性能得到很大提升。
    可以從原來的1900MB/s提升到5000+MB/s

  2. 從測試結果來看, 大量的SMMU tlb miss不會造成系統新能下降。或者說在把系統裏
    原來的內存拷貝去掉後, 換成SVA,性能上的增加遠大於smmu tlb miss的開銷。
    測試中使用的smmu的L1 tlb entry個數是512, L2 tlb entry個數是2048,tlb粒度是
    4KB, 我們假設就算這個smmu的所有tlb都給ZIP用(L2 tlb是這個smmu下面所接外設共享
    的), 那麼所有tlb所覆蓋的內存範圍也就512 × 4K + 2014 × 4K = 10MB。測試中,
    我們使用100M的測試數據,每次壓縮4K數據,直到把這100M的數據都壓縮完。可以看
    到就算前一個10M的數據都可以tlb命中,那麼在下一個10M的數據處理中,每個4K開始
    總會有一個tlb不命中。(該外設的burst大小是128 Byte, 所以在處理一個4K數據時,
    最多隻有第一次128 Byte的讀寫會有tlb miss,後續的tlb都是hit的)

  3. 如果在IO流程中有設備缺頁,性能會有很大的下降,至少會下降到沒有缺頁流程的
    1/6。在測試中,對於壓縮的源數據,在準備數據的時候會把數據寫到內存中,這個
    動作會引發CPU側的系統缺頁,爲了把目的數據內存也提前在CPU側缺頁,我們先用
    memset把目的數據內存都寫0。實際測試的時候可以發現,對於一次測試(如上所說的,
    把100MB數據以4K大小爲單位壓縮完), 在第一次測試的時候性能總是很差(執行時間
    很長), 如果我們,再以同樣的數據多測試幾次,並且統計每一次執行的時間,會發現
    除了第一次外,以後每次的執行時間都很短,這樣每次的性能都很好。

    一開始,懷疑是smmu的tlb命中率有關,因爲根據上面的分析,頁表應該在開始測試
    之前就在CPU側建立好了。但是,根據上面的分析可以知道,即使在一次測試裏smmu
    的tlb也會大量的刷新,每次測試的情況也是一樣的,不會出現後面測試的tlb miss
    會減少。實際中smmu pmcg工具測試的結果也表示,第一次測試中的tlb hit和後面測試
    的tlb hit(平均值)沒有差異,但是第一次的性能依然很差。
    (smmu pmcg的用法可以具體參考: https://blog.csdn.net/scarecrow_byr/article/details/101673228)

    於是懷疑還是缺頁的問題,在smmu缺頁處理里加了打印,發現第一次測試的時候,每
    次都會執行到smmu缺頁處理裏。懷疑是上面目的地址使用memset的方式無法引起缺頁,
    在memset之前,把目的地址隨便寫入一些值,然後再用memset清0,再次測試發現
    第一次測試是不會有設備層缺頁了,第一次測試的性能也和後面的一樣。

5. 虛擬化


    https://blog.csdn.net/scarecrow_byr/article/details/104606571
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章