内存管理-63-VMA的创建与销毁

基于msm-5.4

一、VMA创建路径汇总

1. mmap()系统调用-用户态请求映射

syscall_mmap_pgoff()                              [fs/exec.c]
    ↓
ksys_mmap_pgoff()                                 [mm/mmap.c]
    ↓
vm_mmap_pgoff()
    ↓
do_mmap()                                         [mm/mmap.c]
    ├─ get_unmapped_area()                        /* 查找可用地址 */
    ├─ mmap_region()                              [CORE VMA CREATE]
    │   ├─ vma_merge()                           /* 尝试合并相邻 VMA */
    │   │   └─ __vma_adjust()
    │   │       ├─ anon_vma_lock_write()
    │   │       └─ anon_vma_interval_tree_pre/post_update_vma()
    │   │
    │   ├─ vm_area_alloc(mm)                     /* ★ 分配新 VMA 对象 */
    │   │   └─ kmem_cache_alloc(vm_area_cachep, GFP_KERNEL)
    │   │
    │   ├─ vma_set_anonymous(vma)                /* 标记为匿名或文件映射 */
    │   │
    │   ├─ vma_link(mm, vma, prev, rb_link, rb_parent)  /* ★ 链接到 mm_struct */
    │   │   ├─ __vma_link_list()                /* 链接到链表 */
    │   │   ├─ __vma_link_rb()                  /* 链接到红黑树 */
    │   │   │   └─ vma_rb_insert()
    │   │   │       └─ rb_insert_augmented()    /* 红黑树插入 */
    │   │   ├─ __vma_link_file()                /* 链接到文件映射树 */
    │   │   │   └─ vma_interval_tree_insert()
    │   │   └─ mm->map_count++
    │   │
    │   ├─ call_mmap(file, vma)                 /* 驱动/文件系统回调 */
    │   ├─ vm_stat_account()                    /* 统计虚拟内存使用 */
    │   └─ vma_set_page_prot()
    │
    └─ mm_populate()                             /* 预加载页面(可选) */

关键函数:

/* mmap_region() - 创建新 VMA 的核心函数 */
unsigned long mmap_region(struct file *file, unsigned long addr,
        unsigned long len, vm_flags_t vm_flags, unsigned long pgoff, struct list_head *uf)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma, *prev;
    
    //检查内存限制
    if (!may_expand_vm(mm, vm_flags, len >> PAGE_SHIFT))
        return -ENOMEM;
    
    //清除旧VMA(如有重叠)
    while (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {
        if (do_munmap(mm, addr, len, uf))
            return -ENOMEM;
    }
    
    //尝试合并相邻 VMA
    vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);
    if (vma)
        goto out;  /* 成功合并,跳过分配 */
    
    //分配新VMA
    vma = vm_area_alloc(mm);  // ★★★ 核心分配点
    if (!vma)
        return -ENOMEM;
    
    vma->vm_start = addr;
    vma->vm_end = addr + len;
    vma->vm_flags = vm_flags;
    vma->vm_page_prot = vm_get_page_prot(vm_flags);
    vma->vm_pgoff = pgoff;
    
    //文件映射处理
    if (file) {
        vma->vm_file = get_file(file);
        //驱动可能改变 vma->vm_file
        call_mmap(file, vma);
    } else if (vm_flags & VM_SHARED) {
        shmem_zero_setup(vma); //shmem共享映射
    } else {
        vma_set_anonymous(vma); //匿名映射
    }
    
    //链接到进程地址空间, ★★★ 核心链接点
    vma_link(mm, vma, prev, rb_link, rb_parent);
    
    vm_stat_account(mm, vma->vm_flags, len >> PAGE_SHIFT);
    perf_event_mmap(vma);
    
out:
    return addr;
}


2. fork()路径-进程复制时的VMA继承

fork() / clone() / vfork()                        [kernel/fork.c]
    ↓
_do_fork()
    ↓
