# Day 28 Page Migration (三)

文件

Non-LRU 分页迁移
================

虽然迁移旨在减少 NUMA 系统中记忆体存取的延迟,
想要创建高层级分页的记忆体压缩功能也是主要顾客。

当前实作的问题是它仅被设计来迁移 *LRU* 分页。
然而,在驱动程序中,存在着可能可以迁移的 non-lru 分页,
例如 zsmalloc、virtio-balloon 分页。

对於 virtio-balloon 分页,迁移程序码路径的某些部分已被接上
并添加了 virtio-balloon 特定的函数来拦截、接下迁移的逻辑。
这些已属於特定的驱动程序,所以其他想要使分页面也能移动的驱动程序
必须在迁移程序码路径中添加自己特定的 hook。

为了克服这个问题,VM 支援 non-LRU 分页迁移,
它提供了 non-LRU 的可移动页面一些通用函数,
且迁移程序码路径并不含有驱动程序特定的 hook 。

如果驱动程序想让自己的分页可移动,
它应该定义三个函数,
分别是 struct address_space_operations 的函数指标。

1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);``

   VM 对驱动程序的 isolate_page 函数的期望是
   如果驱动程序成功隔离分面,则返回 *true*。
   返回 true 时,VM 会将分页标记为 PG_isolated 
   所以在多个 CPU 中的并发隔离的处理跳过该分页。
   如果驱动程序无法隔离分页,则应返回 *false*。
   
   成功隔离分页後,VM 会使用 page.lru 栏位,
   因此驱动程序不应该期望保留该栏位中的值。

2. ``int (*migratepage) (struct address_space *mapping,``
|   ``struct page *newpage, struct page *oldpage, enum migrate_mode);``

   隔离後,VM 呼叫拥有隔离分页驱动程序的 migratepage。
   migratepage 的作用是将旧分页的内容移动到新分页
   并设置 struct page newpage 的栏位。
   请记住,如果您成功迁移了旧分页并返回 MIGRATEPAGE_SUCCESS,
   则您应该在 page_lock 的保护下,
   透过 __ClearPageMovable() 向 VM 指示旧分页不再可移动;
   如果驱动程序当下无法迁移页面,驱动程序可以返回-EAGAIN。
   收到 -EAGAIN,VM 将在短时间内重新尝试分页迁移,
   因为 VM 将 -EAGAIN 解释为 “临时迁移失败”。
   如果返回除了 -EAGAIN 之外的任何错误,VM 将放弃页面迁移而不重试。

   驱动程序不应存取 VM 在函数中使用的 page.lru 栏位。

3. ``void (*putback_page)(struct page *);``

   如果隔离分页迁移失败,VM 应归还隔离分页至驱动程序,
   因此 VM 使用驱动程序的 putback_page 来处理迁移失败分页。
   在这个函数中,驱动程序应该把隔离分页放回自己的资料结构中。

4. non-lru 可移动分页旗标
   
   有两个分页旗标用於支援 non-lru 的可移动页面。

   * PG_movable
   
     驱动程序应使用以下函数使分页在 page_lock 下可移动::

    void __SetPageMovable(struct page *page, struct address_space *mapping)
     
     它需要 address_space 的参数来注册
     会被 VM 使用的迁移系列的函数。
     确切地说,PG_movable 并不是一个 struct page 的真正旗标。
     而是,VM 重用 page->mapping 的低位元来表示它。
::
    #define PAGE_MAPPING_MOVABLE 0x2
    page->mapping = page->mapping | PAGE_MAPPING_MOVABLE;
      
     所以驱动程序不应该直接存取 page->mapping。
     而是应该在 page_lock 下,使用 page_mapping,
     被屏蔽掉低二位元的 page->mapping,
     以便获得正确的 struct address_space。
     
     对於是否为 non-lru 可移动分页的测试,
     VM 提供了 __PageMovable 函数。
     但它不能保证能识别 non-lru 可移动页面,
     因为 page->mapping 栏位与 struct page 中的其他变数是统合的。
     同样,如果驱动程序释放被 VM 隔离後的分页,
     page->mapping 并不会有一个稳定的值,
     虽然它有 PAGE_MAPPING_MOVABLE(参阅 __ClearPageMovable)。
     但是一旦分页被隔离则 __PageMovable 能不花成本的
     确认分页是否是 LRU 或 non-LRU 可移动的。
     因为 LRU 分页在 page->mapping 中永远不会有 PAGE_MAPPING_MOVABLE。
     这适合在 lock_page() 下更耗费成本的确认方式之前,
     先在 pfn 扫描选择受害者,测试是否为 non-lru 可移动分页。
    
     为了保证确认是否为 non-lru 可移动页面,
     VM 提供了 PageMovable 函数。
     与 __PageMovable 不同,PageMovable 函数在 lock_page() 下
     验证 page->mapping 和 mapping->a_ops->lock_page。
     lock_page() 用以防止 page->mapping 突然的被破坏。
     
     使用 __SetPageMovable 的驱动程序应该在 page_lock() 下透过
     __ClearMovablePage 在释放页面之前清除旗标。

   * PG_isolated
     
     为了防止多个 CPU 之间同时进行隔离,
     VM 在 lock_page() 下标记了隔离分页为 PG_isolated。
     所以如果 CPU 遇到 PG_isolated non-lru 可移动页面,
     它是可以跳过的。
     驱动程序不需要操控旗标,因为 VM 会自动设置/清除它。
     请记住,如果驱动程序看到 PG_isolated 分页,
     表示该分页面已被 VM 隔离,所以它不应存取 page.lru 栏位。
     PG_isolated 是 PG_reclaim 旗标的别名,
     因此驱动程序不应为了自己的目的使用该旗标。

我的理解

  • kernel 中,管理记忆体的模组会以2的幂次大小来纪录可用的记忆体区块,high-order 分页指的是较高幂次大小的分页。
  • 这里的 VM 感觉指的像是 Virtual Machine (因为提到 memory ballooning),但好像也没有那麽确定 ((也有可能是 virtual memory ?

後记

  • ... so concurrent isolation in several CPUs skip the page for isolation... 不是很确定这句话的意思。
  • 提到了 page_lock() 和 lock_page(),查了一下,page_lock 应该是一个变数,而 lock_page()才是 function,所以 page_lock() 看起来也是个 typo。

参考资料

延伸阅读


<<:  【D23】修改食谱#3:不知道来的客人是谁,先设定预设值

>>:  Day22 Create-react-app开发React

[Day 22] Reactive Programming - Spring WebFlux(Hello World) Part 2

前言 在上一篇成功实作最基本的WebFlux功能,看到了一些有点熟悉又有点陌生的新朋友,在这边补充说...

Day 1:Native vs. Not-so-native

接下来的文章,我把范围限缩在「桌上型作业系统」,目前主流的作业系统有 Windows, macOS,...

食谱搜寻系统demo

Icebear终於完成简易的食谱搜寻系统啦!!今天就献丑一下啦!! 明天Icebear会整理系统缺点...

html 改变按钮位子

昨天为止我们成功做出了多个按钮并且在鼠标移上时改变了他的效果,今天我们想要改变按钮的位子,让他可以靠...

【Day08】数据输入元件 - Rate

元件介绍 Rate 是一个评分元件。一方面可以对於评价的数据展示,另一方面可以让人进行对评分的操作。...