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

1.1 虚拟地址空间与 VMA:每个进程的私有世界与划分管理方法

本篇目标:理解 Linux 如何为每个进程维护独立的虚拟地址空间,以及地址空间如何被划分为一个个 VMA(Virtual Memory Area)。VMA 是内核管理虚拟内存的基本单元,也是 HMM 判断地址范围权限和类型的依据。


1. 虚拟地址空间:进程眼中的世界

每个 Linux 进程都拥有自己独立的虚拟地址空间。从进程的视角看,它独占一整块连续的地址范围(在 64 位系统上通常是 128TB 用户空间),完全不知道其他进程的存在。

这种"私有世界"的抽象由内核的mm_struct结构体维护:

// include/linux/mm_types.hstructmm_struct{structmaple_treemm_mt;// VMA 的 maple tree(地址区间管理)unsignedlongmmap_base;// mmap 区域的基地址unsignedlongtask_size;// 用户空间大小(进程可用地址上限)pgd_t*pgd;// 页表根指针(第 3 篇详解)atomic_tmm_users;// 使用该地址空间的线程数atomic_tmm_count;// mm_struct 自身的引用计数intmap_count;// VMA 的数量structrw_semaphoremmap_lock;// 保护 VMA 操作(HMM 必须持有)unsignedlongstart_code,end_code;// 代码段范围unsignedlongstart_data,end_data;// 数据段范围unsignedlongstart_brk,brk;// 堆的起始和当前位置unsignedlongstart_stack;// 栈的起始地址// ...};

关键字段解读

字段含义HMM 相关性
mm_mt存储所有 VMA 的 maple treeHMM 通过 VMA 判断地址范围的权限
mmap_basemmap 区域基地址决定了动态映射区域的位置
task_size用户空间边界(通常 128TB)虚拟地址不能超过这个范围
mmap_lock保护地址空间的读写信号量HMM 所有操作都需要持有此锁
pgd页表根指针hmm_range_fault()遍历的起点(第 3 篇详解)
map_countVMA 数量反映地址空间的碎片化程度

mm_struct中的start_code/end_codestart_brk/brkmmap_basestart_stack等字段精确描述了进程地址空间中各区域的位置——它们与下一节的地址空间布局图一一对应。


2. 64 位进程地址空间布局

这样一个虚拟地址空间,被划分为了多个不同用途的地址区域。

在 x86_64 架构上,128TB 用户空间的典型布局如下(低地址在下,高地址在上):

图中有几个值得注意的要点:

非规范地址(Non-canonical Address):x86_64 虽然有 64 位地址线,但硬件实际只使用低 48 位(或 57 位,取决于是否开启 5 级页表)。CPU 要求地址的高位必须是低 48 位最高位的符号扩展——不满足这个规则的地址称为"非规范地址"。任何对非规范地址的访问都会触发硬件异常,因此用户空间(低地址)和内核空间(高地址)之间天然存在一个巨大的不可用空洞。

已映射区域 vs 未映射区域:128TB 的用户空间并不是全部可用的。只有进程明确请求使用的地址范围(通过加载程序、分配堆、映射文件等操作)才是"已映射"的——CPU 访问这些地址时能正常读写数据。而那些从未被使用的地址范围就是"未映射"的空洞,访问它们会触发段错误(SIGSEGV)。

从图中可以看出,地址空间并非铁板一块——它由若干不连续的已映射区域组成,中间穿插着大片未映射的空洞。内核需要一种方式来精确记录"哪些地址范围是已映射的、各自有什么权限"。这就是下一节要介绍的VMA(Virtual Memory Area):每个已映射区域(代码段、数据段、堆、mmap、栈)对应一个或多个 VMA,而未映射区域没有 VMA。


3. VMA:地址空间的管理单元

虽然地址空间看起来是一整块连续的范围,但内核并不是整体管理它的。而是将其划分为若干个VMA(Virtual Memory Area)——每个 VMA 描述一段连续的、具有相同属性的虚拟地址区间。

