# 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 是不是多的 (?

延伸阅读


<<:  创建App-自创简略帐号设定

>>:  day17:First-class function

D25-(9/25)-群创(3481)-面板族群

注:发文日和截图的日期不一定是同一天,所以价格计算上和当日不同,是很正常的。 声明:这一系列文章并无...

D4- 如何透过 Google Apps Script 来整合 Google Form / Google Sheet 并自动寄出客制的 Email?

来到了第四天,我们可以进入比较复杂一点点的操作。但一样先讲结论,如果你很急着用,可以直接使用这份 A...

爆品技术文的3个写作技巧,一点就通

标题杀人 思路先於技术 莫忘初心 标题杀人 曝光就是第一个敲门砖,Google 演算法好不容易愿意...

ASP.NET MVC 从入门到放弃(Day19)-MVC模型(Model)介绍

接下来讲讲Model 部分... 简单来说Model负责与资料库沟通的相关逻辑,或者定义模板(.cs...

IOS、Python自学心得30天 Day-10 模组训练改善-2

前言: 储存上次进度训练模组後,目前遇到一个小问题,载入方面一直没试成功,所以我先做了一个辨别模型的...