copy_process()
    ├─ copy_mm()                                  [kernel/fork.c]
    │   └─ dup_mm(tsk, current->mm)
    │       ├─ allocate_mm()
    │       ├─ mm_init()                        /* 初始化新 mm_struct */
    │       └─ dup_mmap(mm, oldmm)              [CORE FORK PATH]
    │           └─ for each VMA in oldmm->mmap:
    │               ├─ vm_area_dup(mpnt)         /* ★ 复制 VMA 结构 */
    │               │   └─ kmem_cache_alloc(vm_area_cachep, GFP_KERNEL)
    │               │   └─ INIT_LIST_HEAD(&new->anon_vma_chain)
    │               │
    │               ├─ vma_dup_policy()          /* 复制 NUMA 策略 */
    │               ├─ anon_vma_fork(tmp, mpnt)  /* 设置反向映射 */
    │               ├─ copy_page_range()         /* 复制页表(fork 的昂贵部分) */
    │               │
    │               ├─ __vma_link_rb()           /* ★ 链接到新 mm_struct 的红黑树 */
    │               ├─ vma_interval_tree_insert_after()  /* 文件映射链接 */
    │               └─ mm->map_count++
    │
    └─ (其他进程初始化...)

关键 fork 时 VMA 操作:

/* dup_mmap - 在 fork 时复制整个 VMA 链 */
static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
{
    struct vm_area_struct *mpnt, *tmp, *prev, **pprev;
    struct rb_node **rb_link, *rb_parent;
    
    for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
        //跳过 VM_DONTCOPY 的 VMA(如 VDSO)
        if (mpnt->vm_flags & VM_DONTCOPY) {
            vm_stat_account(mm, mpnt->vm_flags, -vma_pages(mpnt));
            continue;
        }
        
        //内存检查和计费
        if (mpnt->vm_flags & VM_ACCOUNT) {
            if (security_vm_enough_memory_mm(oldmm, vma_pages(mpnt)))
                goto fail_nomem;
        }
        
        //复制 VMA 结构体
        tmp = vm_area_dup(mpnt);  // ★ 分配新 VMA
        if (!tmp) goto fail_nomem;
        
        //复制 NUMA 策略(空实现)
        if (vma_dup_policy(mpnt, tmp))
            goto fail_nomem_policy;
        
        tmp->vm_mm = mm;
        
        //关键:fork 后的 anon_vma 链接可能在子进程有不同的树结构
        if (tmp->vm_flags & VM_WIPEONFORK) {
            /* VM_WIPEONFORK:在 fork 后清除此 VMA 的内容 */
            tmp->anon_vma = NULL;
            if (anon_vma_prepare(tmp))
                goto fail_nomem_anon_vma_fork;
        } else if (anon_vma_fork(tmp, mpnt)) {
            /* 正常情况:继承父进程的 anon_vma 树 */
            goto fail_nomem_anon_vma_fork;
        }
        
        //清除写时复制后会改变的标志
        tmp->vm_flags &= ~(VM_LOCKED | VM_LOCKONFAULT);
        tmp->vm_next = tmp->vm_prev = NULL;
        
        //处理文件映射
        if (tmp->vm_file) {
            struct inode *inode = file_inode(tmp->vm_file);
            struct address_space *mapping = tmp->vm_file->f_mapping;
            
            get_file(tmp->vm_file);
            if (tmp->vm_flags & VM_DENYWRITE)
                atomic_dec(&inode->i_writecount);
            i_mmap_lock_write(mapping);
            if (tmp->vm_flags & VM_SHARED)
                atomic_inc(&mapping->i_mmap_writable);
            
            // ★ 在文件映射中插入新 VMA
            vma_interval_tree_insert_after(tmp, mpnt, &mapping->i_mmap);
            
            i_mmap_unlock_write(mapping);
        }
        
        // 链接到新的 mm_struct
        *pprev = tmp;
        pprev = &tmp->vm_next;
        tmp->vm_prev = prev;
        prev = tmp;
        
        __vma_link_rb(mm, tmp, rb_link, rb_parent);  // ★ 红黑树插入
        rb_link = &tmp->vm_rb.rb_right;
        rb_parent = &tmp->vm_rb;
        
        mm->map_count++;
        
        //复制页表(最耗时)
        if (!(tmp->vm_flags & VM_WIPEONFORK))
            if (copy_page_range(mm, oldmm, mpnt))
                goto out;  //错误处理
        
        //驱动/文件系统的 open 回调
        if (tmp->vm_ops && tmp->vm_ops->open)
            tmp->vm_ops->open(tmp);
    }
    
    return 0;  // 成功
}


3. brk()路径-堆段扩展

brk(addr)                               [kernel/fork.c]
    ↓
