当前位置: 首页 > news >正文

从异构内存管理角度看 Linux MM 锁机制的进化史 —— 一把大锁到异构内存迁移

本文系统地梳理 Linux MM 子系统中与"内存迁移"相关的锁与同步机制的演进脉络:每把锁因何而生、解决了什么瓶颈、又带来什么新问题

贯穿全文的一条主线:锁粒度不断从"一把大锁保护整块"细化为"多把小锁各管一小块";同时不断引入新机制,去同步 CPU 页表之外的观察者(设备/虚拟机)和迁移中的访问者。


目录

  • 阶段一:一把大锁的时代
  • 阶段二:锁粒度细化
  • 阶段三:同步 CPU 之外的观察者
  • 阶段四:异构内存与设备迁移
  • 阶段五:缺页路径再提速
  • 一张演进全景图
  • 对"迁移"这件事的累积影响

1. 阶段一:一把大锁的时代

1.1mmap_sem—— 地址空间的全局读写锁

最早,一个进程地址空间里几乎一切都由mm->mmap_sem(读写信号量)保护:VMA 树的查找与修改;缺页处理handle_mm_fault();页表遍历、get_user_pages();以及mmap/munmap/mprotect/mremap/brk等系统调用。

优点:模型极简——“改地址空间相关的东西,先拿这把锁”。

瓶颈:它是进程级的单锁。多线程程序里,一个线程缺页(读锁)会与另一个线程mmap(写锁)互斥;高并发缺页彼此也在读锁的 cache line 上颠簸。对数据库、JVM、大型 C++ 服务这类"多线程 + 大量按需分页"的负载,mmap_sem长期是头号扩展性瓶颈。

演进动作:2020 年前后社区把mmap_sem更名为mmap_lock,并引入一整套封装 API(mmap_read_lock()/mmap_write_lock()/...)。更名本身不改变语义,但把"所有加锁点"收敛到统一入口,为后续"把职责从这把大锁里拆出去"铺路。

1.2page_table_lock—— 每 mm 一把的页表锁

页表项的修改最初由mm->page_table_lock(每个 mm 一把自旋锁)串行化。

瓶颈:多核同时修改不同页表区域时,仍要争抢同一把锁。对大地址空间、多核并发缺页,这把锁的争抢很快显现。

阶段一的共同问题:锁的作用域太大。保护范围与并发实体(线程/CPU)不匹配,天然限制扩展性。阶段二就是针对性地"拆"。


2. 阶段二:锁粒度细化

2.1 split PTL —— 把页表锁拆到"每个页表页一把"

动机:page_table_lock太粗。

做法:引入split page table lock——锁不再是每 mm 一把,而是挂在每个页表页(pmd/pte 页)的struct page,每页一把。取锁接口:PTE 级用pte_offset_map_lock(mm, pmd, addr, &ptl);PMD 级(THP)用pmd_lock(mm, pmd)

收益:修改不同页表页的 PTE 之间不再互斥,页表操作真正并行化。这就是基础篇里反复出现的PTL。后来还配合RCU 释放页表页,让无锁的页表读者(如 GUP-fast、lockless page fault 的部分阶段)能安全地遍历。

关键遗产:“改一个 PTE 必须拿覆盖它的那把 PTL”成为全内核的硬约定。munmap 的zap_pte_range、mprotect 的change_pte_range、mremap 的move_ptes、迁移的 collect/finalize、rmap 的try_to_migrate_one——全都遵守它。这正是"某条路径即使不持 mmap_lock,只要在 PTL 下改 PTE 就与其他改 PTE 者串行化"的根基。

2.2 object-based rmap —— 从物理页反查所有映射

动机:早期没有高效的"物理页 → 谁映射了它"的反查。换出或迁移一个被多进程共享的页时,找齐所有 PTE 代价高昂,甚至只能扫全表。

做法:引入基于对象的reverse mapping(rmap):匿名页通过anon_vma/anon_vma_chain把一个页关联到映射它的所有 VMA;文件页通过address_space的区间树。

收益:rmap_walk()可以枚举一个 folio 的所有映射并逐一处理。这是后来try_to_migrate()能"只拿着物理页、不知道任何虚拟地址"就把该页从所有地址空间里解除映射的前提——也是migrate_device_*(按 PFN)整条路线成立的基础。

2.3 page lock → folio lock —— 冻结单个物理页

动机:需要一个"对单页操作的序列化点",让迁移/回收/写回期间别人别插手。

做法:用struct pagePG_locked位(lock_page/unlock_page)。近年folio 化改造后统一为folio lock(folio_lock/folio_trylock/folio_unlock),天然覆盖复合页(THP)。

