.. include:: ../disclaimer-zh_CN.rst :Original: Documentation/mm/hmm.rst :翻译: å¸å»¶è…¾ Yanteng Si <siyanteng@loongson.cn> :æ ¡è¯‘: ================== 异构内å˜ç®¡ç† (HMM) ================== æ供基础设施和帮助程åºä»¥å°†éžå¸¸è§„内å˜ï¼ˆè®¾å¤‡å†…å˜ï¼Œå¦‚æ¿ä¸Š GPU 内å˜ï¼‰é›†æˆåˆ°å¸¸è§„å†…æ ¸è·¯å¾„ä¸ï¼Œå…¶ 基石是æ¤ç±»å†…å˜çš„专用struct page(请å‚阅本文档的第 5 至 7 节)。 HMM 还为 SVM(共享虚拟内å˜ï¼‰æ供了å¯é€‰çš„帮助程åºï¼Œå³å…许设备é€æ˜Žåœ°è®¿é—®ä¸Ž CPU ä¸€è‡´çš„ç¨‹åº åœ°å€ï¼Œè¿™æ„å‘³ç€ CPU 上的任何有效指针也是该设备的有效指针。这对于简化高级异构计算的使用å˜å¾— å¿…ä¸å¯å°‘ï¼Œå…¶ä¸ GPUã€DSP 或 FPGA 用于代表进程执行å„ç§è®¡ç®—。 本文档分为以下部分:在第一部分ä¸ï¼Œæˆ‘æ示了与使用特定于设备的内å˜åˆ†é…器相关的问题。在第二 部分ä¸ï¼Œæˆ‘æ示了许多平å°å›ºæœ‰çš„硬件é™åˆ¶ã€‚第三部分概述了 HMM 设计。第四部分解释了 CPU 页 表镜åƒçš„工作原ç†ä»¥åŠ HMM 在这ç§æƒ…况下的目的。第五部分处ç†å†…æ ¸ä¸å¦‚何表示设备内å˜ã€‚最åŽï¼Œ 最åŽä¸€èŠ‚介ç»äº†ä¸€ä¸ªæ–°çš„è¿ç§»åŠ©æ‰‹ï¼Œå®ƒå…许利用设备 DMA 引擎。 .. contents:: :local: 使用特定于设备的内å˜åˆ†é…器的问题 ================================ 具有大é‡æ¿è½½å†…å˜ï¼ˆå‡ GB)的设备(如 GPU)历æ¥é€šè¿‡ä¸“用驱动程åºç‰¹å®š API 管ç†å…¶å†…å˜ã€‚这会 é€ æˆè®¾å¤‡é©±åŠ¨ç¨‹åºåˆ†é…和管ç†çš„内å˜ä¸Žå¸¸è§„应用程åºå†…å˜ï¼ˆç§æœ‰åŒ¿åã€å…±äº«å†…å˜æˆ–常规文件支æŒå†…å˜ï¼‰ 之间的隔æ–。从这里开始,我将把这个方é¢ç§°ä¸ºåˆ†å‰²çš„地å€ç©ºé—´ã€‚我使用共享地å€ç©ºé—´æ¥æŒ‡ä»£ç›¸å的情况: å³ï¼Œè®¾å¤‡å¯ä»¥é€æ˜Žåœ°ä½¿ç”¨ä»»ä½•åº”用程åºå†…å˜åŒºåŸŸã€‚ 分割的地å€ç©ºé—´çš„å‘ç”Ÿæ˜¯å› ä¸ºè®¾å¤‡åªèƒ½è®¿é—®é€šè¿‡è®¾å¤‡ç‰¹å®š API 分é…的内å˜ã€‚è¿™æ„味ç€ä»Žè®¾å¤‡çš„è§’åº¦æ¥ çœ‹ï¼Œç¨‹åºä¸çš„所有内å˜å¯¹è±¡å¹¶ä¸å¹³ç‰ï¼Œè¿™ä½¿å¾—ä¾èµ–于广泛的库的大型程åºå˜å¾—å¤æ‚。 具体æ¥è¯´ï¼Œè¿™æ„味ç€æƒ³è¦åˆ©ç”¨åƒ GPU è¿™æ ·çš„è®¾å¤‡çš„ä»£ç 需è¦åœ¨é€šç”¨åˆ†é…的内å˜ï¼ˆmallocã€mmap ç§æœ‰ã€mmap å…±äº«ï¼‰å’Œé€šè¿‡è®¾å¤‡é©±åŠ¨ç¨‹åº API 分é…的内å˜ä¹‹é—´å¤åˆ¶å¯¹è±¡ï¼ˆè¿™ä»ç„¶ä»¥ mmap 结æŸï¼Œ 但是是设备文件)。 对于平é¢æ•°æ®é›†ï¼ˆæ•°ç»„ã€ç½‘æ ¼ã€å›¾åƒâ€¦â€¦ï¼‰ï¼Œè¿™å¹¶ä¸éš¾å®žçŽ°ï¼Œä½†å¯¹äºŽå¤æ‚æ•°æ®é›†ï¼ˆåˆ—表ã€æ ‘……), 很难åšåˆ°æ£ç¡®ã€‚å¤åˆ¶ä¸€ä¸ªå¤æ‚çš„æ•°æ®é›†éœ€è¦é‡æ–°æ˜ å°„å…¶æ¯ä¸ªå…ƒç´ 之间的所有指针关系。这很容易出错, 而且由于数æ®é›†å’Œåœ°å€çš„é‡å¤ï¼Œç¨‹åºæ›´éš¾è°ƒè¯•ã€‚ 分割地å€ç©ºé—´ä¹Ÿæ„味ç€åº“ä¸èƒ½é€æ˜Žåœ°ä½¿ç”¨å®ƒä»¬ä»Žæ ¸å¿ƒç¨‹åºæˆ–å¦ä¸€ä¸ªåº“ä¸èŽ·å¾—çš„æ•°æ®ï¼Œå› æ¤æ¯ä¸ªåº“å¯èƒ½ ä¸å¾—ä¸ä½¿ç”¨è®¾å¤‡ç‰¹å®šçš„内å˜åˆ†é…器æ¥é‡å¤å…¶è¾“入数æ®é›†ã€‚å¤§åž‹é¡¹ç›®ä¼šå› æ¤å—到影å“ï¼Œå¹¶å› ä¸ºå„ç§å†…å˜ æ‹·è´è€Œæµªè´¹èµ„æºã€‚ å¤åˆ¶æ¯ä¸ªåº“çš„API以接å—æ¯ä¸ªè®¾å¤‡ç‰¹å®šåˆ†é…器分é…的内å˜ä½œä¸ºè¾“入或输出,并ä¸æ˜¯ä¸€ä¸ªå¯è¡Œçš„选择。 这将导致库入å£ç‚¹çš„组åˆçˆ†ç‚¸ã€‚ 最åŽï¼Œéšç€é«˜çº§è¯è¨€ç»“构(在 C++ ä¸ï¼Œå½“然也在其他è¯è¨€ä¸ï¼‰çš„è¿›æ¥ï¼Œç¼–译器现在有å¯èƒ½åœ¨æ²¡æœ‰ç¨‹ åºå‘˜å¹²é¢„的情况下利用 GPU 和其他设备。æŸäº›ç¼–译器识别的模å¼ä»…适用于共享地å€ç©ºé—´ã€‚对所有 其他模å¼ï¼Œä½¿ç”¨å…±äº«åœ°å€ç©ºé—´ä¹Ÿæ›´åˆç†ã€‚ I/O 总线ã€è®¾å¤‡å†…å˜ç‰¹æ€§ ====================== 由于一些é™åˆ¶ï¼ŒI/O 总线削弱了共享地å€ç©ºé—´ã€‚大多数 I/O 总线åªå…许从设备到主内å˜çš„基本 内å˜è®¿é—®ï¼›ç”šè‡³ç¼“å˜ä¸€è‡´æ€§é€šå¸¸æ˜¯å¯é€‰çš„。从 CPU 访问设备内å˜ç”šè‡³æ›´åŠ 有é™ã€‚通常情况下,它 ä¸æ˜¯ç¼“å˜ä¸€è‡´çš„。 如果我们åªè€ƒè™‘ PCIE 总线,那么设备å¯ä»¥è®¿é—®ä¸»å†…å˜ï¼ˆé€šå¸¸é€šè¿‡ IOMMU)并与 CPU 缓å˜ä¸€ 致。但是,它åªå…许设备对主å˜å‚¨å™¨è¿›è¡Œä¸€ç»„有é™çš„原åæ“作。这在å¦ä¸€ä¸ªæ–¹å‘上更糟:CPU åªèƒ½è®¿é—®æœ‰é™èŒƒå›´çš„设备内å˜ï¼Œè€Œä¸èƒ½å¯¹å…¶æ‰§è¡ŒåŽŸåæ“ä½œã€‚å› æ¤ï¼Œä»Žå†…æ ¸çš„è§’åº¦æ¥çœ‹ï¼Œè®¾å¤‡å†…å˜ä¸ 能被视为与常规内å˜ç‰åŒã€‚ å¦ä¸€ä¸ªä¸¥é‡çš„å› ç´ æ˜¯å¸¦å®½æœ‰é™ï¼ˆçº¦ 32GBytes/s,PCIE 4.0 å’Œ 16 通é“)。这比最快的 GPU å†…å˜ (1 TBytes/s) æ…¢ 33 å€ã€‚最åŽä¸€ä¸ªé™åˆ¶æ˜¯å»¶è¿Ÿã€‚从设备访问主内å˜çš„延迟比设备访问自 己的内å˜æ—¶é«˜ä¸€ä¸ªæ•°é‡çº§ã€‚ 一些平å°æ£åœ¨å¼€å‘æ–°çš„ I/O 总线或对 PCIE çš„æ·»åŠ /修改以解决其ä¸ä¸€äº›é™åˆ¶ (OpenCAPIã€CCIX)。它们主è¦å…许 CPU 和设备之间的åŒå‘缓å˜ä¸€è‡´æ€§ï¼Œå¹¶å…许架构支æŒçš„所 有原åæ“作。é—憾的是,并éžæ‰€æœ‰å¹³å°éƒ½éµå¾ªè¿™ä¸€è¶‹åŠ¿ï¼Œå¹¶ä¸”一些主è¦æž¶æž„没有针对这些问题的硬 件解决方案。 å› æ¤ï¼Œä¸ºäº†ä½¿å…±äº«åœ°å€ç©ºé—´æœ‰æ„义,我们ä¸ä»…å¿…é¡»å…许设备访问任何内å˜ï¼Œè€Œä¸”还必须å…许任何内 å˜åœ¨è®¾å¤‡ä½¿ç”¨æ—¶è¿ç§»åˆ°è®¾å¤‡å†…å˜ï¼ˆåœ¨è¿ç§»æ—¶é˜»æ¢ CPU 访问)。 共享地å€ç©ºé—´å’Œè¿ç§» ================== HMM 打算æ供两个主è¦åŠŸèƒ½ã€‚第一个是通过å¤åˆ¶cpu页表到设备页表ä¸æ¥å…±äº«åœ°å€ç©ºé—´ï¼Œå› æ¤å¯¹ 于进程地å€ç©ºé—´ä¸çš„任何有效主内å˜åœ°å€ï¼Œç›¸åŒçš„地å€æŒ‡å‘相åŒçš„物ç†å†…å˜ã€‚ 为了实现这一点,HMM æ供了一组帮助程åºæ¥å¡«å……设备页表,åŒæ—¶è·Ÿè¸ª CPU 页表更新。设备页表 æ›´æ–°ä¸åƒ CPU 页表更新那么容易。è¦æ›´æ–°è®¾å¤‡é¡µè¡¨ï¼Œæ‚¨å¿…须分é…一个缓冲区(或使用预先分é…çš„ ç¼“å†²åŒºæ± ï¼‰å¹¶åœ¨å…¶ä¸å†™å…¥ GPU 特定命令以执行更新(å–æ¶ˆæ˜ å°„ã€ç¼“å˜å¤±æ•ˆå’Œåˆ·æ–°ç‰ï¼‰ã€‚è¿™ä¸èƒ½é€š 过所有设备的通用代ç æ¥å®Œæˆã€‚å› æ¤ï¼Œä¸ºä»€ä¹ˆHMMæ供了帮助器,在把硬件的具体细节留给设备驱 动程åºçš„åŒæ—¶ï¼ŒæŠŠä¸€åˆ‡å¯ä»¥è€ƒè™‘çš„å› ç´ éƒ½è€ƒè™‘è¿›åŽ»äº†ã€‚ HMM æ供的第二ç§æœºåˆ¶æ˜¯ä¸€ç§æ–°çš„ ZONE_DEVICE 内å˜ï¼Œå®ƒå…许为设备内å˜çš„æ¯ä¸ªé¡µé¢åˆ†é…一个 struct page。这些页é¢å¾ˆç‰¹æ®Šï¼Œå› 为 CPU æ— æ³•æ˜ å°„å®ƒä»¬ã€‚ç„¶è€Œï¼Œå®ƒä»¬å…许使用现有的è¿ç§»æœº 制将主内å˜è¿ç§»åˆ°è®¾å¤‡å†…å˜ï¼Œä»Ž CPU 的角度æ¥çœ‹ï¼Œä¸€åˆ‡çœ‹èµ·æ¥éƒ½åƒæ˜¯æ¢å‡ºåˆ°ç£ç›˜çš„页é¢ã€‚使用 struct pageå¯ä»¥ä¸ŽçŽ°æœ‰çš„ mm 机制进行最简å•ã€æœ€å¹²å‡€çš„集æˆã€‚å†æ¬¡ï¼ŒHMM ä»…æ供帮助程åºï¼Œ 首先为设备内å˜çƒæ’拔新的 ZONE_DEVICE 内å˜ï¼Œç„¶åŽæ‰§è¡Œè¿ç§»ã€‚è¿ç§»å†…容和时间的ç–略决定留 给设备驱动程åºã€‚ 请注æ„,任何 CPU 对设备页é¢çš„访问都会触å‘缺页异常并è¿ç§»å›žä¸»å†…å˜ã€‚例如,当支æŒç»™å®šCPU åœ°å€ A 的页é¢ä»Žä¸»å†…å˜é¡µé¢è¿ç§»åˆ°è®¾å¤‡é¡µé¢æ—¶ï¼Œå¯¹åœ°å€ A 的任何 CPU 访问都会触å‘缺页异常 并å¯åŠ¨å‘主内å˜çš„è¿ç§»ã€‚ å‡å€Ÿè¿™ä¸¤ä¸ªç‰¹æ€§ï¼ŒHMM ä¸ä»…å…许设备镜åƒè¿›ç¨‹åœ°å€ç©ºé—´å¹¶ä¿æŒ CPU 和设备页表åŒæ¥ï¼Œè€Œä¸”还通 过è¿ç§»è®¾å¤‡æ£åœ¨ä½¿ç”¨çš„æ•°æ®é›†éƒ¨åˆ†æ¥åˆ©ç”¨è®¾å¤‡å†…å˜ã€‚ 地å€ç©ºé—´é•œåƒå®žçŽ°å’Œ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() 回调期间,设备驱动程åºå¿…须对范围执行更新æ“ä½œï¼ˆå°†èŒƒå›´æ ‡è®°ä¸ºåª è¯»ï¼Œæˆ–å®Œå…¨å–æ¶ˆæ˜ å°„ç‰ï¼‰ã€‚设备必须在驱动程åºå›žè°ƒè¿”回之å‰å®Œæˆæ›´æ–°ã€‚ 当设备驱动程åºæƒ³è¦å¡«å……一个虚拟地å€èŒƒå›´æ—¶ï¼Œå®ƒå¯ä»¥ä½¿ç”¨:: 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 页表更新å‘生任何竞争。 利用 default_flags å’Œ pfn_flags_mask ==================================== hmm_range 结构有 2 个å—段,default_flags å’Œ pfn_flags_mask,它们指定整个范围 的故障或快照ç–略,而ä¸å¿…为 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 将在至少读å–(å³æœ‰æ•ˆï¼‰çš„所有页é¢ä¸å¼‚å¸¸ï¼Œå¹¶ä¸”å¯¹äºŽåœ°å€ == range->start + (index_of_write << PAGE_SHIFT) 它将异常写入æƒé™ï¼Œå³ï¼Œå¦‚æžœ CPU pte 没有设置写æƒé™ï¼Œé‚£ä¹ˆHMM将调用handle_mm_fault()。 hmm_range_fault 完æˆåŽï¼Œæ ‡å¿—ä½è¢«è®¾ç½®ä¸ºé¡µè¡¨çš„当å‰çŠ¶æ€ï¼Œå³ HMM_PFN_VALID | 如果页 é¢å¯å†™ï¼Œå°†è®¾ç½® HMM_PFN_WRITE。 ä»Žæ ¸å¿ƒå†…æ ¸çš„è§’åº¦è¡¨ç¤ºå’Œç®¡ç†è®¾å¤‡å†…å˜ ================================== å°è¯•äº†å‡ ç§ä¸åŒçš„设计æ¥æ”¯æŒè®¾å¤‡å†…å˜ã€‚第一个使用特定于设备的数æ®ç»“æž„æ¥ä¿å˜æœ‰å…³è¿ç§»å†…å˜ çš„ä¿¡æ¯ï¼ŒHMM 将自身挂接到 mm 代ç çš„å„个ä½ç½®ï¼Œä»¥å¤„ç†å¯¹è®¾å¤‡å†…å˜æ”¯æŒçš„地å€çš„任何访问。 事实è¯æ˜Žï¼Œè¿™æœ€ç»ˆå¤åˆ¶äº† struct page 的大部分å—段,并且还需è¦æ›´æ–°è®¸å¤šå†…æ ¸ä»£ç è·¯å¾„æ‰ èƒ½ç†è§£è¿™ç§æ–°çš„内å˜ç±»åž‹ã€‚ å¤§å¤šæ•°å†…æ ¸ä»£ç 路径从ä¸å°è¯•è®¿é—®é¡µé¢åŽé¢çš„内å˜ï¼Œè€Œåªå…³å¿ƒstruct page的内容。æ£å› 为如æ¤ï¼Œ HMM 切æ¢åˆ°ç›´æŽ¥ä½¿ç”¨ struct page 用于设备内å˜ï¼Œè¿™ä½¿å¾—å¤§å¤šæ•°å†…æ ¸ä»£ç 路径ä¸çŸ¥é“差异。 我们åªéœ€è¦ç¡®ä¿æ²¡æœ‰äººè¯•å›¾ä»Ž CPU ç«¯æ˜ å°„è¿™äº›é¡µé¢ã€‚ ç§»å…¥å’Œç§»å‡ºè®¾å¤‡å†…å˜ ================== 由于 CPU æ— æ³•ç›´æŽ¥è®¿é—®è®¾å¤‡å†…å˜ï¼Œå› æ¤è®¾å¤‡é©±åŠ¨ç¨‹åºå¿…须使用硬件 DMA æˆ–è®¾å¤‡ç‰¹å®šçš„åŠ è½½/å˜ å‚¨æŒ‡ä»¤æ¥è¿ç§»æ•°æ®ã€‚migrate_vma_setup()ã€migrate_vma_pages() å’Œ migrate_vma_finalize() 函数旨在使驱动程åºæ›´æ˜“于编写并集ä¸è·¨é©±åŠ¨ç¨‹åºçš„通用代ç 。 在将页é¢è¿ç§»åˆ°è®¾å¤‡ç§æœ‰å†…å˜ä¹‹å‰ï¼Œéœ€è¦åˆ›å»ºç‰¹æ®Šçš„设备ç§æœ‰ ``struct page`` 。这些将用 作特殊的“交æ¢â€é¡µè¡¨æ¡ç›®ï¼Œä»¥ä¾¿ 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)); 还有devm_request_free_mem_region(), devm_memremap_pages(), devm_memunmap_pages() å’Œ devm_release_mem_region() 当资æºå¯ä»¥ç»‘定到 ``struct device``. 整体è¿ç§»æ¥éª¤ç±»ä¼¼äºŽåœ¨ç³»ç»Ÿå†…å˜ä¸è¿ç§» NUMA 页é¢(see :ref:`Page migration <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()`` 调用æ¥é历设备周围的页表,使 其他设备的MMUæ— æ•ˆï¼Œä»¥ä¾¿åœ¨ ``args->src`` 数组ä¸å¡«å†™è¦è¿ç§»çš„PFN。 ``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æ¡ç›®å°†è¢« ``lock_page()``é”定,与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)); 现在驱动程åºçŸ¥é“这个页é¢æ£åœ¨è¢«è¿ç§»ï¼Œå®ƒå¯ä»¥ä½¿è®¾å¤‡ç§æœ‰ MMU æ˜ å°„æ— æ•ˆå¹¶å°†è®¾å¤‡ç§æœ‰ 内å˜å¤åˆ¶åˆ°ç³»ç»Ÿå†…å˜æˆ–å¦ä¸€ä¸ªè®¾å¤‡ç§æœ‰é¡µé¢ã€‚ç”±äºŽæ ¸å¿ƒ Linux å†…æ ¸ä¼šå¤„ç† CPU 页表失 æ•ˆï¼Œå› æ¤è®¾å¤‡é©±åŠ¨ç¨‹åºåªéœ€ä½¿å…¶è‡ªå·±çš„ MMU æ˜ å°„å¤±æ•ˆã€‚ 驱动程åºå¯ä»¥ä½¿ç”¨ ``migrate_pfn_to_page(src[i])`` æ¥èŽ·å–æºè®¾å¤‡çš„ ``struct page`` é¢ï¼Œå¹¶å°†æºé¡µé¢å¤åˆ¶åˆ°ç›®æ ‡è®¾å¤‡ä¸Šï¼Œå¦‚果指针为 ``NULL`` ï¼Œæ„ å‘³ç€æºé¡µé¢æ²¡æœ‰è¢«å¡«å……到系统内å˜ä¸ï¼Œåˆ™æ¸…é™¤ç›®æ ‡è®¾å¤‡çš„ç§æœ‰å†…å˜ã€‚ 4. ``migrate_vma_pages()`` 这一æ¥æ˜¯å®žé™…“æ交â€è¿ç§»çš„地方。 如果æºé¡µæ˜¯ ``pte_none()`` 或 ``is_zero_pfn()`` 页,这时新分é…çš„é¡µä¼šè¢«æ’ å…¥åˆ°CPU的页表ä¸ã€‚如果一个CPU线程在åŒä¸€é¡µé¢ä¸Šå‘生异常,这å¯èƒ½ä¼šå¤±è´¥ã€‚然而,页 表被é”定,åªæœ‰ä¸€ä¸ªæ–°é¡µä¼šè¢«æ’入。如果它失去了竞争,设备驱动将看到 ``MIGRATE_PFN_MIGRATE`` ä½è¢«æ¸…除。 如果æºé¡µè¢«é”定ã€éš”离ç‰ï¼Œæº ``struct page`` ä¿¡æ¯çŽ°åœ¨è¢«å¤åˆ¶åˆ°ç›®æ ‡ ``struct page`` ,最终完æˆCPU端的è¿ç§»ã€‚ 5. 设备驱动为ä»åœ¨è¿ç§»çš„页é¢æ›´æ–°è®¾å¤‡MMU页表,回滚未è¿ç§»çš„页é¢ã€‚ 如果 ``src`` æ¡ç›®ä»ç„¶æœ‰ ``MIGRATE_PFN_MIGRATE`` ä½è¢«è®¾ç½®ï¼Œè®¾å¤‡é©±åŠ¨å¯ä»¥ 更新设备MMU,如果 ``MIGRATE_PFN_WRITE`` ä½è¢«è®¾ç½®ï¼Œåˆ™è®¾ç½®å†™å¯ç”¨ä½ã€‚ 6. ``migrate_vma_finalize()`` 这一æ¥ç”¨æ–°é¡µçš„页表项替æ¢ç‰¹æ®Šçš„è¿ç§»é¡µè¡¨é¡¹ï¼Œå¹¶é‡Šæ”¾å¯¹æºå’Œç›®çš„ ``struct page`` 的引用。 7. ``mmap_read_unlock()`` 现在å¯ä»¥é‡Šæ”¾é”了。 独å 访问å˜å‚¨å™¨ ============== 一些设备具有诸如原åPTEä½çš„功能,å¯ä»¥ç”¨æ¥å®žçŽ°å¯¹ç³»ç»Ÿå†…å˜çš„原å访问。为了支æŒå¯¹ä¸€ 个共享的虚拟内å˜é¡µçš„原åæ“ä½œï¼Œè¿™æ ·çš„è®¾å¤‡éœ€è¦å¯¹è¯¥é¡µçš„访问是排他的,而ä¸æ˜¯æ¥è‡ªCPU 的任何用户空间访问。 ``make_device_exclusive_range()`` 函数å¯ä»¥ç”¨æ¥ä½¿ä¸€ 个内å˜èŒƒå›´ä¸èƒ½ä»Žç”¨æˆ·ç©ºé—´è®¿é—®ã€‚ 这将用特殊的交æ¢æ¡ç›®æ›¿æ¢ç»™å®šèŒƒå›´å†…çš„æ‰€æœ‰é¡µçš„æ˜ å°„ã€‚ä»»ä½•è¯•å›¾è®¿é—®äº¤æ¢æ¡ç›®çš„行为都会 å¯¼è‡´ä¸€ä¸ªå¼‚å¸¸ï¼Œè¯¥å¼‚å¸¸ä¼šé€šè¿‡ç”¨åŽŸå§‹æ˜ å°„æ›¿æ¢è¯¥æ¡ç›®è€Œå¾—到æ¢å¤ã€‚驱动程åºä¼šè¢«é€šçŸ¥æ˜ å°„å·² ç»è¢«MMU通知器改å˜ï¼Œä¹‹åŽå®ƒå°†ä¸å†æœ‰å¯¹è¯¥é¡µçš„独å 访问。独å 访问被ä¿è¯æŒç»åˆ°é©±åŠ¨ç¨‹åº 放弃页é¢é”和页é¢å¼•ç”¨ä¸ºæ¢ï¼Œè¿™æ—¶é¡µé¢ä¸Šçš„任何CPU异常都å¯ä»¥æŒ‰æ‰€è¿°è¿›è¡Œã€‚ å†…å˜ cgroup (memcg) å’Œ rss 统计 =============================== ç›®å‰ï¼Œè®¾å¤‡å†…å˜è¢«è§†ä¸º rss 计数器ä¸çš„任何常规页é¢ï¼ˆå¦‚果设备页é¢ç”¨äºŽåŒ¿å,则为匿å, 如果设备页é¢ç”¨äºŽæ–‡ä»¶æ”¯æŒé¡µé¢ï¼Œåˆ™ä¸ºæ–‡ä»¶ï¼Œå¦‚果设备页é¢ç”¨äºŽå…±äº«å†…å˜ï¼Œåˆ™ä¸º shmem)。 这是为了ä¿æŒçŽ°æœ‰åº”用程åºçš„æ•…æ„选择,这些应用程åºå¯èƒ½åœ¨ä¸çŸ¥æƒ…的情况下开始使用设备 内å˜ï¼Œè¿è¡Œä¸å—å½±å“。 一个缺点是 OOM æ€æ‰‹å¯èƒ½ä¼šæ€æ»ä½¿ç”¨å¤§é‡è®¾å¤‡å†…å˜è€Œä¸æ˜¯å¤§é‡å¸¸è§„系统内å˜çš„应用程åºï¼Œ å› æ¤ä¸ä¼šé‡Šæ”¾å¤ªå¤šç³»ç»Ÿå†…å˜ã€‚在决定以ä¸åŒæ–¹å¼è®¡ç®—设备内å˜ä¹‹å‰ï¼Œæˆ‘们希望收集更多关 于应用程åºå’Œç³»ç»Ÿåœ¨å˜åœ¨è®¾å¤‡å†…å˜çš„情况下在内å˜åŽ‹åŠ›ä¸‹å¦‚何å应的实际ç»éªŒã€‚ å¯¹å†…å˜ cgroup åšå‡ºäº†ç›¸åŒçš„决定。设备内å˜é¡µé¢æ ¹æ®ç›¸åŒçš„å†…å˜ cgroup 计算,常规 页é¢å°†è¢«è®¡ç®—在内。这确实简化了进出设备内å˜çš„è¿ç§»ã€‚这也æ„味ç€ä»Žè®¾å¤‡å†…å˜è¿ç§»å›žå¸¸è§„ 内å˜ä¸ä¼šå¤±è´¥ï¼Œå› ä¸ºå®ƒä¼šè¶…è¿‡å†…å˜ cgroup é™åˆ¶ã€‚一旦我们对设备内å˜çš„使用方å¼åŠå…¶å¯¹ 内å˜èµ„æºæŽ§åˆ¶çš„å½±å“有了更多的了解,我们å¯èƒ½ä¼šåœ¨åŽé¢é‡æ–°è€ƒè™‘这个选择。 请注æ„,设备内å˜æ°¸è¿œä¸èƒ½ç”±è®¾å¤‡é©±åŠ¨ç¨‹åºæˆ–通过 GUP å›ºå®šï¼Œå› æ¤æ¤ç±»å†…å˜åœ¨è¿›ç¨‹é€€å‡ºæ—¶ 总是被释放的。或者在共享内å˜æˆ–文件支æŒå†…å˜çš„æƒ…å†µä¸‹ï¼Œå½“åˆ é™¤æœ€åŽä¸€ä¸ªå¼•ç”¨æ—¶ã€‚