SYSCALL_DEFINE1(brk, ...)
    ├─ find_vma(mm, oldbrk)
    └─ do_brk_flags()                   [mm/mmap.c CORE]
        ├─ find_vma_links()             /* 确定插入位置 */
        ├─ do_munmap()                  /* 清除重叠的 VMA(可选) */
        ├─ may_expand_vm()              /* 检查限制 */
        ├─ vma_merge()                  /* 尝试与相邻 VMA 合并 */
        │   └─ __vma_adjust()
        │
        └─ vm_area_alloc(mm)            /* ★ 如果不能合并则分配新 VMA */
            └─ vma_link()               /* ★ 链接 */
                ├─ __vma_link_list()
                ├─ __vma_link_rb()
                └─ mm->map_count++

brk 实现特点:
尝试合并以减少 VMA 数量(堆通常是连续的); - 匿名映射,标记为 `VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT`; 调用 `vma_set_anonymous()` 标记为匿名。


4. execve()路径-程序替换时的堆/栈VMA创建

execve()                                [fs/exec.c]
    ↓
do_execve()
    ↓
do_execveat_common()
    ↓
__do_execve_file()
    ├─ unshare_files()                  /* 复制文件描述符表 */
    ├─ bprm_mm_init(bprm)               [CORE EXEC]
    │   └─ mm_alloc()                   /* 为新程序分配新 mm_struct */
    │       ├─ allocate_mm()
    │       └─ mm_init()
    │           └─ arch_pick_mmap_layout()  /* 初始化 mmap 布局 */
    │
    └─ __bprm_mm_init(bprm)
        ├─ vm_area_alloc(mm)            /* ★ 为堆栈创建临时 VMA */
        │   └─ vma_set_anonymous()
        │
        ├─ down_write(&mm->mmap_sem)
        ├─ vma->vm_start = vma->vm_end - PAGE_SIZE;     /* 堆栈虚拟地址 */
        ├─ vma->vm_flags = VM_SOFTDIRTY | VM_STACK_FLAGS | VM_STACK_INCOMPLETE_SETUP;
        ├─ insert_vm_struct(mm, vma)    /* ★ 临时堆栈 VMA 插入 */
        │   ├─ find_vma_links()
        │   ├─ __vma_link()
        │   │   ├─ __vma_link_list()
        │   │   ├─ __vma_link_rb()      /* 红黑树初始插入 */
        │   └─ mm->map_count++
        │
        ├─ up_write(&mm->mmap_sem)
        └─ (准备参数/环境变量到堆栈...)

    ├─ prepare_arg_pages(bprm, argv, envp)  /* 将参数/环境变量复制到堆栈 */
    ├─ search_binary_handler(bprm)          /* 装载 ELF/Mach-O 等 */
    │   └─ fmt->load_binary(bprm)           /* 执行程序格式装载程序 */
    │       ├─ load_elf_binary()            /* ELF 装载器示例 */
    │       │   ├─ setup_new_mm()           /* 旧 mm 替换为新 mm */
    │       │   ├─ elf_map()                /* 映射 ELF 段 */
    │       │   │   └─ vm_mmap_pgoff()      /* ← 递归调用 mmap_region */
    │       │   │       └─ mmap_region()    /* ★ 创建代码/数据段的 VMA */
    │       │   └─ (加载 VDSO / 解释器等...)
    │       │
    │       ├─ flush_old_exec(bprm)        /* 清理旧进程上下文 */
    │       │   ├─ de_thread()             /* 线程组隐除 */
    │       │   └─ exec_mmap(bprm->mm)     /* ★ 用新 mm_struct 替换进程 mm */
    │       │       ├─ mm_release()
    │       │       ├─ task->mm = bprm->mm
    │       │       ├─ activate_mm()
    │       │       └─ mmput(old_mm)       /* 释放旧 mm 引用 */
    │       │
    │       ├─ setup_arg_pages(bprm)       /* 最终化堆栈 */
    │       │   ├─ shift_arg_pages()       /* 移动堆栈到最终位置 */
    │       │   ├─ mprotect_fixup()        /* 设置堆栈权限位 */
    │       │   ├─ vma->vm_flags &= ~VM_STACK_INCOMPLETE_SETUP
    │       │   └─ expand_stack()          /* 预热堆栈空间 */
    │       │
    │       └─ start_thread()              /* 建立 CPU 上下文跳转 */
    │
    └─ free_bprm()                          /* 清理执行环境 */

