Linux 6.6内核内存管理深度解析(六):GUP / pin_user_pages — 怎么把用户态内存“钉“给设备 DMA

发布时间:2026/10/15 2:43:05

Linux 6.6内核内存管理深度解析(六):GUP / pin_user_pages — 怎么把用户态内存“钉“给设备 DMA 〇、全景设备要 DMA 用户态内存得先把页钉住前面 #3 讲了缺页把虚拟地址映射到物理页#5 讲了 page cache。但还有一个关键问题设备GPU、网卡、NVMe要做 DMA 访问用户态内存时它拿的是物理地址、且要在一段时间内持续访问。可用户态内存是按需分配、随时可能被换出/迁移/写时复制的——如果设备 DMA 到一半页被换出去了或 COW 改了就出数据损坏。GUPGet User Pages就是解决这个把用户态的虚拟地址走页表翻成物理页并且**“钉住”pin**它保证它在 DMA 期间稳定。用户态虚拟地址进程地址空间GUP走页表 vaddr → struct page物理页被 pin 住不被换出/迁移/COWdma_map_pageIOMMU #7物理页 → 设备 IOVA设备 DMA读/写这段内存get_user_pagesFOLL_GET临时引用pin_user_pagesFOLL_PIN长期 pin一句话主线GUP 把用户态虚拟地址走页表翻译成物理页并钉住供设备 DMA有两个入口——get_user_pagesFOLL_GET临时引用、页仍可能被 COW/迁移和pin_user_pagesFOLL_PIN长期 pin、页不能被 COW/迁移pin 的核心是pin 计数独立于 ref 计数单页 folio 用refcount 1024的 bias 编码、大 folio 用_pincount精确计数让内核能检测这页被 DMA 钉住了从而跳过 COW/迁移两条路径——_fast快路径不持锁直接遍历页表慢路径__get_user_pages持 mmap_lock 还能faultin_page触发缺页。一、为什么要有 pin而不是只get_page加个引用设备 DMA 用户态内存需要两样东西① 物理页地址设备只认物理地址② 这个页在 DMA 期间稳定不被换出到 swap、不被内存回收迁移、不被写时复制改内容。如果只用get_pagepage-_refcount1只满足页不被释放满足不了稳定——内核无法区分这个 refcount 是普通页表映射还是某个设备正在 DMA 它。于是写这页时内核不知道它正被设备 DMA会按普通 COW 把它原地改写复用同一物理页改内容而设备还在读写旧页 →CPU 看到新数据、设备写到旧页数据错乱。所以内核需要单独的 pin 计数把设备 DMA 引用和普通页表引用区分开这就是FOLL_PIN。源码注释include/linux/mm_types.h:1301写得清楚FOLL_PIN indicates that a special kind of tracking (not just page-_refcount)… potentially changing the pages’ data. In contrast to FOLL_GET (whose pages are released via put_page())…二、两个入口get_user_pagesvspin_user_pages// mm/gup.c (v6.6, line 2367)longget_user_pages(unsignedlongstart,unsignedlongnr_pages,unsignedintgup_flags,structpage**pages){// is_valid_gup_args 里补 FOLL_TOUCHreturn__get_user_pages_locked(current-mm,start,nr_pages,pages,locked,gup_flags);}// mm/gup.c (v6.6, line 3370)longpin_user_pages(unsignedlongstart,unsignedlongnr_pages,unsignedintgup_flags,structpage**pages){// is_valid_gup_args 里补 FOLL_PINreturn__gup_longterm_locked(current-mm,start,nr_pages,pages,locked,gup_flags);}两者接口一模一样差别全在flag上入口flag语义释放典型场景get_user_pages*FOLL_GET临时引用refcount1页仍可能被 COW/迁移put_pageO_DIRECT 读写的瞬时访问、process_vm_readvpin_user_pages*FOLL_PIN调用者通常再传FOLL_LONGTERM长期 pin页不能被 COW/迁移/ksmunpin_user_page*RDMA、vfio、GPU 直通等长期 DMAFOLL_PIN是内部 flagmm/internal.h:960FOLL_PIN 119调用者不能自己传由pin_user_pages*内部帮你设置——这就是两个 API 分开的原因不让调用者乱设 flag用 API 名字就定死了get 还是 pin。此外pin_user_pages还会走__gup_longterm_locked隐含长期持有的语义见 §五。每个入口还有几个变体_fast不持锁的快路径、_unlocked内部自管锁、_remote操作别的进程 mm。三、核心机制pin 计数独立于 ref 计数把 pin 和普通引用区分开落到struct folio上是try_grab_foliogup.c:126干的// mm/gup.c (v6.6, line 126节选if(flagsFOLL_GET)returntry_get_folio(page,refs);// ① 普通 getrefcount refs// FOLL_PIN 分支if(is_zero_page(page))returnpage_folio(page);// ② 零页不 pin共享、永不消失foliotry_get_folio(page,refs);// ③ 先正常 refcount refs// …if(folio_test_large(folio))atomic_add(refs,folio-_pincount);// ④a 大 folio独立 pincount 字段elsefolio_ref_add(folio,refs*(GUP_PIN_COUNTING_BIAS-1));// ④b 单页 foliorefcount 按 1024 步进三种情况核心是**“pin 数怎么单独记账”**FOLL_GET①就是try_get_folio给_refcount1没有任何 pin 记账。页被谁 get 了内核不知道所以仍可能被 COW/迁移。大 folio④astruct folio里有独立的_pincount字段直接精确atomic_add同时_refcount也至少 1保证确实被引用。单页 folio④bstruct page没有多余字段放 pincount于是复用 refcount 的高位——pin 一次就把_refcount加GUP_PIN_COUNTING_BIAS1024。这样pin 数 ≈ refcount / 1024内核只要看 refcount 是否 ≥ 1024 就知道这页被 pin 了。// include/linux/mm.h (v6.6, line 1577)#defineGUP_PIN_COUNTING_BIAS(1U10)// 1024释放端gup_put_foliogup.c:189对称FOLL_PIN 大 folio 减_pincount、单页 folio 减 1024FOLL_GET 就普通folio_put_refs。为什么单页用 bias 而不是独立字段struct page已经塞得满满当当union 复用没空间给每个页再开一个 pincount 计数器。用 1024 的 bias 复用 refcount是用一点计数精度refcount 上限被 pin 数压缩 1024 倍换零额外内存的折中大 folio 本来就是多个页合成一个、folio结构宽裕才开得起独立_pincount。四、两条路径_fast无锁快路径 vs 慢路径4.1 慢路径__get_user_pages持锁 能触发缺页get_user_pages/pin_user_pages默认走慢路径__get_user_pagesgup.c:1184它持着mmap_lock逐页// mm/gup.c (v6.6, line 1184节选do{// ① 找到覆盖这个地址的 vmagup_vma_lookup 权限检查 check_vma_flagsif(!vma||startvma-vm_end){vmagup_vma_lookup(mm,start);// …retcheck_vma_flags(vma,gup_flags);}// ② 走页表找物理页pagefollow_page_mask(vma,start,foll_flags,ctx);// ③ 页没映射 → 触发缺页faultin_page→ 重试if(!page||PTR_ERR(page)-EMLINK){retfaultin_page(vma,start,foll_flags,…);// …gotoretry;}// ④ try_grab_folio 把页抓住get 或 pin// …}while(…);① vma 权限和 #3 缺页一样先查 vmagup_vma_lookup、检查访问权限check_vma_flags②follow_page_maskgup.c:809沿 PGD→P4D→PUD→PMD→PTE 逐级走页表找到目标物理页大页 vma 走hugetlb_follow_page_mask③faultin_page页还没映射没物理页就触发一次缺页把页按需分配/读进来复用 #3 的handle_mm_fault再 retry④try_grab_folio真正把页抓住§三。所以慢路径 “走页表 缺页兜底”功能最全代价是要持mmap_lock读锁并发上有限制。4.2 快路径gup_pte_range无锁直接读 pteget_user_pages_fastgup.c:3270/pin_user_pages_fastgup.c:3304不持mmap_lock直接遍历页表// mm/gup.c (v6.6, line 2555节选staticintgup_pte_range(pmd_tpmd,pmd_t*pmdp,unsignedlongaddr,unsignedlongend,unsignedintflags,…){ptempteppte_offset_map(pmd,addr);do{pte_tpteptep_get_lockless(ptep);// 无锁原子读 pte// …if(pte_protnone(pte))gotopte_unmap;// 复杂情况 → 退回慢路径if(!pte_access_permitted(pte,flagsFOLL_WRITE))gotopte_unmap;// …foliotry_grab_folio(page,1,flags);// 读完之后 recheck pte 没被并发改过改过就放弃if(pmd_val(pmd)!pmd_val(*pmdp)||pte_val(pte)!pte_val(ptep_get(ptep)))gotopte_unmap;// …}while(…);}快路径的 trick无锁读 pteptep_get_lockless原子读不碰锁安全靠关中断 RCU IPI页表页被释放前TLB 刷新的 IPI 会阻塞在关中断的 CPU 上加上 RCU 延迟释放页表页保证遍历期间页表不被脚下释放见get_user_pages_fast上的大段注释gup.c:2409recheck读完页后再比对 pmd/pte 没被并发改动改了说明有 race可能被 split/换出就放弃、退回慢路径只做已映射的页遇到pte_protnone、无权限、THP split、special pte 等复杂情况一律goto pte_unmap退回慢路径——快路径不触发缺页页必须已经映射好了。所以快路径 “页已映射的前提下无锁快速翻页表”适合高频、页基本都在的场合一旦遇到它搞不定的就 fallback 到慢路径。五、FOLL_LONGTERM长期 pin 要先把 movable 区的页迁走pin_user_pages走的是__gup_longterm_lockedgup.c:2187。注意pin_user_pages只自动帮你设FOLL_PINFOLL_LONGTERM是调用者额外传的RDMA/vfio 等长期 DMA 都会传。只有设了FOLL_LONGTERM才会多出下面这步迁移// mm/gup.c (v6.6, line 2187节选staticlong__gup_longterm_locked(…){if(!(gup_flagsFOLL_LONGTERM))return__get_user_pages_locked(…);// 非 longterm跟普通 get 一样flagsmemalloc_pin_save();do{nr_pinned_pages__get_user_pages_locked(…);// …/* FOLL_LONGTERM implies FOLL_PIN */rccheck_and_migrate_movable_pages(nr_pinned_pages,pages);}while(rc-EAGAIN);memalloc_pin_restore(flags);returnrc?rc:nr_pinned_pages;}check_and_migrate_movable_pagesgup.c:2161会检查 pin 住的页有没有落在movable zone比如 CMA 区有就把它们迁移到 non-movable zone。为什么要迁movable zoneCMA 尤其的设计前提是这些页可以随便迁移腾出来给连续分配用见后续 CMA 篇。而长期 pin 的页不能被迁移——它留在 movable 区就会卡死内存规整/CMA 分配。所以长期 pin 前先把它们迁走别占着可迁移的地盘。try_grab_folio里对应的检查是folio_is_longterm_pinnablegup.c:158fast 路径遇到不可长期 pin 的页直接失败退回慢路径交给慢路径去迁移。六、与 DMA / IOMMU 的关系GUP 是零拷贝的前半段用户态零拷贝 DMA 的完整链路是两段拼接GUP本篇用户态虚拟地址 → 物理页并把页 pin 住DMA APIIOMMU #7物理页 → 设备能访问的IOVAdma_map_page/dma_map_single建 IOMMU 页表。驱动先pin_user_pages拿到物理页数组struct page **再对每个页dma_map_page得到设备地址然后告诉设备去这个 IOVA 读/写。设备 DMA 完反向dma_unmap_pageunpin_user_page释放。GUP 管CPU 视角的页稳不稳定DMA API 管设备视角的地址翻不翻译——两者缺一不可这就是 GPU 显存拷贝cuMem、RDMA、NVMe 直通这些用户态零拷贝的共同底座。七、为什么这样设计Why 层7.1 为什么需要单独的 pin 计数而不是复用 refcount因为**“被普通引用和被设备 DMA 钉住是两种不同的约束**普通引用只要求别释放”DMA pin 还要求别换出、别迁移、别 COW 改内容。复用 refcount 会让内核分不清这两种情况——它不知道一个 refcount 里混着DMA 引用于是照常 COW/迁移导致设备写旧页、CPU 看新页的数据错乱。pin 计数独立出来后folio_maybe_dma_pinnedmm.h:1919能检测这页被 DMA 钉住了COW 时page_needs_cow_for_dmamm.h:1947fork 时用会提前 break COW 复制一份、而不是原地改保住设备正在用的那份。7.2 为什么单页用 bias1024、大 folio 用独立_pincountstruct page字段已满塞不下独立计数器用 1024 的 bias 复用 refcount 高位是零额外内存换计数精度压缩 1024 倍的折中。大 folio 是多个页合成、struct folio有空间才开独立字段做精确计数。这是内存开销 vs 计数精度的取舍按 folio 大小分两档。7.3 为什么有 fast 和 slow 两条路径因为 GUP 是热路径每笔 DMA、每次 RDMA 都调而持mmap_lock的慢路径有锁竞争、还要能触发缺页。fast 路径关中断 无锁原子读 pte recheck把页已经映射好了的常见情况做到最快遇到复杂情况页没映射、THP split、PROT_NONE就退回慢路径兜底。“快路径覆盖常见 case、慢路径覆盖完整语义”和 #5 缺页的 fast/slow 是同一套思想。7.4 为什么长期 pin 要把 movable 区的页迁走movable zoneCMA的页本来就是要被迁移腾地方的长期 pin 的页不能被迁移留在里面就成了钉子户卡死 compaction/CMA 连续分配。所以 pin 前先把它们迁到 non-movable zone别让长期钉死的页占着随时要搬走的区。7.5 为什么零页不 pin零页是全局共享、永不回收、永不迁移的特殊页pin 它既没必要它本来就不会变也浪费 pin 计数。所以try_grab_folio对is_zero_page直接返回、不记账。八、与已有博客的交叉引用已有博客本篇覆盖的关系内存 #3 页表缺页GUP 慢路径的follow_page_mask就是走同一套多级页表faultin_page复用 #3 的handle_mm_fault缺页机制。GUP 是缺页机制的一个只读查询 pin的变体内存 #5 page cacheO_DIRECT绕过 page cache 后驱动用 GUP 拿到用户缓冲区物理页直接 DMApage cache 是文件缓存GUP 是用户内存 pin两者是 DMA 的两条不同数据来源IOMMU #7 DMA API本篇 §六GUP 拿物理页vaddr→pagedma_map_page再映射成 IOVApage→设备地址两段拼成用户态零拷贝 DMA。GUP 是 DMA API 的上游内存 #4 大页GUP 的gup_huge_pmd/hugetlb_follow_page_mask能直接 pin 大页THP/HugeTLB 的大页也可被 pin 给设备附本篇关键源码索引符号位置get_user_pages/get_user_pages_unlockedmm/gup.c:2367/:2395pin_user_pages/pin_user_pages_unlockedmm/gup.c:3370/:3390get_user_pages_fast/pin_user_pages_fastmm/gup.c:3270/:3304__get_user_pages慢路径mm/gup.c:1184__gup_longterm_lockedpin 慢路径mm/gup.c:2187follow_page_mask慢路径页表遍历mm/gup.c:809gup_pte_range快路径 pte 遍历mm/gup.c:2555try_grab_folioget/pin 核心mm/gup.c:126try_get_folioFOLL_GETmm/gup.c:71gup_put_folio释放mm/gup.c:189check_and_migrate_movable_pages迁出 movable 区mm/gup.c:2161GUP_PIN_COUNTING_BIAS1024include/linux/mm.h:1577FOLL_WRITE/FOLL_GET/FOLL_LONGTERMinclude/linux/mm_types.h:1332/:1334/:1355FOLL_PIN内部 flagmm/internal.h:960
延伸阅读