进程地址空间中的 VMA 示意: 地址 ────────────────────────────────────────────── │ VMA1: [0x400000, 0x401000) 代码段 r-x │ │ VMA2: [0x601000, 0x602000) 数据段 rw- │ │ VMA3: [0x602000, 0x623000) 堆 rw- │ │ ... 未映射 ... │ │ VMA4: [0x7f..., 0x7f...) libc.so r-x │ │ VMA5: [0x7f..., 0x7f...) libc.so rw- │ │ VMA6: [0x7fff..., 0x7fff...) 栈 rw- │ ─────┴─────────────────────────────────────────────

3.1 struct vm_area_struct

每个 VMA 由内核的vm_area_struct描述:

// include/linux/mm_types.hstructvm_area_struct{unsignedlongvm_start;// VMA 起始地址(含)unsignedlongvm_end;// VMA 结束地址(不含)structmm_struct*vm_mm;// 所属的地址空间pgprot_tvm_page_prot;// 页面保护属性(给硬件 MMU 用)vm_flags_tvm_flags;// VMA 属性标志(给内核逻辑用)structanon_vma*anon_vma;// 匿名页的反向映射conststructvm_operations_struct*vm_ops;// 操作回调unsignedlongvm_pgoff;// 文件映射的偏移(页单位)structfile*vm_file;// 映射的文件(NULL = 匿名映射)void*vm_private_data;// 驱动私有数据// ...};

关键字段解读

字段含义HMM 相关性
vm_start/vm_endVMA 覆盖的虚拟地址范围 [start, end)HMM 按 VMA 边界确定操作范围
vm_flags权限和类型标志HMM 通过它判断可读/可写/是否 PFNMAP 等
vm_page_prot硬件级页保护最终写入 PTE 的保护位
vm_file关联的文件(或 NULL)区分文件映射 vs 匿名映射
vm_opsVMA 操作回调fault回调用于缺页时分配页面
vm_mm所属的 mm_structHMM 通过它获取 mmap_lock 和 pgd

3.2 vm_flags:VMA 的属性标志

vm_flags是一个位图,编码了 VMA 的权限和行为特征:

// include/linux/mm.h(部分)#defineVM_READ0x00000001// 可读#defineVM_WRITE0x00000002// 可写#defineVM_EXEC0x00000004// 可执行#defineVM_SHARED0x00000008// 共享映射(vs 私有/COW)#defineVM_MAYREAD0x00000010// 允许 mprotect 设为可读#defineVM_MAYWRITE0x00000020// 允许 mprotect 设为可写#defineVM_MAYEXEC0x00000040// 允许 mprotect 设为可执行#defineVM_IO0x00004000// 设备 I/O 映射(不可迁移)#defineVM_PFNMAP0x00000400// 纯 PFN 映射(无 struct page)#defineVM_MIXEDMAP0x10000000// 混合映射(有些页有 struct page)

HMM 特别关注的 vm_flags

标志HMM 行为
VM_READ/VM_WRITE决定hmm_range_fault()输出的 PFN 是否标记为可写
VM_IOHMM 跳过——I/O 映射不参与 HMM 管理
VM_PFNMAPHMM 跳过——纯 PFN 映射没有 struct page
VM_SHARED影响 COW 行为和迁移策略

3.3 VMA 的类型

根据vm_filevm_flags的组合,VMA 可分为几种类型:

类型vm_file特征示例
匿名私有NULL进程独占,COW堆、栈、malloc
匿名共享NULL + VM_SHARED多进程共享POSIX 共享内存
文件私有非 NULLCOW,延迟加载mmap(MAP_PRIVATE, fd)
文件共享非 NULL + VM_SHARED多进程共享,回写文件mmap(MAP_SHARED, fd)
设备映射非 NULL + VM_IO直接映射设备 BARGPU framebuffer

HMM 主要工作在匿名私有文件私有VMA 上——这些才是需要在 CPU 和设备之间迁移的页面。


4. VMA 的组织:Maple Tree