execve 核心VMA变化:新进程分配全新 `mm_struct`; 为临时堆栈创建初始 VMA; 使用 `elf_map()` --> `vm_mmap_pgoff()` 创建代码/数据段的 VMA; `exec_mmap()` 用新 mm 替换进程; 旧 mm 的所有 VMA 在 exit_mmap() 中销毁。


5. stack扩展路径-栈段动态增长

页面错误或 expand_stack() 显式调用         [mm/mmap.c]
    ↓
expand_downwards(vma, address)          /* 栈向下增长 */
    ├─ 或 expand_upwards(vma, address)  /* 栈向上增长 */
    ├─ acct_stack_growth()              /* 检查栈大小限制 */
    ├─ __vma_adjust()                   /* ★ 扩展现有 VMA,不分配新的 */
    │   ├─ anon_vma_interval_tree_pre_update_vma()
    │   ├─ vma->vm_start = new_start    /* 或 vm_end 改变 */
    │   ├─ vma_gap_update()
    │   ├─ anon_vma_interval_tree_post_update_vma()
    │   └─ mm->map_count 不变            /* 重点:不添加新 VMA */
    │
    └─ (无新 VMA 分配,只扩展现有)

特点:栈扩展不创建新 VMA,而是调整现有 VMA 的边界。


6. mremap()路径-内存区间重新映射

mremap(addr, old_len, new_len, ...)     [mm/mremap.c]
    ↓
SYSCALL_DEFINE5(mremap, ...)
    ├─ Find source vma
    ├─ copy_vma()                       /* 创建新 VMA 副本 */
    │   ├─ vma_merge()                  /* 尝试合并 */
    │   │   └─ __vma_adjust()
    │   │
    │   └─ vma_dup()                    /* ★ 复制源 VMA 结构体 */
    │       ├─ vm_area_dup()
    │       │   └─ kmem_cache_alloc(vm_area_cachep, GFP_KERNEL)
    │       │
    │       ├─ vma_dup_policy()         /* 复制 NUMA 策略 */
    │       ├─ anon_vma_clone()         /* 设置反向映射链 */
    │       ├─ vma_link()               /* ★ 链接到新位置 */
    │       │   ├─ __vma_link_list()
    │       │   ├─ __vma_link_rb()      /* 红黑树插入 */
    │       │   └─ mm->map_count++
    │       │
    │       └─ move_page_tables()       /* 迁移页表(性能优化) */
    │
    └─ __do_munmap(old_start, old_len)  /* 销毁原 VMA */
        └─ (详见销毁路径)


7. userfaultfd()路径-用户态故障处理

ioctl(uffd, UFFDIO_REGISTER, ...)       [fs/userfaultfd.c]
    ├─ register_userfaultfd_range()
    └─ 没有创建 VMA,只标记现有 VMA 和设置陷阱处理。VMA 本身由 mmap() 提前创建。


8. 特殊VMA创建:VDSO、VVAR、VSYSCALL

/* VDSO - Virtual Dynamic Shared Object */
arch_setup_additional_pages()           [arch/x86/entry/vdso/vma.c]
    └─ _install_special_mapping()       [mm/mmap.c]
        ├─ __install_special_mapping()
        │   ├─ vm_area_alloc(mm)        /* ★ 分配 VMA */
        │   ├─ vma->vm_flags = vm_flags | VM_DONTEXPAND | VM_SOFTDIRTY
        │   ├─ vma->vm_ops = &special_mapping_vmops
        │   ├─ vma->vm_private_data = spec
        │   └─ insert_vm_struct(mm, vma)  /* ★ 链接 */
        │
        └─ 类似的处理 vvar, vsyscall 等


二、VMA销毁路径汇总

1. munmap()系统调用-显式解除映射

munmap(addr, len)                       [mm/mmap.c]
    ↓
SYSCALL_DEFINE2(munmap, ...)
    ↓