更多相关文章

2026/10/15 2:43:05

Spring Boot+Vue在线作业系统开发实践

1. 项目背景与核心价值去年帮本地一所中学开发在线作业系统时,我深刻体会到传统纸质作业的痛点:教师批改效率低、作业流转周期长、学情反馈滞后。这个基于Spring Boot的移动端作业管理系统,正是为了解决这些教育场景中的实际痛点而生。这套系…

2026/10/15 2:43:05

练题簿在线刷题:导入自己的题目,随时练习、集中复习

把资料整理成题库,让在线刷题、错题回顾和模拟自测接上每天的学习。复习资料放在表格、文档和图片里,每次找题、核对答案都要来回翻。练题簿让这些内容进入自己的题库,通过在线刷题反复使用,从课后巩固到阶段备考,都能…

2026/10/15 2:43:05

钓鱼源码本地搭建与安全分析:从环境配置到防御靶场

简介:这份源码资源面向需要研究电商平台钓鱼页面实现原理的安全学习者与开发者,提供转转、闲鱼两套仿站前端及独立后台管理程序,并集成个人二维码收款功能,可用于理解此类页面的结构组织与交互逻辑。压缩包共355个文件&#xff0c…

2026/10/15 4:03:08

家长怎么远程控制孩子手机 家长控制孩子手机的方法

家长怎么远程控制孩子手机?很多家长日常工作忙碌,无法时刻陪伴孩子,难以实时掌握孩子的手机使用情况,管控十分被动。家长怎么远程控制孩子手机呢?无需复杂设备和繁琐设置,无界趣连2.0适配亲子远程管控场景&…

2026/10/15 4:03:08

Android 计算器开发实战:从界面设计到核心逻辑

摘要:本文手把手带你从零构建一个基于 Android 的计算器应用。内容涵盖项目环境搭建与分层架构规划、界面布局与组件设计、核心状态变量管理、按钮事件监听、屏幕显示更新、输入合法性校验、用户提示机制,以及独立计算类的封装与联调测试。通过完整流程,你将掌握 Android 开…

2026/10/15 4:03:08

Java 项目实战: 交友项目 - 开工任务书

概述 我们开工前要准备 “继续往下写代码的资格”:把一个空目录变成能跑起来的多模块微服务工程,并在上面跑通手机号加验证码登录。 纲要 本章目标:从空工程到可运行的微服务骨架与登录闭环五个板块的任务书:认识项目 / 工程准…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