一个进程可能有几十到几百个 VMA。内核需要一种高效的数据结构来管理它们,支持:

  • 按地址快速查找VMA(find_vma(mm, addr)
  • VMA 的插入和删除mmap/munmap
  • 相邻 VMA 的合并(减少碎片)

Linux 6.x 使用Maple Tree(一种 B-tree 变体)来组织 VMA:

// 查找包含 addr 的 VMAstructvm_area_struct*find_vma(structmm_struct*mm,unsignedlongaddr);// 遍历指定范围内的所有 VMAVMA_ITERATOR(vmi,mm,start);for_each_vma_range(vmi,vma,end){// 处理 [vma->vm_start, vma->vm_end) 范围}

HMM 的 VMA 查找流程

hmm_range_fault(start, end)被调用时,内核需要:

  1. find_vma()或 VMA iterator 找到覆盖[start, end)的所有 VMA
  2. 检查每个 VMA 的vm_flags——跳过VM_IOVM_PFNMAP
  3. 如果存在地址空洞(无 VMA 覆盖),返回错误

4.1 VMA 合并

当新创建的 VMA 与相邻 VMA 具有相同属性时,内核会自动合并它们以减少碎片:

合并前: VMA1: [0x1000, 0x2000) rw- 匿名 VMA2: [0x2000, 0x3000) rw- 匿名 ← 属性相同且地址连续 合并后: VMA1: [0x1000, 0x3000) rw- 匿名 ← 合并为一个 VMA

这对 HMM 是有利的——更大的 VMA 意味着更少的 VMA 遍历和边界检查。


5. 用户态如何创建 VMA

用户态通过系统调用操作 VMA:

系统调用效果
mmap()创建新 VMA(文件映射或匿名映射)
munmap()删除 VMA(或拆分现有 VMA)
mprotect()修改 VMA 权限(可能拆分 VMA)
mremap()移动或扩展 VMA
brk()扩展/收缩堆 VMA

每次这些操作修改地址空间时,都需要持有mmap_lock。HMM 也持有同一把锁来保证遍历时 VMA 不会突然变化。


6. 实验:用 /proc/pid/maps 观察 VMA

Linux 通过/proc/[pid]/maps暴露进程的所有 VMA:

$cat/proc/self/maps555555554000-555555558000 r--p 00000000 08:011234/usr/bin/cat555555558000-555555560000 r-xp 00004000 08:011234/usr/bin/cat555555560000-555555563000 r--p 0000c000 08:011234/usr/bin/cat555555564000-555555565000 rw-p 0000f000 08:011234/usr/bin/cat555555565000-555555586000 rw-p 00000000 00:000[heap]7ffff7d00000-7ffff7d28000 r--p 00000000 08:015678/usr/lib/libc.so.6 7ffff7d28000-7ffff7e80000 r-xp 00028000 08:015678/usr/lib/libc.so.6... 7ffffffde000-7ffffffff000 rw-p 00000000 00:000[stack]

每一行就是一个 VMA,格式为:

起始地址-结束地址 权限 偏移 设备号 inode 路径名

权限字段含义:

  • r/-:可读 / 不可读 →VM_READ
  • w/-:可写 / 不可写 →VM_WRITE
  • x/-:可执行 / 不可执行 →VM_EXEC
  • p/s:私有 / 共享 →VM_SHARED

更详细的用法请参加后面的关联阅读。

6.1 动手实验:创建不同类型的 VMA

下面这个小程序创建了几种典型的 VMA,然后打印/proc/self/maps让你直观看到它们:

// vma_demo.c// 编译: gcc -o vma_demo vma_demo.c// 运行: ./vma_demo#include<stdio.h>#include<stdlib.h>#include<string.h>#include<sys/mman.h>#include<fcntl.h>#include<unistd.h>intmain(void){// 1. 匿名私有映射(类似 malloc 大块分配)void*anon_priv=mmap(NULL,4096*4,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);// 2. 匿名共享映射(可用于父子进程通信)void*anon_shared=mmap(NULL,4096*2,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);// 3. 文件私有映射(COW)intfd=open("/etc/hostname",O_RDONLY);void*file_priv=NULL;if(fd>=0){file_priv=mmap(NULL,4096,PROT_READ,MAP_PRIVATE,fd,0);close(fd);}// 4. 堆分配(brk 管理的 VMA)void*heap=malloc(1024);// 写入数据使页面真正分配memset(anon_priv,'A',4096);memset(anon_shared,'B',4096);// 打印所有 VMAprintf("=== PID %d 的 VMA 列表 ===\n\n",getpid());printf("anon_priv @ %p (匿名私有, rw-p)\n",anon_priv);printf("anon_shared @ %p (匿名共享, rw-s)\n",anon_shared);if(file_priv)printf("file_priv @ %p (文件私有, r--p)\n",file_priv);printf("heap @ %p (堆, rw-p)\n",heap);printf("\n--- /proc/self/maps ---\n\n");// 读取并打印 mapsFILE*f=fopen("/proc/self/maps","r");charline[256];while(fgets(line,sizeof(line),f))fputs(line,stdout);fclose(f);// 清理munmap(anon_priv,4096*4);munmap(anon_shared,4096*2);if(file_priv)munmap(file_priv,4096);free(heap);return0;}

运行示例输出(关键部分):

=== PID 12345 的 VMA 列表 === anon_priv @ 0x7f8a00010000 (匿名私有, rw-p) anon_shared @ 0x7f8a00008000 (匿名共享, rw-s) file_priv @ 0x7f8a00007000 (文件私有, r--p) heap @ 0x5555555592a0 (堆, rw-p) --- /proc/self/maps --- 555555559000-55555557a000 rw-p 00000000 00:00 0 [heap] 7f8a00007000-7f8a00008000 r--p 00000000 08:01 1234 /etc/hostname ← 文件私有 7f8a00008000-7f8a0000a000 rw-s 00000000 00:00 0 ← 匿名共享 7f8a00010000-7f8a00014000 rw-p 00000000 00:00 0 ← 匿名私有 ... 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]