vm_munmap(start, len)
    ├─ down_write(&mm->mmap_sem)
    └─ __do_munmap(mm, start, len, &uf, true)  [CORE UNMAP]
        ├─── split_vma(mm, vma, start, 0)     /* 分裂 VMA 前缀 */
        │   ├─ __split_vma()
        │   │   ├─ vm_area_dup()                /* 分配前缀 VMA */
        │   │   ├─ 前缀.vm_end = split_point
        │   │   ├─ 后缀.vm_start = split_point
        │   │   ├─ anon_vma_clone()             /* 设置前缀反向映射 */
        │   │   └─ __insert_vm_struct()         /* 插入前缀 VMA */
        │   │       ├─ find_vma_links()
        │   │       └─ __vma_link(mm, new, prev, rb_link, rb_parent)
        │   │           ├─ __vma_link_list(mm, new, prev, rb_parent)
        │   │           ├─ __vma_link_rb(mm, new, rb_link, rb_parent)
        │   │           │   └─ vma_rb_insert(new, &mm->mm_rb)
        │   │           │       └─ rb_insert_augmented()
        │   │           └─ mm->map_count++
        │
        ├─── detach_vmas_to_be_unmapped(mm, vma, prev, end)  [CORE]
        │   ├─ 找到 [start, end) 范围内的所有 VMA
        │   └─ 对每个要删除的 VMA vma:
        │       ├─ vma_rb_erase(vma, &mm->mm_rb)        /* ★ 从红黑树删除 */
        │       ├─ mm->map_count--
        │       ├─ unlink_file_vma(vma)                 /* 从文件映射树删除 */
        │       └─ (从链表中隐除,准备销毁)
        │
        ├─── unmap_region(mm, vma, prev, start, end)  [PAGE TABLE UNMAP]
        │   ├─ lru_add_drain()                  /* 刷新 LRU 缓存 */
        │   ├─ tlb_gather_mmu(&tlb, mm, start, end)
        │   ├─ unmap_vmas(&tlb, vma, start, end)        /* ★ 删除页表项 */
        │   │   ├─ for each pte in vma:
        │   │   │   ├─ ptep_get_and_clear(pte)          /* 清除 PTE */
        │   │   │   └─ mark_page_accessed()             /* 更新统计 */
        │   │   └─ page_remove_rmap(page)               /* 减 mapcount */
        │   ├─ free_pgtables(&tlb, vma, start, end)     /* 释放页表页 */
        │   │   ├─ 对每个级别 (PUD/PMD/PTE)
        │   │   │   ├─ pgtable_t ptable = ...
        │   │   │   └─ pgtable_pte_page(ptable) 加入 tlb.freed_tables
        │   │   └─ free_table_pages()
        │   ├─ tlb_finish_mmu(&tlb, start, end)
        │   └─ flush_tlb_range()                        /* TLB 刷新 */
        │
        ├─── remove_vma_list(mm, vma)  [CORE VMA DESTROY]
        │   └─ 对要删除的每个 VMA:
        │       ├─ update_hiwater_vm()          /* 更新水位线统计 */
        │       ├─ long nrpages = vma_pages(vma)
        │       ├─ if (vma->vm_flags & VM_ACCOUNT)
        │       │   nr_accounted += nrpages
        │       ├─ vm_stat_account(mm, vma->vm_flags, -nrpages) /* 更新统计 */
        │       ├─ vma = remove_vma(vma)        /* ★ 销毁单个 VMA */
        │       │   ├─ if (vma->vm_ops && vma->vm_ops->close)
        │       │   │   vma->vm_ops->close(vma)  /* 驱动/FS 清理 */
        │       │   ├─ if (vma->vm_file)
        │       │   │   ├─ unlink_file_vma(vma)  /* 从文件映射树删除 */
        │       │   │   └─ fput(vma->vm_file)    /* 释放文件引用 */
        │       │   ├─ mpol_put(vma_policy(vma)) /* 释放 NUMA 策略 */
        │       │   ├─ vm_area_free(vma)         /* ★★★ 释放 VMA 对象 */
        │       │   │   └─ kmem_cache_free(vm_area_cachep, vma)
        │       │   └─ return vma->vm_next
        │       │
        │       └─ mm->map_count--
        │
        └─ vm_unacct_memory(nr_accounted)       /* 回收资源计费 */

munmap 的关键三步销毁:
(1) 分裂 VMA:仅删除中间范围,保留前后缀。
(2) 分离并卸载页表:从红黑树、文件树移除, 清除 PTE。
(3) 释放 VMA 对象:调用驱动回调、释放到内存池。


2. exit()/exit_group()路径-进程终止时的销毁全部VMA

exit(int)                               [kernel/exit.c]
    ↓
