vm_area_struct :管理【进程的用户态虚拟内存】
vm_struct :管理【内核态的虚拟内存】
vm_area_struct 是进程用户态虚拟内存的分段描述符 ,vm_struct 是内核全局虚拟内存的描述符 ,二者无任何继承关系,是两套独立的内存管理体系。一个管用户层虚拟内存,一个管内核层虚拟内存;
vm_area_struct与vm_struct对比表
| 对比维度 | vm_area_struct |
vm_struct |
|---|---|---|
| 核心归属 | 进程地址空间(mm_struct) |
内核全局地址空间(唯一,所有进程共享) |
| 管理对象 | 进程的用户态虚拟地址区间 | 内核的内核态虚拟地址区间 |
| 映射物理内存 | 物理内存页(用户进程的堆 / 栈 / 代码段 /mmap 映射) | 物理内存页(内核的缓冲区 / 驱动内存 / 模块空间) |
| 是否参与页表映射 | 参与,最终会生成进程的用户态页表项 | 参与,最终会生成内核的全局页表项 |
| 是否支持 Swap 换出 | 支持,普通 VMA 的物理页可被 swap out 到交换分区 | 绝对不支持,内核内存禁止 Swap |
| 是否可加锁(vm_lock) | 支持,打VM_LOCKED标记,物理页常驻内存 |
无锁机制,内核内存本身就常驻物理内存 |
| 内存属性 | 可私有(进程独占)、可共享(多进程共享) | 全局共享,所有进程的内核态都能访问同一段地址 |
| 核心标志位 | VM_READ/WRITE/EXEC/LOCKED/SHARED/ANON |
VM_IO/VM_ALLOC/VM_MAP 等内核专属标志 |
| 核心关联结构体 | mm_struct(一对多,一个进程对应多个 VMA) |
vmap_area、vmap_pgtables(内核虚拟内存池) |
| 是否有并发锁保护 | Linux5.4 + 有vm_lock自旋锁 + 进程mmap_lock |
内核全局vmap_lock自旋锁保护,全局唯一 |
vm_area_struct 是 Linux 内核描述进程虚拟地址空间中连续区间 的核心数据结构,它也被称为虚拟内存区域(VMA) 。内核通过管理多个 vm_area_struct 实例,构建出进程的完整虚拟地址空间,同时实现虚拟内存的权限控制、映射管理和缺页异常处理。
进程的虚拟地址空间并非整块连续的区域,而是由多个互不重叠、权限不同的 VMA 组成,例如:
- 代码段(
.text):只读、可执行 - 数据段(
.data/.bss):可读可写、不可执行 - 堆(heap):可读可写、动态扩展
- 栈(stack):可读可写、自动扩展
- 内存映射段(mmap):如共享库、文件映射
vm_area_struct 的核心作用:
- 描述一个连续虚拟地址区间的起始地址、结束地址、访问权限。
- 关联该区间对应的内存映射类型(如匿名映射、文件映射)。
- 提供缺页异常处理函数,当进程访问该区间未映射的地址时,内核会调用对应的处理逻辑。
- 组织成红黑树和双向链表,方便内核快速查找、插入和删除 VMA。
vm_area_struct 的结构体定义

