iT邦幫忙

2021 iThome 鐵人賽

DAY 23
1
Software Development

閱讀 Linux Kernel 文件系列 第 23

# Day 23 Heterogeneous Memory Management (HMM) (三)

文件

定址空間鏡像實作和 API
=====================

定址空間鏡像的主要目標是
複製一個範圍的 CPU 分頁表到裝置的分頁表;
HMM 作為輔助保持兩者同步。
一個要鏡像程序定址空間的裝置驅動程式
必須從註冊 mmu_interval_notifier 開始::

 int mmu_interval_notifier_insert(struct mmu_interval_notifier *interval_sub,
                  struct mm_struct *mm, unsigned long start,
                  unsigned long length,
                  const struct mmu_interval_notifier_ops *ops);

在 ops->invalidate() 回呼函數執行期間,
裝置驅動程式必須更新該範圍(標記該範圍為只能讀取,或完全 unmap ... 等)。
這裝置必須在驅動程式的回呼函數結束之前完成更新。

當裝置驅動程式想要填充一個虛擬地址範圍時,可以用::

  int hmm_range_fault(struct hmm_range *range);

如果有寫的存取時,會在缺失或是只有讀取權限的分頁項上觸發分頁錯誤。
分頁錯誤使用通用的 mm 分頁錯誤處理程式碼路徑,就像在處理 CPU 分頁面錯誤。

這兩個函數都將 CPU 分頁表項複製到它們的 pfns 陣列中。
每個陣列中的分頁表項都對應於虛擬範圍中的位址。
HMM 提供一組旗標來輔助驅動程式來識別特殊的 CPU 分頁表項。

在 sync_cpu_device_pagetables() 回呼中"鎖"是最重要的部分,
驅動程式必須遵守以保持所有事情正確地同步,使用模式是::

 int driver_populate_range(...)
 {
      struct hmm_range range;
      ...

      range.notifier = &interval_sub;
      range.start = ...;
      range.end = ...;
      range.hmm_pfns = ...;

      if (!mmget_not_zero(interval_sub->notifier.mm))
          return -EFAULT;

 again:
      range.notifier_seq = mmu_interval_read_begin(&interval_sub);
      mmap_read_lock(mm);
      ret = hmm_range_fault(&range);
      if (ret) {
          mmap_read_unlock(mm);
          if (ret == -EBUSY)
                 goto again;
          return ret;
      }
      mmap_read_unlock(mm);

      take_lock(driver->update);
      if (mmu_interval_read_retry(&ni, range.notifier_seq) {
          release_lock(driver->update);
          goto again;
      }

      /* Use pfns array content to update device page table,
       * under the update lock */

      release_lock(driver->update);
      return 0;
 }

driver->update 鎖與驅動程序在其內部的 invalidate 回呼函數
使用的鎖是相同的。在呼叫 mmu_interval_read_retry() 之前必須持有該鎖
以避免與同時執行的 CPU 分頁表更新發生任何競爭(race)。

利用 default_flags 和 pfn_flags_mask
====================================

hmm_range 結構有 2 個欄位,default_flags 和 pfn_flags_mask,
它們指定了整個範圍的 fault 或 snapshot 方針,
而不需對 pfns 陣列的每個元素設置。

例如,如果裝置驅動程式想要讓某個範圍的分頁都至少具有可讀權限,它設置::

    range->default_flags = HMM_PFN_REQ_FAULT;
    range->pfn_flags_mask = 0;

並如上所述呼叫 hmm_range_fault()。
這將填充所有在範圍內的分頁,使之至少具有讀取權限。

現在假設驅動程式想要做同樣的事情,
除了範圍內的其中一個分頁,它想要擁有寫權限。
那麼驅動程式設置::

    range->default_flags = HMM_PFN_REQ_FAULT;
    range->pfn_flags_mask = HMM_PFN_REQ_WRITE;
    range->pfns[index_of_write] = HMM_PFN_REQ_WRITE;

有了這樣的設置,HMM 將發生分頁錯誤並且將所有分頁設立至少讀取(即有效)權限,
並且對於 address == range->start + (index_of_write << PAGE_SHIFT) 
它會設立寫權限,即,如果 CPU pte 沒有設置寫權限,則 HMM 將呼叫 handle_mm_fault()。

hmm_range_fault 完成後,旗標位元會被設置為當前頁表狀態,
即,如果分頁是可寫的,那 HMM_PFN_VALID | HMM_PFN_WRITE 將被設置。

從核心主體的角度來表示和管理裝置記憶體
===================================

嘗試了幾種不同的設計來支援裝置記憶體。
第一個使用特定於裝置的資料結構來儲存有關遷移記憶體的資訊
並且 HMM 將自己 hook 在 mm 程式碼的各個位置
以處理對由裝置記憶體儲存的位址。
最後發現這方法會複製大部分 struct page 的欄位,
還需要更新很多核心程式碼路徑以讓核心瞭解這種新的記憶體。

大多數核心程式碼路徑不會嘗試存取分頁後的記憶體而只關心 struct page 的內容。
正因為如此,HMM 改變成直接使用 struct page 來管理裝置記憶體,
而大多數核心程式碼路徑的不知道其中的區別。
我們只需要確保沒有人試圖從 CPU 端映射這些分頁。

我的理解

今天閱讀的文件內容描述了 HMM 達成 mirror CPU 分頁表和 share memory space 的實作內容,
就如同前面文件作者所述,HMM 機制是提供輔助的 function,以提供 device driver 實作時使用。

接下來應該就都是 driver 要自己實作的部分

一開始提到的要先註冊 mmu_interval_notifier,然後在 ops->invalidation return 之前要完成該範圍的分頁資訊更新。
(ops->invalidation 是 mmu_interval_notifier_ops 的一個 function pointer)

再來使用 hmm_range_fault 將這些更新 populate 出去

sync_cpu_device_pagetables() 這個似乎是個 deprecated 的 function 在 5.4 kernel 之前還有出現過,看來文件這部分可能沒有修改到。

然後有一段範例的 driver 程式碼示範了要如何運用 hmm。

後記

  • Leverage default_flags and pfn_flags_mask 這段裡面有一個句子 This will fill fault all pages in the range with at least read permission.,不知道裡面的 fill 或是 fault 是不是多的 (?

延伸閱讀


上一篇
# Day 22 Heterogeneous Memory Management (HMM) (二)
下一篇
# Day 24 Heterogeneous Memory Management (HMM) (四)
系列文
閱讀 Linux Kernel 文件30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言