前言
在Linux的虚拟内存管理中,虚拟内存区域(VMA) 就像是建筑工地上的施工蓝图,而将VMA正确链接到各种数据结构中的过程,则如同将这些蓝图精确归档到不同的档案系统中------有的按地址顺序排列,有的按文件关系组织,还有的专门管理匿名内存。这个过程不仅需要极高的精确性,还要保证在各种复杂场景下的高效运作。
想象一下,当进程请求一个新的内存映射时,内核需要:
- 在数十个现有VMA中找到精确的插入位置
- 同时维护链表、红黑树、优先级树、匿名VMA链表等多个数据结构
- 处理文件权限、共享映射、非线性映射等特殊情况
- 确保所有操作在并发环境下的安全性
本文将深入揭示Linux内核如何通过精巧的算法设计,在毫秒级时间内完成这些复杂的"内存拼图"操作,构建起进程虚拟地址空间的完整骨架
核心架构:四重链接系统
Linux内核为每个VMA维护着四个独立但相互关联的数据结构,形成一个高效的管理网络:
地址空间链表 - 顺序管理的基石
c
// 在VMA链表中插入新节点
if (prev) {
vma->vm_next = prev->vm_next; // 新节点指向后继
prev->vm_next = vma; // 前驱指向新节点
} else {
mm->mmap = vma; // 作为链表头
}
作用:保持VMA的地址顺序,支持全量遍历和连续性检查
红黑树 - 快速查找的引擎
c
rb_link_node(&vma->vm_rb, rb_parent, rb_link); // 初步链接
rb_insert_color(&vma->vm_rb, &mm->mm_rb); // 重新平衡
作用 :实现O(log n)的高效地址查找,支撑find_vma等核心操作
文件映射优先级树 - 共享管理的智慧
c
if (vma->vm_flags & VM_NONLINEAR)
vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear); // 非线性→链表
else
vma_prio_tree_insert(vma, &mapping->i_mmap); // 线性→优先级树
作用:管理文件映射关系,支持区间查询和冲突处理
匿名VMA链表 - 反向映射的纽带
c
list_add(&vma->anon_vma_node, &anon_vma->head); // 添加到匿名链表
作用:建立物理页到VMA的反向映射,支撑页面回收和迁移
智能处理:多场景自适应
冲突解决机制
当多个VMA映射相同的文件区间时:
c
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
{
// 根据现有节点的状态智能选择添加策略
if (!old->shared.vm_set.parent)
list_add(...); // 情况1:添加到现有链表
else if (old->shared.vm_set.head)
list_add_tail(...); // 情况2:添加到链表尾部
else {
// 情况3:创建新的链表关系
vma->shared.vm_set.head = old;
old->shared.vm_set.head = vma;
}
}
合并优化策略
当相邻VMA可以合并时:
c
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
{
if (!vma->anon_vma) {
vma->anon_vma = next->anon_vma; // 继承匿名VMA
list_add(&vma->anon_vma_node, &next->anon_vma_node);
}
list_del(&next->anon_vma_node); // 移除被合并的VMA
}
将新VMA插入到进程的地址空间管理中__insert_vm_struct
c
static void
__insert_vm_struct(struct mm_struct * mm, struct vm_area_struct * vma)
{
struct vm_area_struct * __vma, * prev;
struct rb_node ** rb_link, * rb_parent;
__vma = find_vma_prepare(mm, vma->vm_start,&prev, &rb_link, &rb_parent);
if (__vma && __vma->vm_start < vma->vm_end)
BUG();
__vma_link(mm, vma, prev, rb_link, rb_parent);
mm->map_count++;
}
函数代码分析
c
static void
__insert_vm_struct(struct mm_struct * mm, struct vm_area_struct * vma)
mm:内存管理结构vma:要插入的虚拟内存区域
变量声明
c
struct vm_area_struct * __vma, * prev;
struct rb_node ** rb_link, * rb_parent;
__vma:找到的现有VMA(可能为NULL)prev:前一个VMArb_link:红黑树中插入位置的指针rb_parent:红黑树中插入位置的父节点
查找插入位置
c
__vma = find_vma_prepare(mm, vma->vm_start,&prev, &rb_link, &rb_parent);
-
mm:内存管理结构 -
vma->vm_start:要插入VMA的起始地址 -
&prev:返回前一个VMA的指针 -
&rb_link:返回红黑树插入位置的指针 -
&rb_parent:返回红黑树插入位置的父节点 -
返回值 :第一个结束地址大于
vma->vm_start的VMA
重叠检查
c
if (__vma && __vma->vm_start < vma->vm_end)
BUG();
-
__vma不为NULL(存在现有VMA) -
现有VMA的起始地址小于要插入VMA的结束地址
-
如果检测到重叠,触发内核错误(内核崩溃)
链接VMA到数据结构
c
__vma_link(mm, vma, prev, rb_link, rb_parent);
- 使用
find_vma_prepare准备好的所有信息 - 将VMA实际插入到链表和红黑树中
更新映射计数
c
mm->map_count++;
- 增加内存管理结构的映射计数
- 作用:跟踪当前进程有多少个VMA
总结
主要功能:安全地将新VMA插入到进程的地址空间管理中
-
位置查找
- 找到新VMA在地址空间中的正确位置
- 准备链表和红黑树的插入信息
-
重叠检测
- 严格检查新VMA是否与现有VMA重叠
- 防止内存管理冲突
-
数据结构更新
- 将VMA插入到链表和红黑树中
- 维护数据结构的完整性
-
统计信息更新
- 更新进程的VMA映射计数
- 支持系统监控和限制检查
将VMA完整地链接到进程的所有内存管理数据结构中__vma_link/__vma_link_list/__vma_link_rb/__anon_vma_link
c
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_link_rb(mm, vma, rb_link, rb_parent);
__anon_vma_link(vma);
}
static inline void
__vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, struct rb_node *rb_parent)
{
if (prev) {
vma->vm_next = prev->vm_next;
prev->vm_next = vma;
} else {
mm->mmap = vma;
if (rb_parent)
vma->vm_next = rb_entry(rb_parent,
struct vm_area_struct, vm_rb);
else
vma->vm_next = NULL;
}
}
void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,
struct rb_node **rb_link, struct rb_node *rb_parent)
{
rb_link_node(&vma->vm_rb, rb_parent, rb_link);
rb_insert_color(&vma->vm_rb, &mm->mm_rb);
}
void __anon_vma_link(struct vm_area_struct *vma)
{
struct anon_vma *anon_vma = vma->anon_vma;
if (anon_vma) {
list_add(&vma->anon_vma_node, &anon_vma->head);
validate_anon_vma(vma);
}
}
__vma_link 主函数
c
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_link_rb(mm, vma, rb_link, rb_parent);
__anon_vma_link(vma);
}
- 作用:协调三个子函数,完成VMA的完整链接
- 链接到VMA链表
- 链接到红黑树
- 链接到匿名VMA链表
__vma_link_list 函数
情况1:有前驱节点
c
if (prev) {
vma->vm_next = prev->vm_next;
prev->vm_next = vma;
}
- 插入前:
prev → next - 插入后:
prev → vma → next
情况2:无前驱节点(插入到链表头)
c
} else {
mm->mmap = vma;
if (rb_parent)
vma->vm_next = rb_entry(rb_parent,
struct vm_area_struct, vm_rb);
else
vma->vm_next = NULL;
}
mm->mmap = vma:设置新的链表头- 如果有红黑树父节点,通过它找到下一个VMA
- 否则设置为NULL(只有一个VMA)
__vma_link_rb 函数
链接节点到红黑树
c
rb_link_node(&vma->vm_rb, rb_parent, rb_link);
-
&vma->vm_rb:要插入的VMA的红黑树节点 -
rb_parent:父节点 -
rb_link:插入位置的指针 -
将节点初步链接到红黑树中
重新平衡红黑树
c
rb_insert_color(&vma->vm_rb, &mm->mm_rb);
- 执行红黑树的重新平衡操作,维护树的平衡性质
__anon_vma_link 函数
获取匿名VMA
c
struct anon_vma *anon_vma = vma->anon_vma;
- 从VMA中获取关联的匿名VMA管理结构
链接到匿名VMA链表
c
if (anon_vma) {
list_add(&vma->anon_vma_node, &anon_vma->head);
validate_anon_vma(vma);
}
-
只有当VMA有关联的匿名VMA时才处理
-
list_add:将VMA添加到匿名VMA链表的头部 -
validate_anon_vma:调试模式下验证链接正确性
设计原理分析
为什么需要三个独立的链接操作?
链表的作用
- 顺序遍历:支持O(n)的全量遍历
- 简单性:易于理解和调试
- 地址连续性:天然保持地址空间顺序
红黑树的作用
- 快速查找:支持O(log n)的地址查找
- 高效管理:支持区间查询和范围操作
匿名VMA链表的作用
- 反向映射:支持从物理页找到所有映射的VMA
- 匿名页管理:专门管理没有文件后端的页
总结
主要功能:将VMA完整地链接到进程的所有内存管理数据结构中
将VMA链接到关联文件的映射结构中__vma_link_file
c
static inline void __vma_link_file(struct vm_area_struct *vma)
{
struct file * file;
file = vma->vm_file;
if (file) {
struct address_space *mapping = file->f_mapping;
if (vma->vm_flags & VM_DENYWRITE)
atomic_dec(&file->f_dentry->d_inode->i_writecount);
if (vma->vm_flags & VM_SHARED)
mapping->i_mmap_writable++;
flush_dcache_mmap_lock(mapping);
if (unlikely(vma->vm_flags & VM_NONLINEAR))
vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);
else
vma_prio_tree_insert(vma, &mapping->i_mmap);
flush_dcache_mmap_unlock(mapping);
}
}
static inline void vma_nonlinear_insert(struct vm_area_struct *vma,
struct list_head *list)
{
vma->shared.vm_set.parent = NULL;
list_add_tail(&vma->shared.vm_set.list, list);
}
void vma_prio_tree_insert(struct vm_area_struct *vma,
struct prio_tree_root *root)
{
struct prio_tree_node *ptr;
struct vm_area_struct *old;
vma->shared.vm_set.head = NULL;
ptr = prio_tree_insert(root, &vma->shared.prio_tree_node);
if (ptr != &vma->shared.prio_tree_node) {
old = prio_tree_entry(ptr, struct vm_area_struct,
shared.prio_tree_node);
vma_prio_tree_add(vma, old);
}
}
__vma_link_file 函数
获取文件指针
c
struct file * file;
file = vma->vm_file;
-
从VMA中获取关联的文件指针
-
只有当VMA有关联文件时才进行处理(文件映射)
文件权限处理
c
struct address_space *mapping = file->f_mapping;
- 获取文件的页缓存管理结构
c
if (vma->vm_flags & VM_DENYWRITE)
atomic_dec(&file->f_dentry->d_inode->i_writecount);
VM_DENYWRITE:表示这个映射阻止其他进程以写模式打开文件atomic_dec:减少文件的写引用计数- 效果:阻止其他进程修改文件
c
if (vma->vm_flags & VM_SHARED)
mapping->i_mmap_writable++;
VM_SHARED:共享映射,修改会写回文件i_mmap_writable++:增加可写映射计数- 用途:跟踪有多少个可写的共享映射
映射结构更新
c
if (unlikely(vma->vm_flags & VM_NONLINEAR))
vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);
- 条件 :
unlikely提示非线性映射是罕见情况 - 处理:调用非线性映射插入函数
c
else
vma_prio_tree_insert(vma, &mapping->i_mmap);
- 默认情况:大多数映射是线性的,使用优先级树插入
vma_nonlinear_insert 函数
c
static inline void vma_nonlinear_insert(struct vm_area_struct *vma,
struct list_head *list)
- 作用:将VMA插入非线性映射链表
c
vma->shared.vm_set.parent = NULL;
- 初始化:非线性映射没有父节点概念
c
list_add_tail(&vma->shared.vm_set.list, list);
- 操作:将VMA添加到链表尾部
vma_prio_tree_insert 函数
c
void vma_prio_tree_insert(struct vm_area_struct *vma,
struct prio_tree_root *root)
- 作用:将VMA插入优先级树
c
vma->shared.vm_set.head = NULL;
- 准备:清空共享结构的头指针
c
ptr = prio_tree_insert(root, &vma->shared.prio_tree_node);
- 将VMA的优先级树节点插入到树中
- 如果插入成功返回自身,如果冲突返回已存在的节点
处理插入冲突
c
if (ptr != &vma->shared.prio_tree_node) {
old = prio_tree_entry(ptr, struct vm_area_struct,
shared.prio_tree_node);
vma_prio_tree_add(vma, old);
}
-
如果插入时发现冲突(相同区间已存在)
-
获取已存在的VMA
-
调用
vma_prio_tree_add将新VMA添加到冲突链表中
功能总结
主要功能:将VMA正确链接到文件的映射管理结构中
-
权限管理
- 处理
VM_DENYWRITE标志,控制文件写访问 - 跟踪
VM_SHARED可写映射数量
- 处理
-
映射结构维护
- 线性映射:插入优先级树,支持快速区间查找
- 非线性映射:插入链表,管理随机映射关系
-
并发控制
- 使用映射锁保护数据结构
- 确保缓存一致性
-
冲突处理
- 处理优先级树插入冲突
- 维护共享映射的正确关系
将映射相同文件区间的新VMA添加到现有VMA的链表中vma_prio_tree_add
c
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
{
/* Leave these BUG_ONs till prio_tree patch stabilizes */
BUG_ON(RADIX_INDEX(vma) != RADIX_INDEX(old));
BUG_ON(HEAP_INDEX(vma) != HEAP_INDEX(old));
vma->shared.vm_set.head = NULL;
vma->shared.vm_set.parent = NULL;
if (!old->shared.vm_set.parent)
list_add(&vma->shared.vm_set.list,
&old->shared.vm_set.list);
else if (old->shared.vm_set.head)
list_add_tail(&vma->shared.vm_set.list,
&old->shared.vm_set.head->shared.vm_set.list);
else {
INIT_LIST_HEAD(&vma->shared.vm_set.list);
vma->shared.vm_set.head = old;
old->shared.vm_set.head = vma;
}
}
函数代码分析
c
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
vma:要添加的新VMAold:已存在的VMA
初始化新VMA
c
vma->shared.vm_set.head = NULL;
vma->shared.vm_set.parent = NULL;
-
head = NULL:新VMA不是链表头 -
parent = NULL:新VMA不是树节点 -
新VMA被初始化为普通的链表节点
情况1:old不是树节点
c
if (!old->shared.vm_set.parent)
list_add(&vma->shared.vm_set.list,
&old->shared.vm_set.list);
-
old->shared.vm_set.parent == NULL,old本身是链表节点(不是树节点) -
list_add:将新VMA添加到old所在的链表中 -
新VMA被插入到链表头部
情况2:old是树节点且有链表
c
else if (old->shared.vm_set.head)
list_add_tail(&vma->shared.vm_set.list,
&old->shared.vm_set.head->shared.vm_set.list);
-
old->shared.vm_set.parent != NULL(old是树节点) -
old->shared.vm_set.head != NULL(old已经有链表) -
list_add_tail:将新VMA添加到链表尾部 -
通过
old->shared.vm_set.head找到链表头
情况3:old是树节点但无链表
c
else {
INIT_LIST_HEAD(&vma->shared.vm_set.list);
vma->shared.vm_set.head = old;
old->shared.vm_set.head = vma;
}
old->shared.vm_set.parent != NULL(old是树节点)old->shared.vm_set.head == NULL(old还没有链表)
INIT_LIST_HEAD(&vma->shared.vm_set.list):初始化新VMA的链表头vma->shared.vm_set.head = old:新VMA指向树节点作为链表头old->shared.vm_set.head = vma:树节点指向新VMA作为链表头
总结
主要功能:将映射相同文件区间的新VMA添加到现有VMA的链表中
-
一致性验证
- 确保新老VMA映射完全相同的文件区间
- 通过基数索引和堆索引双重验证
-
节点类型识别
- 根据old节点的指针状态识别其角色
- 支持树节点、链表头、链表节点三种状态
-
链表管理
- 处理三种不同的添加场景
- 维护正确的链表结构
-
并发安全设计
- 通过指针关系而非标志位识别节点类型
- 避免锁的复杂依赖
匿名VMA链接和验证函数__anon_vma_link
c
void __anon_vma_link(struct vm_area_struct *vma)
{
struct anon_vma *anon_vma = vma->anon_vma;
if (anon_vma) {
list_add(&vma->anon_vma_node, &anon_vma->head);
validate_anon_vma(vma);
}
}
static inline void validate_anon_vma(struct vm_area_struct *find_vma)
{
#ifdef RMAP_DEBUG
struct anon_vma *anon_vma = find_vma->anon_vma;
struct vm_area_struct *vma;
unsigned int mapcount = 0;
int found = 0;
list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {
mapcount++;
BUG_ON(mapcount > 100000);
if (vma == find_vma)
found = 1;
}
BUG_ON(!found);
#endif
}
__anon_vma_link 函数
- 将VMA链接到其匿名VMA的链表中
获取匿名VMA指针
c
struct anon_vma *anon_vma = vma->anon_vma;
- 从VMA结构中获取关联的匿名VMA管理结构
c
if (anon_vma) {
- 只有当VMA有关联的匿名VMA时才进行处理
c
list_add(&vma->anon_vma_node, &anon_vma->head);
list_add:将VMA添加到匿名VMA链表的头部&vma->anon_vma_node:VMA中的链表节点&anon_vma->head:匿名VMA的链表头
c
validate_anon_vma(vma);
- 调试模式下验证链接的正确性
validate_anon_vma 函数
c
static inline void validate_anon_vma(struct vm_area_struct *find_vma)
{
#ifdef RMAP_DEBUG
- 只有在定义了
RMAP_DEBUG时才会编译和运行这个函数 - 用于调试反向映射系统的正确性
变量声明
c
struct anon_vma *anon_vma = find_vma->anon_vma;
struct vm_area_struct *vma;
unsigned int mapcount = 0;
int found = 0;
anon_vma:要验证的匿名VMAvma:遍历时的当前VMAmapcount:映射计数,用于检测无限循环found:标记是否找到目标VMA
遍历匿名VMA链表
c
list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {
- 遍历
anon_vma->head链表中的所有VMA
映射计数检查
c
mapcount++;
BUG_ON(mapcount > 100000);
- 增加映射计数
- 如果计数超过100000,触发内核错误
- 目的:检测可能的无限循环或链表损坏
查找目标VMA
c
if (vma == find_vma)
found = 1;
- 如果找到目标VMA,设置found标志为1
验证结果检查
c
BUG_ON(!found);
- 如果遍历完整个链表都没有找到目标VMA,触发内核错误
- 目标VMA必须存在于它声称所属的匿名VMA链表中
设计原理分析
为什么需要匿名VMA链表?
当物理页需要被换出或迁移时,需要找到所有映射该页的VMA,这就是反向映射
匿名页的特殊性
匿名页没有文件后端,需要通过VMA来管理映射关系
总结
主要功能:将VMA正确链接到匿名VMA管理结构中,支持反向映射:
- 链表管理
- 将VMA添加到匿名VMA的链表中
- 维护VMA与匿名VMA的关联关系
合并两个相邻的匿名VMA管理结构__anon_vma_merge
c
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
{
if (!vma->anon_vma) {
BUG_ON(!next->anon_vma);
vma->anon_vma = next->anon_vma;
list_add(&vma->anon_vma_node, &next->anon_vma_node);
} else {
/* if they're both non-null they must be the same */
BUG_ON(vma->anon_vma != next->anon_vma);
}
list_del(&next->anon_vma_node);
}
函数代码分析
c
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
vma:当前VMA(合并后的主VMA)next:下一个VMA(将被合并的VMA)
当前VMA没有匿名VMA的情况
c
if (!vma->anon_vma) {
BUG_ON(!next->anon_vma);
vma->anon_vma = next->anon_vma;
list_add(&vma->anon_vma_node, &next->anon_vma_node);
}
-
当前VMA没有关联的匿名VMA管理结构
-
验证:确保下一个VMA必须有匿名VMA
-
当前VMA继承下一个VMA的匿名VMA管理结构
-
将当前VMA添加到下一个VMA在匿名VMA链表中的位置
当前VMA有匿名VMA的情况
c
} else {
/* if they're both non-null they must be the same */
BUG_ON(vma->anon_vma != next->anon_vma);
}
- 确保两个VMA的匿名VMA指针相同
- 相邻的VMA如果要合并,它们必须共享相同的匿名VMA管理结构
移除下一个VMA的链表节点
c
list_del(&next->anon_vma_node);
-
从匿名VMA链表中移除下一个VMA的节点
-
下一个VMA不再属于任何匿名VMA链表
-
下一个VMA的
anon_vma_node被从链表中解除链接
总结
主要功能:合并两个相邻的匿名VMA管理结构
-
匿名VMA继承
- 处理当前VMA没有匿名VMA的情况
- 从下一个VMA继承匿名VMA管理结构
-
一致性验证
- 确保要合并的VMA共享相同的匿名VMA
- 防止不一致的合并操作
-
链表管理
- 将当前VMA插入到匿名VMA链表的正确位置
- 从链表中移除下一个VMA