关键字段解析
| 字段 | 核心作用 |
|---|---|
vm_start/vm_end |
定义 VMA 的虚拟地址范围,左闭右开 区间 [vm_start, vm_end) |
vm_mm |
关联到进程的 mm_struct,一个 mm_struct 对应多个 vm_area_struct |
vm_page_prot |
页表项的保护属性,最终会写入 PTE 表项的权限位,由 MMU 硬件强制执行 |
vm_flags |
软件层面的标志,控制内核对该 VMA 的处理逻辑,如 VM_SHARED(共享映射)、VM_ANON(匿名映射) |
vm_ops |
缺页处理的核心接口,例如 fault 函数用于处理该 VMA 的缺页异常 |
vm_file |
区分映射类型:非 NULL 为文件映射 (如 mmap 共享库),NULL 为匿名映射(如堆、栈) |
vm_area_struct 的组织方式
每个进程的 mm_struct 中,通过两种数据结构组织所有 vm_area_struct:
- 双向链表 :字段
mm->mmap指向链表头,vm_next/vm_prev串联所有 VMA,适合遍历所有区间(如进程退出时销毁所有 VMA)。 - 红黑树 :字段
mm->mm_rb指向树根,vm_rb为树节点,适合快速查找(如缺页异常时定位 VMA)。
两种结构的关系:同一个 VMA 同时存在于链表和红黑树中,内核根据场景选择不同的结构操作。
关键标志位 vm_flags 与权限控制
vm_flags 是 VMA 的核心属性,控制内核对该区间的管理策略,常见标志位如下:
| 标志位 | 含义 |
|---|---|
VM_READ |
该区间允许读访问 |
VM_WRITE |
该区间允许写访问 |
VM_EXEC |
该区间允许执行 |
VM_SHARED |
共享映射(多个进程可共享该区间的物理页) |
VM_ANON |
匿名映射(无对应的文件,如堆、栈) |
VM_GROWSDOWN |
该区间可向下扩展(如栈) |
VM_GROWSUP |
该区间可向上扩展(如堆) |
权限检查规则:
- 进程访问 VMA 的权限必须包含在
vm_flags中,否则触发缺页异常。 vm_page_prot是vm_flags的硬件映射,内核会将vm_flags转换为页表项的权限位(如_PAGE_READ、_PAGE_WRITE),由 MMU 强制执行。
VMA 的创建
当进程通过 mmap()、brk() 等系统调用申请虚拟内存时,内核会创建新的 vm_area_struct:
mmap()系统调用 :用户态调用mmap()后,内核执行do_mmap()函数,分配一段空闲虚拟地址区间,初始化vm_area_struct的字段(如vm_start、vm_end、vm_ops、vm_file),并将其插入到mm_struct的链表和红黑树中。- 堆扩展(
brk()) :brk()会调整堆对应的 VMA 的vm_end地址,若相邻地址无冲突,直接扩展;否则创建新 VMA。 - 栈扩展:栈的扩展是自动的,当进程访问栈顶之外的地址时,触发缺页异常,内核会扩展栈对应的 VMA。
VMA 的查找
内核需要频繁查找进程虚拟地址对应的 VMA,例如缺页异常处理 、内存保护检查 时,核心函数是 find_vma():
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);
- 功能:在
mm指向的地址空间中,查找包含addr或第一个大于addr的 VMA。 - 实现:优先通过红黑树快速查找(时间复杂度 O(logn)),链表仅用于遍历所有 VMA。
VMA 的合并与拆分
- 合并 :当新创建的 VMA 与相邻 VMA 的权限、映射类型、vm_ops 完全相同时,内核会将它们合并为一个大的 VMA,减少 VMA 数量,提升管理效率。
- 拆分 :当进程通过
munmap()释放 VMA 中间的一段地址时,内核会将原 VMA 拆分为两个独立的 VMA。
VMA 的销毁
当进程释放虚拟内存时(如 munmap()、进程退出),内核会调用 do_munmap() 函数,从链表和红黑树中移除对应的 vm_area_struct,并释放其占用的内存。
vm_area_struct 与缺页异常
vm_area_struct 是内核处理缺页异常的关键依据,完整流程如下:
- 进程访问一个虚拟地址,MMU 发现该地址对应的页表项无效,触发缺页异常。
- 内核进入缺页异常处理函数
do_page_fault()。 - 调用
find_vma(mm, addr)查找该地址所属的 VMA:- 若未找到 VMA → 地址非法,发送
SIGSEGV信号终止进程。 - 若找到 VMA → 检查访问权限是否匹配(如写只读 VMA),不匹配则发送
SIGSEGV。
- 若未找到 VMA → 地址非法,发送
- 调用 VMA 的
vm_ops->fault函数,处理具体的缺页逻辑:- 匿名映射(堆 / 栈) :分配物理页,建立页表映射(
anon_vma相关逻辑)。 - 文件映射(共享库) :从磁盘读取文件数据到物理页,建立页表映射(
filemap_fault)。
- 匿名映射(堆 / 栈) :分配物理页,建立页表映射(
- 更新页表项,刷新 TLB,异常返回后进程继续执行。
vm_struct 的设计初衷:
专门用来描述内核态中,由 vmalloc() 系列函数分配的「虚拟地址连续、物理地址离散」的内核虚拟内存区间,是内核管理「vmalloc 内存区」的核心结构体。
vm_struct 结构体完整定义

vm_struct 关键字段
addr:核心字段,内核分配的连续虚拟地址起始地址 ,内核代码直接使用这个地址访问内存,这个地址一定落在内核的vmalloc地址区间(x86_64:0xffffc90000000000 ~ 0xffffe7ffffffffff);pages+nr_pages:pages是物理页指针数组,每个元素指向一个struct page物理页结构体;nr_pages是数组长度,记录该虚拟地址映射了多少个离散的物理页;这两个字段是实现「虚拟连续、物理离散」的核心;flags:内核虚拟内存的类型标志,核心取值如下,无其他杂项:VM_ALLOC:该内存由vmalloc()分配,物理页离散,虚拟地址连续;VM_MAP:该内存由vmap()分配,把已有的物理页映射成连续虚拟地址;VM_IOREMAP:该内存是内核对硬件 IO 地址的映射(如驱动映射外设寄存器);
next:所有的vm_struct通过单向链表串联,内核维护一个全局的vmlist链表头,管理所有内核态的vm_struct;phys_addr:仅在映射物理连续的内存时有效(如 IO 映射),vmalloc()分配的内存该字段为 0。
vm_struct 核心特性
归属「内核全局共享」,所有进程共用一份
内核只有一个全局的 vm_struct 链表 (vmlist),所有进程的内核态都访问这同一个链表,所有进程看到的内核虚拟地址空间是完全相同的。
对比:
vm_area_struct是进程私有,每个进程都有自己的 VMA 链表;vm_struct是内核全局,所有进程共享。
管理「内核态虚拟内存」,绝对禁止用户态访问
vm_struct 描述的虚拟地址,全部落在内核地址空间 (高于PAGE_OFFSET),用户态进程无法直接访问,用户态访问会触发段错误(SIGSEGV),只有内核态代码(驱动、内核模块、内核系统调用)能访问。
对比:
vm_area_struct描述的是用户态虚拟地址,用户态进程可直接访问,内核态通过copy_to_user()/copy_from_user()间接访问。
虚拟地址连续,物理地址离散
这是vm_struct最核心的特点,也是和「线性映射」的核心区别:
- 内核通过
vmalloc()申请内存时,返回的addr是连续的内核虚拟地址,内核代码可以像访问数组一样连续读写; - 但这个虚拟地址对应的物理内存页是离散的、不连续的 ,内核通过
pages数组管理这些离散的物理页; - 内核会为这些离散的物理页,单独建立内核页表映射,MMU 通过页表完成地址翻译,对内核代码透明。
永不换出、永不回收,无 Swap、无锁定机制
vm_struct管理的内核内存,有两个「绝对禁止」:
- 绝对禁止被 Swap 换出到交换分区,内核内存永远不会进入 Swap;
- 绝对不会被内核内存回收机制(kswapd)回收 ,除非内核主动调用
vfree()释放;
对比:
vm_area_struct管理的用户内存,默认可被 Swap 换出,需要手动加VM_LOCKED才常驻内存;vm_struct无需任何锁定,天生常驻物理内存。
内存分配是「立即映射」,无懒加载
vmalloc()分配内存时,内核会立即分配物理页 + 建立内核页表映射,返回的虚拟地址可以直接使用,不会触发缺页异常;
对比:
vm_area_struct是「懒加载」,创建时只分配虚拟地址,访问时才触发缺页异常分配物理页。
vm_area_struct 与 vm_struct 核心区别
【管理的内存层级】------ 最核心区别
vm_area_struct:管理进程的用户态虚拟内存(3G 以下,x86_32)/(0~0x7fffffffffff,x86_64);vm_struct:管理内核的内核态虚拟内存(3G 以上,x86_32)/(0xffff888000000000 以上,x86_64)。
【归属与作用域】
vm_area_struct:进程私有 ,每个进程都有独立的mm_struct和 VMA 链表,进程 A 的 VMA 对进程 B 不可见;vm_struct:内核全局共享 ,只有一个全局链表vmlist,所有进程的内核态都访问同一份vm_struct,内核内存对所有进程可见。
【物理地址与虚拟地址的关系】
vm_area_struct:无固定关系,可映射「物理连续」(如共享库)或「物理离散」(如堆)的内存,虚拟地址连续即可;vm_struct:固定为「虚拟连续、物理离散」 ,这是其核心设计目标,也是vmalloc()的核心价值。
【Swap 交换支持】
vm_area_struct:支持,普通 VMA 的物理页可被内核 kswapd 换出到 Swap 分区,需要时再换入;vm_struct:完全不支持,内核内存永不 Swap,这是内核的硬性规定,防止内核崩溃。
【内存锁定机制】
vm_area_struct:支持,通过VM_LOCKED标志位 +vm_lock自旋锁,实现物理页常驻内存,禁止换出;vm_struct:无需锁定,内核内存本身就常驻物理内存,无任何锁定相关的字段和机制。
【内存分配的加载方式】
vm_area_struct:懒加载(按需映射),创建 VMA 时只分配虚拟地址区间,物理页和页表映射在进程访问时通过缺页异常分配;vm_struct:立即映射 ,调用vmalloc()时,内核立即分配物理页、建立页表映射,返回的虚拟地址可直接使用,无缺页异常。
【内存释放的主动程度】
vm_area_struct:可主动释放(munmap()),也可进程退出时由内核自动释放所有 VMA;vm_struct:必须主动释放 ,内核不会自动回收,调用vmalloc()后必须调用vfree()释放,否则会造成内核内存泄漏,最终导致系统崩溃。
【数据结构的组织方式】
vm_area_struct:双结构组织 → 双向链表 + 红黑树,链表用于遍历所有 VMA,红黑树用于快速查找指定地址的 VMA;vm_struct:单结构组织 → 单向链表 ,内核中vm_struct的数量远少于进程的 VMA 数量,单向链表足够高效,无需红黑树。
【并发保护的锁机制】
vm_area_struct:双层锁保护 → 进程的mmap_lock(读写信号量,粗粒度) + 每个 VMA 的vm_lock(自旋锁,细粒度);vm_struct:单层锁保护 → 内核全局的vmap_lock(自旋锁),保护vmlist链表的并发修改,全局唯一。
【核心创建 / 释放接口】
vm_area_struct:创建→do_mmap(),释放→do_munmap();用户态对应mmap()/munmap()系统调用;vm_struct:创建→vmalloc()/vmap(),释放→vfree()/vunmap();只有内核态能调用,用户态无对应的系统调用。
vm_area_struct 与 vm_struct 的唯一关联
二者虽然是两套独立的内存管理体系,无继承、无嵌套、无包含关系 ,但在内核源码中,有且仅有一处间接关联: 二者都依赖内核的页表机制完成虚拟地址到物理地址的映射,最终都会把映射关系写入页表,由 CPU 的 MMU 硬件完成地址翻译。
vm_area_struct的映射关系 → 写入进程的用户态页表(每个进程独立的页表);vm_struct的映射关系 → 写入内核的全局页表(所有进程共享的页表);
内核中内存分配的选型原则
原则 1:用户态进程申请内存 → 必用 mmap() → 对应 vm_area_struct
用户态进程所有的内存申请(堆、栈、共享内存、文件映射),最终都会调用mmap()系统调用,内核通过vm_area_struct管理这些内存,这是唯一的方式。
原则 2:内核态申请「小内存 + 物理连续」→ 用 kmalloc() → 走「线性映射」,无vm_struct
内核中绝大多数内存申请都是这种场景,比如分配内核结构体、缓冲区等,kmalloc()分配的内存是物理连续的,直接通过线性映射访问,效率极高,无需vm_struct参与。
原则 3:内核态申请「大内存 + 物理离散」→ 用 vmalloc() → 对应 vm_struct
当内核需要申请大块内存(如几 MB / 几十 MB),且不需要物理连续时,用vmalloc(),内核通过vm_struct管理这段内存,虚拟地址连续,物理地址离散,适合驱动的缓冲区、内核模块的大内存申请。
原则 4:内核态映射「硬件 IO 地址」→ 用 ioremap() → 对应 vm_struct(flags=VM_IOREMAP)
驱动开发中,映射外设的物理寄存器地址到内核虚拟地址,用ioremap(),底层会创建vm_struct结构体,标记VM_IOREMAP标志位,这是驱动开发的标配。