- 进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途;一个合法的地址总是落在某个区域当中的,这些区域也不会重叠, 在linux内核中,这样的区域被称之为虚拟内存区域(virtual memory areas),简称 VMA.
- 一个vma就是一块连续的线性地址空间的抽象,它拥有自身的权限(可读,可写,可执行等等)
- 匿名页(Anonymous Page)是 Linux 内核中没有关联任何文件 / 设备的物理内存页,仅用于存储进程的「纯内存数据」(如堆、栈、匿名映射的内存)------ 核心特征是「无持久化的文件数据源」,数据仅存在于内存中,是进程运行时临时数据的核心载体
c
struct vm_area_struct {
struct mm_struct * vm_mm; /* 所属的内存描述符 */
unsigned long vm_start; /* vma的起始地址 */
unsigned long vm_end; /* vma的结束地址 */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb; /* 红黑树中对应的节点 */
pgprot_t vm_page_prot; /* vma的访问权限 */
unsigned long vm_flags; /* 标识集 */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE units */
struct file * vm_file; /* File we map to (can be NULL). */
......
}
- 双向链表(vm_next/vm_prev):主打「顺序遍历」,适配需访问全部 / 连续 VMA 的场景,遍历效率最优;
- 红黑树(vm_rb):主打「地址快速查找 / 插入 / 删除」,适配缺页异常、mmap 等高频地址定位场景,查找效率最优;
- 内核通过「双结构同步」,以少量同步开销换来了「遍历 + 查找」的全场景高效,是经典的「空间换时间」设计
vma 建树、建链表
mmap_region
c
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
int error;
struct rb_node **rb_link, *rb_parent;
/* Clear old maps */
while (find_vma_links(mm, addr, addr + len, &prev, &rb_link,
&rb_parent)) {
if (do_munmap(mm, addr, len, uf))
return -ENOMEM;
}
/* Can we just expand an old mapping?*/
vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);
vma = vm_area_alloc(mm);
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;
vma_link(mm, vma, prev, rb_link, rb_parent);
find_vma_links
- find_vma_links 做两件事: 检查目标地址区间是否与已有 VMA 重叠;② 找到新 VMA 在红黑树中的插入位置(rb_link)和父节点(rb_parent),以及链表的前驱节点(pprev)
- 红黑树的节点插入需要明确「插入到父节点的左 / 右孩子位置」,而二级指针 rb_link 指向红黑树中「父节点的左 / 右孩子指针地址」,是新节点的插入位置;rb_parent(一级指针):新节点在红黑树中的父节点,用于维护父子关系和红黑树平衡
c
static int find_vma_links(struct mm_struct *mm, unsigned long addr,
unsigned long end, struct vm_area_struct **pprev,
struct rb_node ***rb_link, struct rb_node **rb_parent)
{
struct rb_node **__rb_link, *__rb_parent, *rb_prev;
__rb_link = &mm->mm_rb.rb_node;
rb_prev = __rb_parent = NULL;
while (*__rb_link) { // 只要当前遍历位置有节点,就继续往下走
struct vm_area_struct *vma_tmp;
__rb_parent = *__rb_link; // 记录当前节点为「候选父节点」
// 从红黑树节点反向解析出对应的 VMA 结构体(rb_entry 是容器_of 封装)
vma_tmp = rb_entry(__rb_parent, struct vm_area_struct, vm_rb);
if (vma_tmp->vm_end > addr) { // 情况1:当前 VMA 的结束地址 > 目标地址 → 往左子树找
// 关键检查:如果当前 VMA 的起始地址 < 目标区间的结束地址 → 地址重叠!
if (vma_tmp->vm_start < end)
return -ENOMEM; // 重叠则返回错误,无法分配
__rb_link = &__rb_parent->rb_left; // 往左子树遍历
} else { // 情况2:当前 VMA 的结束地址 ≤ 目标地址 → 往右子树找
rb_prev = __rb_parent; // 记录「地址小于 addr 的最后一个 VMA 节点」
__rb_link = &__rb_parent->rb_right; // 往右子树遍历
}
}
*pprev = NULL;
if (rb_prev)
// 从红黑树节点解析出对应的 VMA,作为链表的前驱节点
*pprev = rb_entry(rb_prev, struct vm_area_struct, vm_rb);
*rb_link = __rb_link; // 输出:新节点的插入位置(二级指针)
*rb_parent = __rb_parent; // 输出:新节点的父节点
return 0; // 无重叠,返回成功
}
vma_merge
vma_link
vma_link->__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);
}
__vma_link_list
c
void __vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, struct rb_node *rb_parent)
{
struct vm_area_struct *next;
vma->vm_prev = prev;
if (prev) {
// 情况1:有前驱 VMA → 后继 = 前驱原本的 next(prev->vm_next)
next = prev->vm_next;
prev->vm_next = vma; // 更新前驱的 next → 指向新 VMA
} else {
// 情况2:无前驱 VMA(新 VMA 是链表头)
// struct vm_area_struct *mmap; /* list of VMAs */
mm->mmap = vma; // 将进程链表头指向新 VMA
if (rb_parent) {
// 红黑树父节点对应的 VMA 就是新 VMA 的后继(地址更大)
next = rb_entry(rb_parent, struct vm_area_struct, vm_rb);
} else {
// 无红黑树父节点 → 进程无其他 VMA,后继为 NULL
next = NULL;
}
}
vma->vm_next = next;
if (next)
next->vm_prev = vma;
}
__vma_link_rb
c
void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,
struct rb_node **rb_link, struct rb_node *rb_parent)
{
// 第一步:将新 VMA 的 vm_rb 节点挂载到红黑树的指定位置(仅挂载,不平衡)
rb_link_node(&vma->vm_rb, rb_parent, rb_link);
// 第二步:调整红黑树的颜色和结构,保证自平衡(核心)
vma_rb_insert(vma, &mm->mm_rb);
}