# Day 24 Heterogeneous Memory Management (HMM) (四)

文件


移入和移出装置记忆体
===================

因为 CPU 不能直接存取装置记忆体,所以装置驱动程序必须
使用硬体 DMA 或装置特定的 load/store 指令来迁移资料。
migrate_vma_setup()、migrate_vma_pages() 
和 migrate_vma_finalize() 函数旨在使驱动程序更易於撰写
以及集中通用的跨驱动程序程序码。

在将分页迁移到装置私有记忆体前,需要创建特殊的、装置私有的 "struct page"。
这些将用作特殊的 “swap” 分页表项,使 CPU 程序在尝试存取
已迁移到装置私有记忆体上的分页会产生错误。

可以通过以下方式分配和释放::

    struct resource *res;
    struct dev_pagemap pagemap;

    res = request_free_mem_region(&iomem_resource, /* number of bytes */,
                                  "name of driver resource");
    pagemap.type = MEMORY_DEVICE_PRIVATE;
    pagemap.range.start = res->start;
    pagemap.range.end = res->end;
    pagemap.nr_range = 1;
    pagemap.ops = &device_devmem_ops;
    memremap_pages(&pagemap, numa_node_id());

    memunmap_pages(&pagemap);
    release_mem_region(pagemap.range.start, range_len(&pagemap.range));

当资源可以绑定到 “struct device” 时,
还有 devm_request_free_mem_region()、devm_memremap_pages()、
devm_memunmap_pages() 和 devm_release_mem_region() 可用。

整体迁移步骤类似於在系统记忆体上迁移 NUMA 分页
(见:ref:`页面迁移 <page_migration>`)只不过步骤是分开在
装置驱动程序特定的程序码和共享通用的程序码之间:

1. ``mmap_read_lock()``

   装置驱动程序必须传递一个 ``struct vm_area_struct`` 
   到 migrate_vma_setup() 所以必须在迁移中持有 mmap_read_lock() 
   或 mmap_write_lock()。
   
2. ``migrate_vma_setup(struct migrate_vma *args)``
   
   装置驱动程序初始化 ``struct migrate_vma`` 栏位
   并传递该指标到 migrate_vma_setup()。
   ``args->flags`` 栏位用於筛选应迁移那些分页。
   例如,设置 ``MIGRATE_VMA_SELECT_SYSTEM`` 只会迁移系统记忆体;
   而 ``MIGRATE_VMA_SELECT_DEVICE_PRIVATE`` 只会迁移驻留在
   装置上的私有记忆体。如果设置了後者,则 ``args->pgmap_owner`` 栏位
   是用於标识驱动程序拥有的装置私有分页。
   这样能够避免尝试迁移驻留在其他设备中的装置私有分页。
   目前只有匿名私有 VMA 范围能够从在系统记忆体和装置记忆体之间做迁移。
   
   migrate_vma_setup() 所做的第一步是使用
   ``mmu_notifier_invalidate_range_start()``
   和 ``mmu_notifier_invalidate_range_end()`` 
   在解析分页表(page table walk) 附近,
   用要被迁移的 PFNs 填充 ``args->src`` 阵列,
   来使其他装置的 MMU 被无效化(invalidate)。
   ``invalidate_range_start()`` 回呼函数被传递一个 
   ``struct mmu_notifier_range``
   其中 ``event`` 栏位被设置为 ``MMU_NOTIFY_MIGRATE`` 
   还有 ``owner`` 栏位被设置为
   传递给 migrate_vma_setup() 的 ``args->pgmap_owner``。
   这使装置驱动程序能跳过失效回呼函数,
   仅使实际迁移的装置私有 MMU 映射无效。
   这将在下一节中详细解释。
      
   在遍历分页表时,``pte_none()`` 或 ``is_zero_pfn()`` 项
   导致一个有效的 “zero” PFN 被存在 ``args->src`` 阵列中。
   这让驱动程序分配装置私有记忆体并将之它清零而非复制一个分页的零。
   系统记忆体中的有效 PTE 项或装置私有 struct page 将
   被 “lock_page()” 锁上(locked)、
   被独立於 LRU(系统记忆体因为装置私有分业并不在 LRU 上)、
   被程序中取消映射,
   一个特殊迁移的 PTE 会被插入到原始 PTE 的位置。
   migrate_vma_setup() 也会清空 ``args->dst`` 阵列。
   
3. 装置驱动程序分配目标分页,并将来源分页复制到目标分页。
   
   驱动程序检查每个 ``src`` 项以查看 ``MIGRATE_PFN_MIGRATE``位元
   有没有被设置,并跳过没有在迁移的项。
   装置驱动程序也可以选择不填写该分页 ``dst`` 阵列,
   来跳过迁移该分页。
      
   然後驱动程序分配一个装置私有的 struct page 
   或一个系统记忆体分页,用 ``lock_page()`` 锁上该页,
   并填入 ``dst`` 阵列项::
   
     dst[i] = migrate_pfn(page_to_pfn(dpage)) | MIGRATE_PFN_LOCKED;
   
   现在驱动程序知道这个页面正在被迁移,
   它可以使装置私有 MMU 映射无效并复制装置私有记忆体
   到系统记忆体或其他装置的私有分页。
   Linux 核心主体会处理 CPU 分页表无效化,
   因此装置驱动程序只需使其自己的 MMU 映射无效。
      
   驱动程序可以使用 “migrate_pfn_to_page(src[i])” 来取得来源的 ``struct page``,
   然後将来源分页复制到目标上,
   或如果指标是 ``NULL`` 表示来源分页并未被填充到系统记忆体中,
   而清空目标装置私有记忆体。