do_exit()
    ├─ exit_mm()
    │   └─ mmput(current->mm)
    │       ├─ atomic_dec_and_test(&mm->mm_users)  /* 减少用户计数 */
    │       └─ __mmput()
    │           ├─ uprobe_clear_state(mm)         /* 清除 uprobe */
    │           ├─ exit_aio(mm)                    /* 清除异步 I/O */
    │           ├─ ksm_exit(mm)                    /* KSM 清理 */
    │           ├─ khugepaged_exit(mm)
    │           └─ exit_mmap(mm)                  [CORE MMAP CLEANUP]
    │               ├─ mmu_notifier_release(mm)  /* 通知 mmunotifier 用户 */
    │               ├─ arch_exit_mmap(mm)        /* 架构特定清理 */
    │               ├─ tlb_gather_mmu(&tlb, mm, 0, -1)  /* 收集所有 TLB 操作 */
    │               ├─ unmap_vmas(&tlb, vma, 0, -1)     /* 卸载所有 VMA 页表 */
    │               │   ├─ for each VMA in mm->mmap:
    │               │   │   ├─ unmap_page_range()
    │               │   │   │   └─ 清除范围内所有 PTE
    │               │   │   └─ page_remove_rmap()
    │               │   └─(页表项全部清除)
    │               │
    │               ├─ free_pgtables(&tlb, vma, FIRST_USER_ADDRESS,
    │               │                USER_PGTABLES_CEILING)
    │               │   └─ 释放所有页表页到伙伴分配器
    │               │
    │               ├─ tlb_finish_mmu(&tlb, 0, -1)    /* 完成 TLB 处理 */
    │               │   └─ tlb_flush_range()
    │               │
    │               └─ while (vma) {
    │                   ├─ 逐个 VMA 销毁
    │                   ├─ if (vma->vm_flags & VM_ACCOUNT)
    │                   │   nr_accounted += vma_pages(vma)
    │                   ├─ vma = remove_vma(vma)  /* ★ 销毁 */
    │                   │   ├─ vma->vm_ops->close()
    │                   │   ├─ fput(vma->vm_file)
    │                   │   ├─ vm_area_free(vma)   /* ★ 释放到 slab */
    │                   │   └─ return vma->vm_next
    │                   └─ }
    │
    ├─ mm_put_huge_zero_page(mm)
    ├─ set_mm_exe_file(mm, NULL)
    ├─ (mmlist 清理...)
    └─ mmdrop(mm)                       /* 最终 mm 引用计数归零时释放 mm 本身 */

exit_mmap 的关键功能:
(1) 一次性卸载所有页表(批量 TLB 刷新,高效)
(2) 依次调用每个 VMA 的 `vm_ops->close()`
(3) 释放每个 VMA 对象的 slab 缓存
(4) 最后释放 mm_struct 本身


3. exec()路径中的旧VMA销毁

execve() - at flush_old_exec()
    └─ exec_mmap(bprm->mm)
        ├─ old_mm = current->mm
        ├─ current->mm = bprm->mm  /* ★ 指针切换 */
        ├─ up_read(&old_mm->mmap_sem)
        └─ mmput(old_mm)           /* ★ 引发旧 mm 的销毁路径 */
            └─ 如果是最后一个用户:
                └─ __mmput()
                    └─ exit_mmap(old_mm)  /* 销毁旧进程的所有 VMA */
                        ├─ unmap_vmas()
                        ├─ free_pgtables()
                        └─ remove_vma() × N


4. mmap_region 内的失败回滚

mmap_region() - on error:
    └─ unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end)
        └─ (同 munmap unmap_region 的逻辑)
        
或在 vma_merge 后失败:
    └─ __vma_adjust() 的错误处理
        └─ VMA 恢复或删除


5. brk 回缩路径(堆收缩)

brk(new_addr) where new_addr < current_brk
    └─ mm->brk = new_addr
    └─ __do_munmap(mm, newbrk, oldbrk - newbrk, &uf, true)
        └─ (使用标准 munmap 销毁逻辑)


6. mremap失败回滚

mremap() - failure case:
    └─ 新 VMA 已创建但移动页表失败
        └─ do_munmap(old_start, old_len)  /* 销毁源 VMA */


三、VMA相关操作函数

1. VMA 对象的分配和初始化