收益:迁移用它来"冻结" folio——从装 migration entry 到 finalize 全程持锁;等在 migration entry 上的访问者,本质就是在等这把 folio lock(基础篇 §5.1 已核实等待对象正是PG_locked)。


3. 阶段三:同步 CPU 之外的观察者

前两阶段都在解决"CPU 侧页表/物理页的并发"。但现代系统里,对同一物理页持有映射的不只是 CPU

3.1 mmu_notifier —— 让 GPU/IOMMU/KVM 与 CPU 页表同步

动机:KVM 的影子页表、GPU 的设备页表、IOMMU 的映射,都缓存了"虚拟/设备地址 → 物理页"的翻译。当 CPU 改动或迁移某页时,这些外部翻译必须先失效,否则设备会访问到已被搬走/释放的旧页,造成数据损坏或安全问题。

做法:2008 年前后引入mmu_notifier,有两种形态。经典范围通知:mmu_notifier_invalidate_range_start()/end()成对包住页表修改,约定失效通知必须发生在改 PTE 之前(start)与之后(end)。interval notifier(较新):mmu_interval_notifier,驱动在一段 VA 上注册,该段内任何失效都会回调其.invalidate,GPU SVM(如 drm_gpusvm)用的就是它。

事件类型enum mmu_notifier_event:同一套回调用事件类型区分语义,常见如下表。

事件大意是否带pgmap_owner
MMU_NOTIFY_UNMAP地址被解除映射
MMU_NOTIFY_CLEAR通用清除/失效
MMU_NOTIFY_MIGRATE按地址范围迁移

MMU_NOTIFY_MIGRATEowner的意义:让按 owner 过滤的驱动跳过对"自己拥有、且本次不会真正迁移的 device-private 映射"的失效,避免自我失效。是否利用这个过滤完全取决于驱动回调实现——这一点会直接影响后续具体路径分析的结论。

3.2 migration entry —— 对 CPU 访问者的"路障"

动机:迁移一个页需要时间窗口。窗口内如果有 CPU 访问该 VA,既不能丢访问,也不能让它读到"搬到一半"的页。

做法:把 PTE 临时换成一种特殊swap entry(migration entry)。窗口内任何访问该 VA 会缺页进do_swap_page(),识别出 migration entry 后migration_entry_wait()阻塞在目标 folio 的PG_locked,直到迁移完成解锁再重试。

migration entry(路障)+ folio lock(等待对象)+ PTL(原子替换/移除)三者合起来,构成迁移窗口内对 CPU 侧访问的完整保护。


4. 阶段四:异构内存与设备迁移

4.1 HMM /ZONE_DEVICE/ device-private page

动机:GPU/加速器有自己的高带宽显存。希望让这块显存能像普通内存一样参与进程地址空间(SVM/统一地址空间),按需在 CPU RAM 与设备显存之间迁移。

做法:2017 年前后的HMM(Heterogeneous Memory Management)引入ZONE_DEVICE页与device-private页:设备显存以ZONE_DEVICEstruct page表示;迁到设备后,CPU 侧 PTE 变成device-private swap entry(非 present,但仍在 rmap 中、仍计入 mapcount);CPU 访问该 entry 会触发->migrate_to_ram回调,由驱动把页迁回 RAM。

4.2 两个迁移入口:按地址 vs 按 PFN

在上述积木之上,内核提供了两套面向设备的迁移 API,分别服务不同场景。

(a)migrate_vma_*—— 按虚拟地址范围迁移。场景:CPU 缺页触发(migrate_to_ram)、或用户/驱动请求把一段 VA 迁到显存(migrate_to_devmem)。特征:有 VMA、持 mmap_lock(至少读),走walk_page_range收集 PTE,并发MMU_NOTIFY_MIGRATE(带pgmap_owner)。局限:单 VMA / 单 mm、按地址范围。

(b)migrate_device_*—— 按物理 PFN 迁移。场景:驱动主动腾显存 / 设备 unbind / shrinker——驱动只知道要搬哪些物理 device PFN,不一定持有(也不想遍历)它们的虚拟映射,且这些页可能被多个进程共享。特征:不碰 VMA、不持 mmap_lock,靠rmap(try_to_migrate)找到并解除该页在所有地址空间中的映射,其 PTE 失效通知走MMU_NOTIFY_CLEAR(经try_to_migrate_one,不带 owner)。依赖:正是阶段二的rmap+split PTL+folio lock让"只拿物理页也能安全迁移"成为可能。

这两个入口的差异(是否要 VMA/mmap_lock、如何定位 PTE、发什么 mmu 事件),正是 drm_pagemap 里fault 路径 vs eviction 路径分歧的根源。


5. 阶段五:缺页路径再提速

5.1 per-VMA lock(SPF,Speculative Page Fault)