4. ``migrate_vma_pages()``

   这一步是实际迁移 "被执行" 的地方。
      
   如果来源分页是 ``pte_none()`` 或 ``is_zero_pfn()`` 分页,
   这步骤是将新分配的分页插入 CPU 页表的位置。
   如果 CPU 执行续在同一分页上出现错误,这可能会失败。
   然而,分页表被锁上,只有一个新页面将被插入。
   如果装置驱动程序输掉竞争(race),它将看到 “MIGRATE_PFN_MIGRATE” 位元被清除。
      
   如果来源分页被锁定、隔离等,
   来源的 ``struct page`` 讯息被复制到目标 ``struct page`` 
   在 CPU 端完成迁移。
   If the source page was locked, isolated, etc. the source ``struct page``
   information is now copied to destination ``struct page`` finalizing the
   migration on the CPU side.

5. 装置驱动程序为仍在迁移的分页更新装置 MMU 分页表,回溯不迁移的分页。

   如果 src 项仍然设置了 MIGRATE_PFN_MIGRATE 位元,
   则装置驱动程序可以更新装置 MMU 并,
   如果 ``MIGRATE_PFN_WRITE`` 位已设置,就设置 write enable 位元。
   
6. ``migrate_vma_finalize()``

   这一步将特殊的迁移分页表项替换为新分页的页表项
   并释放对来源和目标 ``struct page`` 的引用。
   
7. ``mmap_read_unlock()``

   锁现在可以被释放了。

Exclusive access memory
=======================

某些装置具有诸如原子 PTE 位元之类的功能,
可用於实做系统记忆体上的原子操作。
支援对共享记忆体分页的原子操作,
这样的装置需要对该分页有,单独於其他使用者空间 CPU 存取的能力。
``make_device_exclusive_range()`` 函数
可使使用者空间无法存取该记忆体范围。

这将以特殊的 swap 项替换掉该范围内所有分页的映射。
任何存取 swap 项的尝试都会导致错误,
而这个错误会透过用原始映射来替换这个 swap 项来解决。
装置驱动程序会被 MMU notifier 通知,映射已被更改,
此後它将不再具有对该分页的单独存取权限。
单独存取会被保证持续到驱动程序取消分页锁定(page lock)和分页参考(page reference),
在这之後的任何 CPU 错误都可以按照描述进行处理。

记忆体 cgroup (memcg) 和 rss 帐务纪录
====================================

目前,装置记忆体在 rss 计数器中被视为一般分页
(如果装置分页用於匿名分页,则匿名,如果装置分页用於文件储存的分页,则是文件,
如果设备页面用於共享记忆体,则为 shmem)。
这是一个有意为之的选择,让可能开始运用装置记忆体的现有应用程序,
不知道有这样的存在,使其运行不受影响。

有一个缺点是 OOM 可能会清掉使用大量装置记忆体的应用程序,
而非使用很多一般系统记忆体的,
因此不会释放太多系统记忆体。
我们希望在决定以不同的方式计算装置记忆体之前,
能收集更多应用程序和系统在有装置记忆体的情况下,
对於记忆体资源压力反应的实际经验。

并且对记忆体 cgroup 做出了相同的决定。
装置记忆体分页和一般分页是用相同的记忆体 cgroup 来做计算。
这确实简化了装置记忆体的迁移。
这也意味着从装置记忆体迁移到一般记忆体不会失败,
因为这并不在记忆体 cgroup 的限制。
在我们得到更多关於装置记忆体是如何使用,且它对记忆体资源控制的影响後,
可能会重新审视这个选择。

请注意,装置记忆体永远不能由装置驱动程序或通过 GUP 固定(pinned),
因此这种记忆体在程序退出时会被释放。或在共享记忆体及文件储存的记忆体的情况下,
会在最後一次的存取时被删除(dropped)。

後记

  • This is allows the device driver 多了一个 is
  • is to invalidate other device's MMUs with the ``mmu_notifier_invalidate_range_start(()`` and ``mmu_notifier_invalidate_range_end()`` calls around the page table walks to fill in the ``args->src`` array with PFNs to be migrated. 也不太确定是在 page table walk 的时候,还是是在 page table walk 的附近,可能要再研究一下~
  • This also means that migration back from device memory to regular memory cannot fail because it would go above memory cgroup limit. 不是很确定这里的 go above 是指 "超过" 还是 "bypass" 的意思,感觉要是 bypass 比较合理~

今天先翻译完,明天来整理一下这整份文件的摘要!


<<:  大共享时代系列_017_共享洗衣机

>>:  Day-21 Child Process

Day 14:RecyclerView 进阶项目布局

本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 Udemy 和 Youtu...

通用标准评估--安全目标(ST)

-通用标准评估 安全目标(Security Target:ST) 供应商可以在安全目标(ST)中指...

Day 14 资料表之间的关联栏位

One2one: 一对一关系,格式为: fields.One2one(关联物件 Name, 字段显示...

30-25 之 DDD 战略设计 1 - 战略设计的目的

接下来我们将要开始重 DDD 的战略设计来开始谈谈,别忘了战略的重点在於 : 如何切 然後还有个金句...

【Day25】人力资源篇-Payroll

#odoo #开源系统 #数位赋能 #E化自主 人力资源流程当中大家最关切的应该就是领薪水了! 但是...