/* kernel/fork.c */
struct vm_area_struct *vm_area_alloc(struct mm_struct *mm)
{
    struct vm_area_struct *vma;
    
    vma = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
    if (vma)
        /* 初始化,清0 VMA,将.vm_ops指向dummy的空函数集 */
        vma_init(vma, mm);
    return vma;
}

static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm)
{
    static const struct vm_operations_struct dummy_vm_ops = {};
    
    memset(vma, 0, sizeof(*vma));
    vma->vm_mm = mm;
    INIT_LIST_HEAD(&vma->anon_vma_chain);
    vma->vm_ops = &dummy_vm_ops;
    vma->vm_page_prot = vm_get_page_prot(0);
}

/* 直接值拷贝一份,然后只重新初始化了 anon_vma_chain 这个链表头*/
struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig)
{
    struct vm_area_struct *new = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
    if (new) {
        *new = *orig;  // 结构体浅复制
        INIT_LIST_HEAD(&new->anon_vma_chain);  //列表重新初始化
    }
    return new;
}

void vm_area_free(struct vm_area_struct *vma)
{
    kmem_cache_free(vm_area_cachep, vma);  //销毁 VMA 对象
}

vm_area_cachep 这个slab缓存是在 start_kernel() --> proc_caches_init() 中创建的,没有指定 ctor 构造函数。


2. VMA链接到mm_struct

/* mm/mmap.c */
void vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
    struct vm_area_struct *prev, struct rb_node **rb_link, struct rb_node *rb_parent)
{
    struct address_space *mapping = NULL;
    
    if (vma->vm_file)
        mapping = vma->vm_file->f_mapping;
        i_mmap_lock_write(mapping);  //文件映射树锁
    }
    
    __vma_link(mm, vma, prev, rb_link, rb_parent); //核心链接
    /* 链接到文件映射树, 插入 file->f_mapping->i_mmap 区间树 */
    __vma_link_file(vma);
    
    if (mapping)
        i_mmap_unlock_write(mapping);
    
    mm->map_count++;  //★ VMA 计数增加
    validate_mm(mm);
}

static void __vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
                      struct vm_area_struct *prev, struct rb_node **rb_link,
                      struct rb_node *rb_parent)
{
    __vma_link_list(mm, vma, prev, rb_parent);  //链表 vma->vm_prev/next 链接
    __vma_link_rb(mm, vma, rb_link, rb_parent); //红黑树链接
}


3. VMA从mm_struct解链

/* mm/mmap.c - detach_vmas_to_be_unmapped 的核心 */
static void detach_vmas_to_be_unmapped(struct mm_struct *mm, 
    struct vm_area_struct *vma, struct vm_area_struct *prev, unsigned long end)
{
    struct vm_area_struct **insertion_point;
    struct vm_area_struct *tail_vma = NULL;
    
    insertion_point = (prev ? &prev->vm_next : &mm->mmap);
    vma->vm_prev = NULL;
    
    do {
        vma_rb_erase(vma, &mm->mm_rb);  /* 从红黑树删除 */
        mm->map_count--;  /* VMA计数递减 */
        tail_vma = vma;
        vma = vma->vm_next;
    } while (vma && vma->vm_start < end);
    
    *insertion_point = vma;
    if (vma) {
        vma->vm_prev = prev;
        vma_gap_update(vma);
    } else {
        mm->highest_vm_end = prev ? vm_end_gap(prev) : 0;
    }
    
    tail_vma->vm_next = NULL;
    vmacache_invalidate(mm);  /* 让VMA cache失效*/
}


4. VMA销毁核心函数

/* mm/mmap.c */
static struct vm_area_struct *remove_vma(struct vm_area_struct *vma)
{
    struct vm_area_struct *next = vma->vm_next;
    
    might_sleep();
    if (vma->vm_ops && vma->vm_ops->close)
        vma->vm_ops->close(vma);  /* 驱动/FS 清理回调 */
    
    if (vma->vm_file)
        fput(vma->vm_file);  /* 释放文件引用 */
    
    mpol_put(vma_policy(vma));  /* 释放 NUMA 策略 */
    vm_area_free(vma); /* 释放 VMA 对象到 slab */
    
    return next;
}

