3. VMA 数据结构:struct vm_area_struct 核心字段解析
虚拟内存区域(VMA)是 Linux 内核管理进程地址空间的核心抽象。每个 VMA 描述一段连续的、具有相同属性的虚拟地址区间。内核使用 struct vm_area_struct 结构体来管理这些区域,它是内存映射、缺页中断、权限检查等操作的基础数据结构。
3.1 VMA 在进程地址空间中的角色
每个进程的 mm_struct 维护一棵红黑树(mmap 链表与红黑树并存),树中的每个节点就是一个 VMA。VMA 定义了虚拟地址的起始、结束、权限、映射文件、操作函数集等关键信息。
核心概念: VMA 是虚拟地址空间管理的“最小管理单元”。内核通过 VMA 实现按需分配、按需加载、写时复制(COW)等高级内存特性。
3.2 核心字段详解
struct vm_area_struct 包含数十个字段,以下是最核心的 6 个字段,它们直接决定了 VMA 的行为和属性。
| 字段名 | 类型 | 作用描述 | 典型值/示例 |
|---|---|---|---|
vm_start |
unsigned long |
VMA 覆盖的虚拟地址起始(包含) | 0x400000 |
vm_end |
unsigned long |
VMA 覆盖的虚拟地址结束(不包含) | 0x401000 |
vm_flags |
unsigned long |
VMA 的访问权限与属性标志位 | VM_READ | VM_WRITE | VM_EXEC |
vm_ops |
const struct vm_operations_struct * |
指向 VMA 操作函数集的指针 | &file_ops 或 NULL |
vm_file |
struct file * |
映射的文件对象(文件映射时有效) | NULL(匿名映射) |
vm_next / vm_prev |
struct vm_area_struct * |
链表指针,用于遍历进程的所有 VMA | 双向链表节点 |
3.3 vm_start 与 vm_end:地址区间定义
这两个字段定义了 VMA 管理的虚拟地址范围:
- 区间表示:
[vm_start, vm_end),即包含起始地址,不包含结束地址。 - 对齐要求: 起始地址必须按页对齐(
PAGE_SIZE对齐),长度是页大小的整数倍。 - 区间不重叠: 同一进程的不同 VMA 覆盖的地址区间不会重叠。
调试技巧: 通过
/proc/[pid]/maps 可以查看进程的所有 VMA 信息,每行对应一个 VMA,格式为 vm_start-vm_end 权限 偏移 主设备:次设备 inode 路径。
3.4 vm_flags:权限与属性标志
vm_flags 是一个位图字段,每个比特位代表一种属性。以下是常用的标志位:
| 标志位 | 值 | 含义 | 典型场景 |
|---|---|---|---|
VM_READ |
0x00000001 | 可读 | 所有映射 |
VM_WRITE |
0x00000002 | 可写 | 堆、栈、数据段 |
VM_EXEC |
0x00000004 | 可执行 | 代码段 |
VM_SHARED |
0x00000008 | 共享映射(多进程共享物理页) | 共享内存、文件映射 |
VM_GROWSDOWN |
0x00000100 | 可向下增长(栈) | 栈区域 |
VM_DENYWRITE |
0x00000800 | 禁止其他进程写映射文件 | 文件映射 |
VM_IO |
0x00004000 | 映射设备 I/O 空间 | 设备驱动 mmap |
注意:
vm_flags 中的权限位与硬件页表项中的权限位是两套独立的机制。VMA 的权限是“软件权限”,页表项中的权限是“硬件权限”。缺页中断处理时,内核会检查 VMA 权限,然后设置页表项权限。
3.5 vm_ops:VMA 操作函数集
vm_ops 是一个指向 struct vm_operations_struct 的指针,定义了 VMA 的生命周期回调函数。这是实现自定义内存映射行为的关键接口。
struct vm_operations_struct {
void (*open)(struct vm_area_struct *vma); // VMA 被复制或引用时调用
void (*close)(struct vm_area_struct *vma); // VMA 被销毁时调用
vm_fault_t (*fault)(struct vm_fault *vmf); // 缺页中断处理(核心函数)
vm_fault_t (*page_mkwrite)(struct vm_fault *vmf); // 页面写保护触发
vm_fault_t (*pfn_mkwrite)(struct vm_fault *vmf); // PFN 映射写保护触发
int (*access)(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write); // 远程访问(ptrace 等)
};
关键点:
fault 回调是缺页中断处理的核心。当进程访问一个尚未分配物理页的虚拟地址时,内核会调用 VMA 的 fault 函数来分配物理页并建立页表映射。文件映射的 fault 函数会从文件系统读取数据;匿名映射的 fault 函数会分配零填充页。
3.6 其他重要字段
| 字段 | 说明 |
|---|---|
vm_pgoff |
文件映射时的文件偏移(以页为单位),匿名映射时为 0 |
vm_rb |
红黑树节点,用于快速查找 VMA |
shared |
共享映射的链表,用于管理共享物理页 |
anon_vma |
匿名映射的反向映射结构,用于写时复制 |
vm_mm |
指向所属进程的 mm_struct 的指针 |
3.7 VMA 操作流程示例:缺页中断处理
当进程访问一个虚拟地址,但页表中没有对应的页表项时,CPU 触发缺页中断。内核的缺页中断处理函数 handle_mm_fault 会执行以下步骤:
- 查找 VMA: 通过红黑树查找虚拟地址所属的 VMA。
- 权限检查: 检查访问类型(读/写/执行)是否与
vm_flags匹配。 - 调用
fault回调: 调用 VMA 的vm_ops->fault函数。 - 分配物理页:
fault函数分配物理页,并从文件读取数据(文件映射)或填充零(匿名映射)。 - 建立页表: 将物理页的地址写入页表项,设置权限位。
3.8 总结:VMA 字段与内存管理的关系
| 内存管理功能 | 依赖的 VMA 字段 | 说明 |
|---|---|---|
| 地址空间划分 | vm_start, vm_end |
定义进程地址空间的布局 |
| 权限控制 | vm_flags |
决定可读/可写/可执行权限 |
| 缺页中断处理 | vm_ops->fault |
按需分配物理页 |
| 写时复制 | anon_vma, vm_flags |
fork 后父子进程共享物理页 |
| 文件映射 | vm_file, vm_pgoff |
将文件内容映射到进程地址空间 |
| 栈自动增长 | VM_GROWSDOWN |
栈向下扩展时自动创建新 VMA |
学习建议: 阅读内核源码时,重点关注
mm/mmap.c 中的 do_mmap 函数(创建 VMA)和 mm/memory.c 中的 handle_mm_fault 函数(缺页中断处理)。这两个函数是 VMA 相关逻辑的核心入口。