动机:即便有了 split PTL,缺页仍要先拿进程级mmap_lock读锁,高并发缺页依旧在这把锁上排队。典型场景是多线程程序:线程 A 在缺页(需要mmap_lock读锁),线程 B 在mmap/brk(需要写锁),二者互斥;即使 A、B 操作的是完全不相干的两段 VMA,也被这把全局锁强行串行化。split PTL 解决的是"改 PTE"的并发,却没解决"进入缺页处理前那道门槛"的并发。

核心思路:既然一次缺页通常只关心一个 VMA,那就把"读锁"下沉到VMA 粒度——只锁住命中的那个 VMA,而不是锁住整棵 VMA 树。

做法:2023 年前后合入per-VMA lock。关键机制有三层。

  1. RCU 查找 VMA:缺页快路径用lock_vma_under_rcu(),在RCU 读侧临界区里(不持mmap_lock)从 maple tree 查到覆盖故障地址的 VMA。RCU 保证遍历期间 VMA 结构体不会被释放。
  2. seqcount 校验 + VMA 读锁:找到候选 VMA 后,用vma_start_read()尝试拿该 VMA 的读锁。它基于seqcount:写者(改这个 VMA 的人)会通过vma_start_write()递增序列号;读者拿锁时比对序列号,若发现有写者正在改这个 VMA,就放弃快路径。成功拿到后,置标志FAULT_FLAG_VMA_LOCK进入handle_mm_fault(),全程不碰mmap_lock
  3. 失败即回退:任何一步不满足(VMA 没找到、正被写者修改、或落进尚未适配 per-VMA lock 的慢路径),就vma_end_read()释放 VMA 读锁,返回VM_FAULT_RETRY,由上层重新以传统mmap_read_lock()走一遍。回退保证了正确性:快路径只做"能安全做"的那部分,拿不准就退回老路。

直觉:mmap_lock是"整栋楼的大门钥匙",per-VMA lock 是"每个房间自己的钥匙"。改 A 房间不再挡住进 B 房间的人;但凡遇到需要动整栋楼结构(跨 VMA、合并/split)的操作,还是得回去拿大门钥匙。

边界:哪些缺页不能在 per-VMA lock 下完成。per-VMA lock 是渐进式铺开的——先覆盖最常见、最独立的匿名页/文件页缺页,尚未适配的路径一律主动退回device-private 的migrate_to_ram正是尚未适配的一员:

}elseif(softleaf_is_device_private(entry)){if(vmf->flags&FAULT_FLAG_VMA_LOCK){/* migrate_to_ram is not yet ready to operate under VMA lock. */vma_end_read(vma);ret=VM_FAULT_RETRY;/* 回退到 mmap_read_lock 重试 */gotoout;}...}

为什么migrate_to_ram目前坚持要mmap_lock:设备迁移回调(如migrate_vma_*)会跨越单个 PTE 的边界——它要walk_page_range遍历一段地址、收集多个 PTE、发MMU_NOTIFY_MIGRATE通知、可能触发 VMA 相关的分配与状态更新。这些动作依赖"整个地址空间布局在此期间稳定",而 per-VMA lock 只锁住一个VMA、且语义上更弱,尚不足以支撑。于是内核选择保守退回:一旦发现是在FAULT_FLAG_VMA_LOCK下命中 device-private entry,立即放弃快路径。

推论(对 GPU SVM 的直接影响):所以 GPU SVM 的 fault 回调真正执行时,一定在mmap_read_lock之下,而不可能在 per-VMA lock 下。这条"退回"逻辑正是"fault 路径 VMA 稳定"这一前提的制度保证——不是驱动自己去争取,而是内核在入口就替它把不安全的情形挡掉了(详见基础篇 §1.1)。

未来演进方向:社区在逐步让更多路径"VMA-lock 化"。若某天migrate_to_ram也适配了 per-VMA lock,上面这段VM_FAULT_RETRY会被移除,fault 路径的锁前提也会随之改写——这也是为什么本文强调"结论要绑定到具体内核版本"。

想深入 per-VMA lock 的底层实现(vm_refcnt/vm_lock_seq/mm_lock_seq三字段、读写锁流程、"假加锁"语义)?见专文:深入 per-VMA lock:Linux 缺页路径如何摆脱 mmap_lock。


6. 一张演进全景图

阶段五 · 缺页提速

阶段四 · 异构内存迁移

阶段二 · 粒度细化

阶段一 · 大锁时代

驱动腾显存触发

更名 mmap_lock,拆分做准备

阶段三 · 同步外部/访问者

mmu_notifier
(range / interval,事件类型 + owner)

migration entry
(阻塞 CPU 访问者于 folio lock)

mmap_sem
(整块地址空间)

page_table_lock
(每 mm 一把)

