4. 虚拟内存区域 VMA:数据结构、操作与页表关联

虚拟内存区域(VMA,Virtual Memory Area)是 Linux 内核管理进程地址空间的核心抽象。每个 VMA 描述一段连续的、具有相同权限和映射属性的虚拟地址区间。内核通过 VMA 链表和红黑树来组织这些区域,实现高效查找、插入和权限检查。本章深入解析 VMA 的数据结构、关键操作函数以及 VMA 与页表之间的关联机制。

4.1 VMA 数据结构

VMA 在内核中使用 struct vm_area_struct 结构体表示,定义在 include/linux/mm_types.h 中。该结构体包含了描述一段虚拟内存区域所需的所有信息。

核心字段说明:
  • vm_start / vm_end:虚拟地址区间的起始和结束地址(包含起始,不包含结束)。
  • vm_flags:权限标志位,如 VM_READ、VM_WRITE、VM_EXEC、VM_SHARED 等。
  • vm_next / vm_prev:双向链表指针,用于按地址顺序遍历所有 VMA。
  • vm_rb:红黑树节点,用于在进程的地址空间红黑树中快速查找。
  • vm_mm:指向所属的 mm_struct(内存描述符)的指针。
  • vm_ops:指向 vm_operations_struct 结构体的指针,定义了针对该 VMA 的特殊操作(如缺页处理)。
  • vm_file:如果 VMA 映射了文件,指向对应的 file 结构体;匿名映射则为 NULL。
  • vm_pgoff:文件映射时的页偏移量。
struct vm_area_struct 核心结构 struct vm_area_struct 地址区间 vm_start (0x7f00...000) vm_end (0x7f00...fff) 权限标志 vm_flags: VM_READ | VM_WRITE VM_EXEC | VM_SHARED 链表指针 vm_next → 下一个 VMA vm_prev ← 上一个 VMA 红黑树节点 vm_rb (rb_node) 用于快速查找 关联结构 vm_mm → mm_struct vm_file → file (文件映射) 操作函数 vm_ops → vm_operations_struct (open/close/fault 等回调) 💡 关键设计 VMA 同时维护在双向链表和红黑树中:链表用于顺序遍历,红黑树用于 O(log n) 快速查找

4.2 VMA 操作函数

内核提供了一组核心函数来操作 VMA,其中 find_vma 和 insert_vm_struct 是最常用的两个。

4.2.1 find_vma:查找虚拟地址所属的 VMA

find_vma 函数用于查找给定虚拟地址所属的 VMA。它首先在红黑树中搜索,如果找到则返回该 VMA;否则返回地址空间中第一个起始地址大于给定地址的 VMA。

/* mm/mmap.c */
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
    struct rb_node *rb_node;
    struct vm_area_struct *vma;

    /* 检查缓存(最近访问的 VMA) */
    vma = vmacache_find(mm, addr);
    if (likely(vma))
        return vma;

    /* 红黑树查找 */
    rb_node = mm->mm_rb.rb_node;
    vma = NULL;

    while (rb_node) {
        struct vm_area_struct *tmp = rb_entry(rb_node,
                        struct vm_area_struct, vm_rb);

        if (tmp->vm_end > addr) {
            vma = tmp;
            if (tmp->vm_start <= addr)
                break;
            rb_node = rb_node->rb_left;
        } else {
            rb_node = rb_node->rb_right;
        }
    }

    if (vma)
        vmacache_update(addr, vma);
    return vma;
}
性能优化:内核使用 vmacache(VMA 缓存)来加速查找。对于具有良好局部性的访问模式,缓存命中率很高,可以避免红黑树遍历的开销。

4.2.2 insert_vm_struct:插入新的 VMA

insert_vm_struct 函数负责将一个新的 VMA 插入到进程的地址空间中。它同时维护链表和红黑树的一致性。