注意观察:

  • 匿名私有:rw-p,无文件路径
  • 匿名共享:rw-s,无文件路径(注意s而非p
  • 文件私有:r--p,有文件路径
  • 堆:rw-p,标记为[heap]

7. mmap_lock:保护地址空间的锁

我们已经知道 VMA 是地址空间的管理单元,而用户态随时可能通过mmap()munmap()等系统调用增删 VMA。与此同时,内核的其他路径(如缺页处理、HMM 的hmm_range_fault())也需要遍历 VMA。如果这些操作并发执行,没有保护机制的话,正在遍历的 VMA 可能突然被删除——这会导致内核崩溃。

Linux 用mm_struct中的mmap_lock解决这个问题。它是一个读写信号量(rw_semaphore),保护整个地址空间的 VMA 结构:

操作锁模式示例
读取/遍历 VMA读锁(mmap_read_lockhmm_range_fault()、page fault
修改 VMA(增删改)写锁(mmap_write_lockmmap()munmap()mprotect()

HMM 的锁规则

// 驱动使用 HMM 的典型模式mmap_read_lock(mm);hmm_range_fault(&range);// 遍历 VMA + 页表mmap_read_unlock(mm);

如果不持有mmap_lock,VMA 可能在遍历过程中被munmap()删除,导致内核崩溃。这也是 HMM 使用mmu_interval_notifier序列号协议的原因之一——即使持有锁,页表仍可能在锁释放的瞬间被修改。


8. 与 HMM 的联系

hmm_range_fault()接收一个虚拟地址范围[start, end),其内部流程的第一步就是 VMA 相关的检查:

// 简化的 hmm_range_fault 逻辑inthmm_range_fault(structhmm_range*range){// 1. 确认 mmap_lock 已持有// 2. walk_page_range() 内部会遍历 VMA:// - 跳过 VM_PFNMAP / VM_IO 类型的 VMA// - 根据 VMA 的 vm_flags 判断读写权限// - 对于地址空洞(无 VMA),报告错误// 3. 在有效 VMA 范围内遍历页表、提取 PFN}

没有 VMA,就没有合法的地址范围;没有 vm_flags,HMM 就不知道设备能以什么权限访问。


9. 本篇关键代码路径

文件核心内容
include/linux/mm_types.hstruct mm_structstruct vm_area_struct定义
include/linux/mm.hVM_READVM_WRITE等 vm_flags 定义
mm/mmap.cVMA 的创建、合并、拆分逻辑
include/linux/maple_tree.hMaple Tree 数据结构
mm/vma.cVMA 查找、遍历(find_vma等)
fs/proc/task_mmu.c/proc/pid/maps实现

10. 下篇预告

第 2 篇:struct page 与 PFN——VMA 背后的物理存储

我们现在知道了地址空间如何被划分为 VMA,但 VMA 只是虚拟的描述——它说"这块地址可读可写",但实际的数据存储在哪里?答案是物理页帧,由struct page管理。

下一篇我们将深入struct page(以及现代的folio),理解内核如何为每个物理页帧维护元数据。HMM 的一个关键创新是:让设备内存(GPU VRAM)也拥有struct page,从而能被内核框架统一管理。


11. 思考题

  1. 一个进程最多能有多少个 VMA?内核有限制吗?(提示:/proc/sys/vm/max_map_count
  2. 为什么 HMM 需要跳过VM_PFNMAP类型的 VMA?
  3. 如果 GPU 驱动想让一块设备内存被 CPU 进程mmap,应该创建什么类型的 VMA?
  4. 为什么64位地址,只使用48或者57位?

📚 关联阅读

  • linux VMA创建场景详解:分析了用户态的哪些函数调用会操作VMA
  • Linux /proc/<pid>/maps 内存映射调试指南:详细分析了maps用途
http://www.jsqmd.com/news/861044/

相关文章:

  • 利用TaoToken模型广场为不同文本处理任务选择性价比最优模型
  • day031
  • 11. 架构:前端工程化与状态管理实战
  • 2025-2026年北京装修设计公司推荐:十大口碑产品评测别墅装修避风格雷区市场份额价格 - 品牌推荐
  • 内存管理原理与策略
  • 2026 最新 Web 安全入门教程 零基础全面吃透 Web 攻防
  • TVA:打通数字AI到物理AI的关键桥梁(系列)
  • 2026年5月马赛克瓷砖品牌推荐:五款排行评测商业空间高耐磨特性专业价格 - 品牌推荐
  • 基于Windows内核驱动框架的游戏控制器虚拟化技术实现方案
  • 2026年四川城市管道清淤检测服务机构实测评测:四川城市管道清淤检测、四川工业污水转运、四川市政管道清淤检测、四川排水管道清淤检测选择指南 - 优质品牌商家
  • Postgresql基础实践教程(二)
  • 2026 网络安全渗透测试行业报告|机遇与前景
  • 新乡施工选仿石漆:在平顶山施工选仿石漆选谁、在开封施工选仿石漆选谁、在新乡施工选仿石漆选谁、在洛阳施工选仿石漆选谁选择指南 - 优质品牌商家
  • 2026年温州整体装修品牌实力对比:5家头部企业服务深度评测与选企建议 - 优家闲谈
  • 2026台州黄金专业回收TOP5评测:台州专业名表回收、台州台州奢侈品回收、台州名表回收、台州奢侈品专业回收、台州奢侈品保管选择指南 - 优质品牌商家
  • 2025-2026年马赛克瓷砖品牌推荐:五大口碑评测卫浴防潮耐用特点性价比高 - 品牌推荐
  • 网络协议基础与TCP/IP详解
  • 0 基础跨行斩获月薪 10k 实力远不及破局魄力
  • 2025-2026年北京装修设计公司推荐:五大口碑评测老房翻新避坑指南市场份额 - 品牌推荐
  • 5分钟学会Windows自动化:Pulover‘s Macro Creator终极指南
  • LDDC终极指南:如何快速获取精准的逐字歌词
  • article-extractor项目架构解析:模块化设计与可扩展性指南
  • 2025-2026年国内人力资源外包公司推荐:五大口碑评测企业用工合规价格选择指南 - 品牌推荐
  • 利用Taotoken审计日志功能追踪与分析团队内部的模型使用情况
  • vcs+verdi+vivado联合仿真
  • 股票打分制方法论
  • 2025-2026年上海靠谱搬家公司推荐:五大排行全程托管省心搬家评测性价比高适用场景 - 品牌推荐
  • 如何快速免费下载无水印抖音视频:一站式批量下载解决方案
  • CANN/asc-devkit cyl_bessel_i0f函数
  • PS 图片模糊修复教程:4 种方法,一键变高清