split PTL
(每页表页一把)

rmap
(物理页 → 所有映射)

page lock → folio lock

HMM / ZONE_DEVICE / device-private

migrate_vma_*
(按地址,持 mmap_lock,MMU_NOTIFY_MIGRATE)

migrate_device_*
(按 PFN,免 VMA,rmap,MMU_NOTIFY_CLEAR)

per-VMA lock (SPF)
device-private 主动退回 mmap_lock

只拿物理页
也能操作全部映射


7. 对"迁移"这件事的累积影响

把历史落到"今天一次设备内存迁移到底靠什么":

迁移要保护的东西由哪一阶段的产物负责
地址空间布局(VMA)阶段一mmap_lock/ 阶段五 per-VMA lock
单个 PTE 的原子替换阶段二split PTL
找齐一个页的所有映射阶段二rmap
冻结物理页(迁移窗口)阶段二folio lock+ refcount
挡住迁移中的 CPU 访问阶段三migration entry
同步 GPU/IOMMU 外部映射阶段三mmu_notifier
按地址 / 按 PFN 两种入口阶段四migrate_vma_/ migrate_device_**

一句话总结:

今天 drm_pagemap 的fault 路径(migrate_vma_)与eviction 路径(migrate_device_)之所以能各自安全、且分别"要/不要 mmap_lock",不是某一个设计决定的,而是上面五个阶段积木叠加的自然结果。理解了这条历史线,再看两条路径的锁差异就会觉得"本该如此"。

注:文中年份为社区大致引入时间,用作时间坐标而非精确版本号;如需落到具体 commit / 内核版本,可按各机制名逐一查证。

http://www.jsqmd.com/news/1243601/

相关文章:

  • 【Python毕业设计】基于 Python 的无人超市商品自助结算与库存管理系统 智能零售无人超市后台运维平台实现(源码+文档+远程调试,全bao定制等)
  • 2026年木工采购指南:如何挑选业内口碑好的五轴雕刻机工厂
  • Web安全入门先攻这五个漏洞,比盲目刷课管用
  • 2026年7月最新!海口万国回收服务怎么样?官方客服揭秘回收价格查询内幕 - 诚收名表回收平台
  • 2026 上海包包回收行情分析,影响回收价格的关键因素 - 讯息早知道
  • 亲身到店探访广州劳力士官方售后服务中心|完整地址及售后服务热线(2026年7月最新) - 劳力士服务中心
  • 2026年7月最新权威发布:北京欧米茄回收怎么样?官方渠道排行+客户服务实测对比 - 嘉价奢侈品回收平台
  • wechatpay-apache-httpclient vs 原生HttpClient:为什么选择这款微信支付SDK?
  • 2026高EPA高纯鱼油品牌热门推荐榜单梳理 - 起跑123
  • 从原理到实践:react-native-responsive-fontSize实现响应式字体的底层逻辑
  • UART中断与DMA寄存器级配置:从原理到实践
  • local.ai未来路线图:即将推出的5大令人期待的新功能
  • 研究生如何两周完成一篇SCI
  • wysiwyg.css与其他Markdown样式库对比:为什么它是最轻量的选择?
  • 2026成都装修公司实力测评:知名品牌哪家强?看完不踩坑 - 推荐官
  • Grok 4.6 与 Kimi K3 全面对比:2万亿参数效率战遇上2.8万亿公开权重
  • 为什么选择vlife本地化部署?企业数据安全与隐私保护的完整解决方案
  • 劳力士官方保养价格查询|维修地址及电话权威信息公告(2026年7月最新) - 劳力士官方服务中心
  • Linux系统警报:Hi, My Name is Keyboard攻击原理与防御措施
  • 亲身探访杭州浪琴官方售后服务中心|维修地址及售后热线(2026年7月最新) - 浪琴服务中心
  • wechatpay-apache-httpclient实战教程:从环境搭建到JSAPI下单完整流程
  • 流动的生产力:2026 武汉智能仓储及物料搬运展览会开启智慧物流新篇章
  • 2026年7月最新百达翡丽大连普兰店万达广场维修保养服务电话 - 百达翡丽官方售后中心
  • 2026武汉甄选靠谱犬舍|皇克莱猫犬舍教你0套路安心买宠 - 同城宠物优选基地
  • Fody开源可持续发展:为什么成为Patron是每位开发者的责任
  • 水基凝胶工艺为什么是高端散热标配?氧化铝陶瓷天花板工艺解析
  • Wine 与 Linux 内核的交互
  • 晚期风格即兴
  • 深入理解wysiwyg.css实现原理:从Sass源码到压缩CSS的完整流程
  • 浪琴石家庄唯一官方售后网点地址及客户服务热线电话2026年7月最新权威声明 - 浪琴服务中心