/* mm/mmap.c */
int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
{
    struct vm_area_struct *prev;
    struct rb_node **rb_link, *rb_parent;

    /* 1. 查找插入位置(红黑树和链表) */
    if (find_vma_links(mm, vma->vm_start, vma->vm_end,
                       &prev, &rb_link, &rb_parent))
        return -ENOMEM;

    /* 2. 将 VMA 插入链表 */
    if ((vma->vm_next = prev ? prev->vm_next : mm->mmap))
        vma->vm_next->vm_prev = vma;
    if (prev)
        prev->vm_next = vma;
    else
        mm->mmap = vma;
    vma->vm_prev = prev;

    /* 3. 将 VMA 插入红黑树 */
    rb_link_node(&vma->vm_rb, rb_parent, rb_link);
    rb_insert_color(&vma->vm_rb, &mm->mm_rb);

    return 0;
}
VMA 插入流程:insert_vm_struct 步骤 1:查找插入位置 find_vma_links() 步骤 2:插入链表 更新 vm_next/vm_prev 步骤 3:插入红黑树 rb_link_node() 插入后的数据结构状态 双向链表: VMA 1 ↔ VMA 2 (新) ↔ VMA 3 ↔ VMA 4 红黑树: VMA 3 (根) VMA 1 VMA 4 VMA 2 (新)

4.3 VMA 与页表的关联

VMA 是进程地址空间的逻辑描述,而页表是 MMU 实际使用的硬件数据结构。两者通过缺页异常(page fault)机制紧密关联。

4.3.1 关联机制

组件 作用 关联方式
VMA 描述虚拟地址区间的属性(权限、映射类型) 提供缺页处理所需的上下文信息
页表 实现虚拟地址到物理地址的转换 由缺页异常处理程序填充
缺页异常 当访问的虚拟地址未建立页表映射时触发 通过 find_vma 查找对应的 VMA
vm_ops->fault 文件映射 VMA 的缺页回调函数 负责读取文件内容到物理页面

4.3.2 缺页处理流程

当 CPU 访问一个尚未建立页表映射的虚拟地址时,MMU 触发缺页异常,内核执行以下流程:

  1. 地址检查:调用 find_vma 查找地址所属的 VMA。
  2. 权限验证:检查 VMA 的 vm_flags 是否允许当前操作(读/写/执行)。
  3. 物理页面分配:通过伙伴系统分配一个物理页面。
  4. 页表填充:将物理页框号写入页表项,设置权限位。
  5. TLB 刷新:使 TLB 中对应的缓存项失效。
VMA 与页表关联:缺页异常处理流程 缺页异常触发 1. find_vma() 查找 VMA 获取地址区间和权限信息 2. 权限验证 检查 vm_flags 是否允许访问 3. 分配物理页面 (伙伴系统) alloc_page() / alloc_pages() 4. 填充页表项 设置 PTE 的物理页框号和权限位 5. TLB 刷新 flush_tlb_page()

4.3.3 文件映射与匿名映射

VMA 根据映射类型分为文件映射和匿名映射,两者的缺页处理方式不同:

特性 文件映射 VMA 匿名映射 VMA
vm_file 指向映射的文件 NULL
缺页处理 调用 vm_ops->fault 从文件读取数据 分配零填充的物理页面
回写 脏页可写回文件 不需要回写(交换分区除外)
典型场景 可执行文件、共享库、mmap 文件 堆、栈、匿名 mmap
关键概念:VMA 与页表之间没有直接的指针关联。VMA 是软件层面的管理结构,页表是硬件使用的转换结构。两者通过缺页异常处理程序在运行时动态建立联系。当进程访问一个地址时,MMU 首先查询页表;如果页表项无效,则触发缺页异常,内核使用 VMA 提供的信息来决定如何填充页表。

4.4 本章小结

  • VMA 数据结构:struct vm_area_struct 包含地址区间、权限标志、链表和红黑树节点,是进程地址空间管理的基本单元。
  • VMA 操作函数:find_vma 通过红黑树和缓存实现高效查找;insert_vm_struct 同时维护链表和红黑树的一致性。
  • VMA 与页表关联:通过缺页异常机制动态建立,VMA 提供上下文信息,页表实现地址转换。
  • 映射类型:文件映射和匿名映射在缺页处理方式上存在本质区别。