16614a3c3SLinus Torvalds.. include:: ../disclaimer-zh_CN.rst
26614a3c3SLinus Torvalds
36614a3c3SLinus Torvalds:Original: Documentation/mm/page_migration.rst
46614a3c3SLinus Torvalds
56614a3c3SLinus Torvalds:翻译:
66614a3c3SLinus Torvalds
76614a3c3SLinus Torvalds 司延腾 Yanteng Si <siyanteng@loongson.cn>
86614a3c3SLinus Torvalds
96614a3c3SLinus Torvalds:校译:
106614a3c3SLinus Torvalds
116614a3c3SLinus Torvalds========
126614a3c3SLinus Torvalds页面迁移
136614a3c3SLinus Torvalds========
146614a3c3SLinus Torvalds
156614a3c3SLinus Torvalds页面迁移允许在进程运行时在NUMA系统的节点之间移动页面的物理位置。这意味着进程所看到的虚拟地
166614a3c3SLinus Torvalds址并没有改变。然而,系统会重新安排这些页面的物理位置。
176614a3c3SLinus Torvalds
186614a3c3SLinus Torvalds也可以参见 :ref: `<异构内存管理 (HMM)>` 以了解将页面迁移到设备私有内存或从设备私有内存中迁移。
196614a3c3SLinus Torvalds
206614a3c3SLinus Torvalds页面迁移的主要目的是通过将页面移到访问该内存的进程所运行的处理器附近来减少内存访问的延迟。
216614a3c3SLinus Torvalds
226614a3c3SLinus Torvalds页面迁移允许进程通过MF_MOVE和MF_MOVE_ALL选项手动重新定位其页面所在的节点,同时通过
236614a3c3SLinus Torvaldsmbind()设置一个新的内存策略。一个进程的页面也可以通过sys_migrate_pages()函数调用从另
246614a3c3SLinus Torvalds一个进程重新定位。migrate_pages()函数调用接收两组节点,并将一个进程位于旧节点上的页面移
256614a3c3SLinus Torvalds动到目标节点上。页面迁移功能由Andi Kleen的numactl包提供(需要0.9.3以上的版本,其仓库
266614a3c3SLinus Torvalds地址https://github.com/numactl/numactl.git)。numactl提供了libnuma,它为页面迁移
276614a3c3SLinus Torvalds提供了与其他NUMA功能类似的接口。执行 cat ``/proc/<pid>/numa_maps``  允许轻松查看进
286614a3c3SLinus Torvalds程的页面位置。参见proc(5)手册中的numa_maps文档。
296614a3c3SLinus Torvalds
306614a3c3SLinus Torvalds如果调度程序将一个进程重新安置到一个遥远的节点上的处理器,手动迁移是很有用的。批量调度程序
316614a3c3SLinus Torvalds或管理员可以检测到这种情况,并将进程的页面移到新处理器附近。内核本身只提供手动的页迁移支持。
326614a3c3SLinus Torvalds自动的页面迁移可以通过用户空间的进程移动页面来实现。一个特殊的函数调用 "move_pages" 允许
336614a3c3SLinus Torvalds在一个进程中移动单个页面。例如,NUMA分析器可以获得一个显示频繁的节点外访问的日志,并可以使
346614a3c3SLinus Torvalds用这个结果将页面移动到更有利的位置。
356614a3c3SLinus Torvalds
366614a3c3SLinus Torvalds较大型的设备通常使用cpusets将系统分割成若干个节点。Paul Jackson为cpusets配备了当任务被
376614a3c3SLinus Torvalds转移到另一个cpuset时移动页面的能力(见:ref:`CPUSETS <cpusets>`)。Cpusets允许进程定
386614a3c3SLinus Torvalds位的自动化。如果一个任务被移到一个新的cpuset上,那么它的所有页面也会随之移动,这样进程的
396614a3c3SLinus Torvalds性能就不会急剧下降。如果cpuset允许的内存节点发生变化,cpuset中的进程页也会被移动。
406614a3c3SLinus Torvalds
416614a3c3SLinus Torvalds页面迁移允许为所有迁移技术保留一组节点中页面的相对位置,这将保留生成的特定内存分配模式即使
426614a3c3SLinus Torvalds进程已被迁移。为了保留内存延迟,这一点是必要的。迁移后的进程将以类似的性能运行。
436614a3c3SLinus Torvalds
446614a3c3SLinus Torvalds页面迁移分几个步骤进行。首先为那些试图从内核中使用migrate_pages()的进程做一个高层次的
456614a3c3SLinus Torvalds描述(对于用户空间的使用,可以参考上面提到的Andi Kleen的numactl包),然后对低水平的细
466614a3c3SLinus Torvalds节工作做一个低水平描述。
476614a3c3SLinus Torvalds
486614a3c3SLinus Torvalds在内核中使用 migrate_pages()
496614a3c3SLinus Torvalds============================
506614a3c3SLinus Torvalds
516614a3c3SLinus Torvalds1. 从LRU中移除页面。
526614a3c3SLinus Torvalds
536614a3c3SLinus Torvalds   要迁移的页面列表是通过扫描页面并把它们移到列表中来生成的。这是通过调用 isolate_lru_page()
546614a3c3SLinus Torvalds   来完成的。调用isolate_lru_page()增加了对该页的引用,这样在页面迁移发生时它就不会
556614a3c3SLinus Torvalds   消失。它还可以防止交换器或其他扫描器遇到该页。
566614a3c3SLinus Torvalds
576614a3c3SLinus Torvalds
58*4e096ae1SMatthew Wilcox (Oracle)2. 我们需要有一个new_folio_t类型的函数,可以传递给migrate_pages()。这个函数应该计算
596614a3c3SLinus Torvalds   出如何在给定的旧页面中分配正确的新页面。
606614a3c3SLinus Torvalds
616614a3c3SLinus Torvalds3. migrate_pages()函数被调用,它试图进行迁移。它将调用该函数为每个被考虑迁移的页面分
626614a3c3SLinus Torvalds   配新的页面。
636614a3c3SLinus Torvalds
646614a3c3SLinus Torvaldsmigrate_pages()如何工作
656614a3c3SLinus Torvalds=======================
666614a3c3SLinus Torvalds
676614a3c3SLinus Torvaldsmigrate_pages()对它的页面列表进行了多次处理。如果当时对一个页面的所有引用都可以被移除,
686614a3c3SLinus Torvalds那么这个页面就会被移动。该页已经通过isolate_lru_page()从LRU中移除,并且refcount被
696614a3c3SLinus Torvalds增加,以便在页面迁移发生时不释放该页。
706614a3c3SLinus Torvalds
716614a3c3SLinus Torvalds步骤:
726614a3c3SLinus Torvalds
736614a3c3SLinus Torvalds1. 锁定要迁移的页面。
746614a3c3SLinus Torvalds
756614a3c3SLinus Torvalds2. 确保回写已经完成。
766614a3c3SLinus Torvalds
776614a3c3SLinus Torvalds3. 锁定我们要迁移到的新页面。锁定它是为了在迁移过程中立即阻止对这个(尚未更新的)页面的
786614a3c3SLinus Torvalds   访问。
796614a3c3SLinus Torvalds
806614a3c3SLinus Torvalds4. 所有对该页的页表引用都被转换为迁移条目。这就减少了一个页面的mapcount。如果产生的
816614a3c3SLinus Torvalds   mapcount不是零,那么我们就不迁移该页。所有试图访问该页的用户空间进程现在将等待页
826614a3c3SLinus Torvalds   面锁或者等待迁移页表项被移除。
836614a3c3SLinus Torvalds
846614a3c3SLinus Torvalds5. i_pages的锁被持有。这将导致所有试图通过映射访问该页的进程在自旋锁上阻塞。
856614a3c3SLinus Torvalds
866614a3c3SLinus Torvalds6. 检查该页的Refcount,如果还有引用,我们就退出。否则,我们知道我们是唯一引用这个页
876614a3c3SLinus Torvalds   面的人。
886614a3c3SLinus Torvalds
896614a3c3SLinus Torvalds7. 检查基数树,如果它不包含指向这个页面的指针,那么我们就退出,因为其他人修改了基数树。
906614a3c3SLinus Torvalds
916614a3c3SLinus Torvalds8. 新的页面要用旧的页面的一些设置进行预处理,这样访问新的页面就会发现一个具有正确设置
926614a3c3SLinus Torvalds   的页面。
936614a3c3SLinus Torvalds
946614a3c3SLinus Torvalds9. 基数树被改变以指向新的页面。
956614a3c3SLinus Torvalds
966614a3c3SLinus Torvalds10. 旧页的引用计数被删除,因为地址空间的引用已经消失。对新页的引用被建立,因为新页被
976614a3c3SLinus Torvalds    地址空间引用。
986614a3c3SLinus Torvalds
996614a3c3SLinus Torvalds11. i_pages锁被放弃。这样一来,在映射中的查找又变得可能了。进程将从在锁上自旋到在
1006614a3c3SLinus Torvalds    被锁的新页上睡眠。
1016614a3c3SLinus Torvalds
1026614a3c3SLinus Torvalds12. 页面内容被复制到新的页面上。
1036614a3c3SLinus Torvalds
1046614a3c3SLinus Torvalds13. 剩余的页面标志被复制到新的页面上。
1056614a3c3SLinus Torvalds
1066614a3c3SLinus Torvalds14. 旧的页面标志被清除,以表明该页面不再提供任何信息。
1076614a3c3SLinus Torvalds
1086614a3c3SLinus Torvalds15. 新页面上的回写队列被触发了。
1096614a3c3SLinus Torvalds
1106614a3c3SLinus Torvalds16. 如果迁移条目被插入到页表中,那么就用真正的ptes替换它们。这样做将使那些尚未等待页
1116614a3c3SLinus Torvalds    锁的用户空间进程能够访问。
1126614a3c3SLinus Torvalds
1136614a3c3SLinus Torvalds17. 页面锁从新旧页面上被撤销。等待页锁的进程将重做他们的缺页异常,并将到达新的页面。
1146614a3c3SLinus Torvalds
1156614a3c3SLinus Torvalds18. 新的页面被移到LRU中,可以被交换器等再次扫描。
1166614a3c3SLinus Torvalds
1176614a3c3SLinus Torvalds非LRU页面迁移
1186614a3c3SLinus Torvalds=============
1196614a3c3SLinus Torvalds
1206614a3c3SLinus Torvalds尽管迁移最初的目的是为了减少NUMA的内存访问延迟,但压缩也使用迁移来创建高阶页面。
1216614a3c3SLinus Torvalds
1226614a3c3SLinus Torvalds目前实现的问题是,它被设计为只迁移*LRU*页。然而,有一些潜在的非LRU页面可以在驱动中
1236614a3c3SLinus Torvalds被迁移,例如,zsmalloc,virtio-balloon页面。
1246614a3c3SLinus Torvalds
1256614a3c3SLinus Torvalds对于virtio-balloon页面,迁移代码路径的某些部分已经被钩住,并添加了virtio-balloon
1266614a3c3SLinus Torvalds的特定函数来拦截迁移逻辑。这对一个驱动来说太特殊了,所以其他想让自己的页面可移动的驱
1276614a3c3SLinus Torvalds动就必须在迁移路径中添加自己的特定钩子。
1286614a3c3SLinus Torvalds
1296614a3c3SLinus Torvalds为了克服这个问题,VM支持非LRU页面迁移,它为非LRU可移动页面提供了通用函数,而在迁移
1306614a3c3SLinus Torvalds路径中没有特定的驱动程序钩子。
1316614a3c3SLinus Torvalds
1326614a3c3SLinus Torvalds如果一个驱动程序想让它的页面可移动,它应该定义三个函数,这些函数是
1336614a3c3SLinus Torvaldsstruct address_space_operations的函数指针。
1346614a3c3SLinus Torvalds
1356614a3c3SLinus Torvalds1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);``
1366614a3c3SLinus Torvalds
1376614a3c3SLinus Torvalds   VM对驱动的isolate_page()函数的期望是,如果驱动成功隔离了该页,则返回*true*。
1386614a3c3SLinus Torvalds   返回true后,VM会将该页标记为PG_isolated,这样多个CPU的并发隔离就会跳过该
1396614a3c3SLinus Torvalds   页进行隔离。如果驱动程序不能隔离该页,它应该返回*false*。
1406614a3c3SLinus Torvalds
1416614a3c3SLinus Torvalds   一旦页面被成功隔离,VM就会使用page.lru字段,因此驱动程序不应期望保留这些字段的值。
1426614a3c3SLinus Torvalds
1436614a3c3SLinus Torvalds2. ``int (*migratepage) (struct address_space *mapping,``
1446614a3c3SLinus Torvalds|	``struct page *newpage, struct page *oldpage, enum migrate_mode);``
1456614a3c3SLinus Torvalds
1466614a3c3SLinus Torvalds   隔离后,虚拟机用隔离的页面调用驱动的migratepage()。migratepage()的功能是将旧页
1476614a3c3SLinus Torvalds   的内容移动到新页,并设置struct page newpage的字段。请记住,如果你成功迁移了旧页
1486614a3c3SLinus Torvalds   并返回MIGRATEPAGE_SUCCESS,你应该通过page_lock下的__ClearPageMovable()向虚
1496614a3c3SLinus Torvalds   拟机表明旧页不再可移动。如果驱动暂时不能迁移该页,驱动可以返回-EAGAIN。在-EAGAIN
1506614a3c3SLinus Torvalds   时,VM会在短时间内重试页面迁移,因为VM将-EAGAIN理解为 "临时迁移失败"。在返回除
1516614a3c3SLinus Torvalds   -EAGAIN以外的任何错误时,VM将放弃页面迁移而不重试。
1526614a3c3SLinus Torvalds
1536614a3c3SLinus Torvalds   在migratepage()函数中,驱动程序不应该接触page.lru字段。
1546614a3c3SLinus Torvalds
1556614a3c3SLinus Torvalds3. ``void (*putback_page)(struct page *);``
1566614a3c3SLinus Torvalds
1576614a3c3SLinus Torvalds   如果在隔离页上迁移失败,VM应该将隔离页返回给驱动,因此VM用隔离页调用驱动的
1586614a3c3SLinus Torvalds   putback_page()。在这个函数中,驱动应该把隔离页放回自己的数据结构中。
1596614a3c3SLinus Torvalds
1606614a3c3SLinus Torvalds非LRU可移动页标志
1616614a3c3SLinus Torvalds
1626614a3c3SLinus Torvalds   有两个页面标志用于支持非LRU可移动页面。
1636614a3c3SLinus Torvalds
1646614a3c3SLinus Torvalds   * PG_movable
1656614a3c3SLinus Torvalds
1666614a3c3SLinus Torvalds     驱动应该使用下面的函数来使页面在page_lock下可移动。::
1676614a3c3SLinus Torvalds
1686614a3c3SLinus Torvalds	void __SetPageMovable(struct page *page, struct address_space *mapping)
1696614a3c3SLinus Torvalds
1706614a3c3SLinus Torvalds     它需要address_space的参数来注册将被VM调用的migration family函数。确切地说,
1716614a3c3SLinus Torvalds     PG_movable不是struct page的一个真正的标志。相反,VM复用了page->mapping的低
1726614a3c3SLinus Torvalds     位来表示它::
1736614a3c3SLinus Torvalds
1746614a3c3SLinus Torvalds	#define PAGE_MAPPING_MOVABLE 0x2
1756614a3c3SLinus Torvalds	page->mapping = page->mapping | PAGE_MAPPING_MOVABLE;
1766614a3c3SLinus Torvalds
1776614a3c3SLinus Torvalds     所以驱动不应该直接访问page->mapping。相反,驱动应该使用page_mapping(),它可
1786614a3c3SLinus Torvalds     以在页面锁下屏蔽掉page->mapping的低2位,从而获得正确的struct address_space。
1796614a3c3SLinus Torvalds
1806614a3c3SLinus Torvalds     对于非LRU可移动页面的测试,VM支持__PageMovable()函数。然而,它并不能保证识别
1816614a3c3SLinus Torvalds     非LRU可移动页面,因为page->mapping字段与struct page中的其他变量是统一的。如
1826614a3c3SLinus Torvalds     果驱动程序在被虚拟机隔离后释放了页面,尽管page->mapping设置了PAGE_MAPPING_MOVABLE,
1836614a3c3SLinus Torvalds     但它并没有一个稳定的值(看看__ClearPageMovable)。但是__PageMovable()在页
1846614a3c3SLinus Torvalds     面被隔离后,无论页面是LRU还是非LRU可移动的,调用它开销都很低,因为LRU页面在
1856614a3c3SLinus Torvalds     page->mapping中不可能有PAGE_MAPPING_MOVABLE设置。在用pfn扫描中的lock_page()
1866614a3c3SLinus Torvalds     进行更大开销的检查来选择受害者之前,它也很适合只是瞥一眼来测试非LRU可移动的页面。
1876614a3c3SLinus Torvalds
1886614a3c3SLinus Torvalds     为了保证非LRU的可移动页面,VM提供了PageMovable()函数。与__PageMovable()不
1896614a3c3SLinus Torvalds     同,PageMovable()在lock_page()下验证page->mapping和
1906614a3c3SLinus Torvalds     mapping->a_ops->isolate_page。lock_page()可以防止突然破坏page->mapping。
1916614a3c3SLinus Torvalds
1926614a3c3SLinus Torvalds     使用__SetPageMovable()的驱动应该在释放页面之前通过page_lock()下的
1936614a3c3SLinus Torvalds     __ClearMovablePage()清除该标志。
1946614a3c3SLinus Torvalds
1956614a3c3SLinus Torvalds   * PG_isolated
1966614a3c3SLinus Torvalds
1976614a3c3SLinus Torvalds     为了防止几个CPU同时进行隔离,VM在lock_page()下将隔离的页面标记为PG_isolated。
1986614a3c3SLinus Torvalds     因此,如果一个CPU遇到PG_isolated非LRU可移动页面,它可以跳过它。驱动程序不需要
1996614a3c3SLinus Torvalds     操作这个标志,因为VM会自动设置/清除它。请记住,如果驱动程序看到PG_isolated页,
2006614a3c3SLinus Torvalds     这意味着该页已经被VM隔离,所以它不应该碰page.lru字段。PG_isolated标志与
2016614a3c3SLinus Torvalds     PG_reclaim标志是同义的,所以驱动程序不应该为自己的目的使用PG_isolated。
2026614a3c3SLinus Torvalds
2036614a3c3SLinus Torvalds监测迁移
2046614a3c3SLinus Torvalds========
2056614a3c3SLinus Torvalds
2066614a3c3SLinus Torvalds以下事件(计数器)可用于监控页面迁移。
2076614a3c3SLinus Torvalds
2086614a3c3SLinus Torvalds1. PGMIGRATE_SUCCESS: 正常的页面迁移成功。每个计数器意味着一个页面被迁移了。如果该
2096614a3c3SLinus Torvalds   页是一个非THP和非hugetlb页,那么这个计数器会增加1。如果该页面是一个THP或hugetlb
2106614a3c3SLinus Torvalds   页面,那么这个计数器会随着THP或hugetlb子页面的数量而增加。例如,迁移一个有4KB大小
2116614a3c3SLinus Torvalds   的基础页(子页)的2MB THP,将导致这个计数器增加512。
2126614a3c3SLinus Torvalds
2136614a3c3SLinus Torvalds2. PGMIGRATE_FAIL: 正常的页面迁移失败。与上面PGMIGRATE_SUCCESS的计数规则相同:如
2146614a3c3SLinus Torvalds   果是THP或hugetlb,这个计数将被子页的数量增加。
2156614a3c3SLinus Torvalds
2166614a3c3SLinus Torvalds3. THP_MIGRATION_SUCCESS: 一个THP被迁移而没有被分割。
2176614a3c3SLinus Torvalds
2186614a3c3SLinus Torvalds4. THP_MIGRATION_FAIL: 一个THP不能被迁移,也不能被分割。
2196614a3c3SLinus Torvalds
2206614a3c3SLinus Torvalds5. THP_MIGRATION_SPLIT: 一个THP被迁移了,但不是这样的:首先,这个THP必须被分割。
2216614a3c3SLinus Torvalds   在拆分之后,对它的子页面进行了迁移重试。
2226614a3c3SLinus Torvalds
2236614a3c3SLinus TorvaldsTHP_MIGRATION_* 事件也会更新相应的PGMIGRATE_SUCCESS或PGMIGRATE_FAIL事件。
2246614a3c3SLinus Torvalds例如,一个THP迁移失败将导致THP_MIGRATION_FAIL和PGMIGRATE_FAIL增加。
2256614a3c3SLinus Torvalds
2266614a3c3SLinus TorvaldsChristoph Lameter,2006年5月8日。
2276614a3c3SLinus Torvalds
2286614a3c3SLinus TorvaldsMinchan Kim,2016年3月28日。
229