内存管理-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) 收藏 举报
浙公网安备 33010602011771号