/* 一次性销毁多个 VMA */
static void remove_vma_list(struct mm_struct *mm, struct vm_area_struct *vma)
{
    unsigned long nr_accounted = 0;
    
    update_hiwater_vm(mm);
    do {
        long nrpages = vma_pages(vma);
        
        if (vma->vm_flags & VM_ACCOUNT)
            nr_accounted += nrpages;
        vm_stat_account(mm, vma->vm_flags, -nrpages);
        vma = remove_vma(vma);  /* 逐个销毁 */
    } whilst (vma);
    
    vm_unacct_memory(nr_accounted);
    validate_mm(mm);
}


四、总结

1. VMA创建和销毁的统计:

------------------------------------------------------------------------------------------------
|    操作    |          创建路径          | VMA对象  | 是否创建新VMA |  合并可能  |  分裂可能  |
|------------|----------------------------|----------|---------------|------------|------------|
| mmap()     | mmap_region()              | 分配     | ✓ 通常        | ✓ 合并     | ✗ 不分裂   |
| fork()     | dup_mmap()                 | 复制每个 | ✓ 1:1         | ✗ 不合并   | ✗ 不分裂   |
| brk()      | do_brk_flags()             | 或合并   | ✓ 如果不合并  | ✓ 总是尝试 | ✗ 不分裂   |
| exec()     | __bprm_mm_init() + ELF装载 | 新mm     | ✓ 代码/数据段 | ✓ 可能     | ✗ 不分裂   |
| stack扩展  | expand_stack()             | 现有扩展 | ✗ 调整现有    | ✗ 无       | ✗ 无       |
| mremap()   | copy_vma()                 | 复制     | ✓ 新位置      | ✓ 合并可能 | ✓ 通常分裂 |
| VDSO映射   | _install_special_mapping() | 分配     | ✓ 1 个        | ✗ 不合并   | ✗ 不分裂   |
| munmap()   | __do_munmap()              | 销毁     | ✗ | ✓ 分裂    | ✓ 两边分裂 |            |
| exit()     | exit_mmap()                | 销毁全部 | ✗ | ✗         | ✗          |            |
| exec()替换 | exec_mmap()切换            | 销毁旧mm | ✗ | ✗         | ✗          |            |
------------------------------------------------------------------------------------------------


2. VMA创建的9大主要入口点:

(1) mmap() --> mmap_region() - 核心创建路径.
(2) fork() --> dup_mmap() - 进程 VMA 复制路径.
(3) brk() --> do_brk_flags() - 堆扩展路径
(4) execve() --> __bprm_mm_init() + ELF 装载 - 程序替换时的新VMA.
(5) mremap() --> copy_vma() - 虚拟地址重映射
(6) stack扩展 --> 调整现有VMA - 栈段动态增长
(7) VDSO 映射 --> _install_special_mapping() - 内核特殊区域
(8) 内核支持的特殊映射 --> 各架构实现
(9) userfaultfd - 用户态故障处理


3. VMA销毁的6大主要路径:

(1) munmap() --> __do_munmap() --> remove_vma_list() - 显式解除映射(分裂+销毁).
(2) process exit() --> exit_mmap() --> remove_vma() × N - 进程终止时全部销毁.
(3) execve()中旧mm销毁 --> mmput() --> exit_mmap() - 旧 mm 替换销毁.
(4) mremap失败 --> do_munmap()清理 - mmap/mremap 失败后清理
(5) mmap_region失败 --> unmap_region() + remove_vma()
(6) brk 回缩 - 堆收缩


4. VMA 的生命周期完全由 mm_struct 管理,创建时通过红黑树、链表、反向映射树多角度索引,销毁时使用批量 TLB 刷新和驱动回调进行高效清理。


5. VMA 在内存中的组织

(1) mm_struct->mmap 是VMA链表头,指向首个VMA对象。链表里面的VMA按地址升序####入队。
(2) 所有 vm_area_struct->vm_rb 组成 mm_struct->mm_rb 红黑树, 用于快速地址查询。
(3) mm_struct->mm_rb 红黑树还是带间隙感知的增强型红黑树(gap-aware augmented red-black tree),按 vm_start 排序 + rb_subtree_gap 优化,用于快速查找空闲地址范围的VMA。
(4) 反向映射树(for anonymous pages), 每个文件 + VMA对组成file的 address_space->i_mmap 树, 用于 page_referenced(); try_to_unmap(); 页面缓存失效化。

 

posted on 2026-04-15 13:50  Hello-World3  阅读(3)  评论(0)    收藏  举报

导航