VMA-virtual memory areas

  • 进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途;一个合法的地址总是落在某个区域当中的,这些区域也不会重叠, 在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 做两件事: 检查目标地址区间是否与已有 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

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);
}
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;
}
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);
}
相关推荐
无事好时节6 小时前
【Linux 进程详解】从 PCB 到 fork
linux
kaoa0006 小时前
Linux入门攻坚——57、HAProxy
linux·运维·服务器
噜啦噜啦嘞好7 小时前
Linux——网络概念
linux·网络
博语小屋7 小时前
简单线程池实现(单例模式)
linux·开发语言·c++·单例模式
import_random7 小时前
[环境变量]export命令的作用是什么
linux
何妨呀~7 小时前
Linux在VMware上添加磁盘与扩展分区
linux·运维·服务器
CIb0la7 小时前
Linux 6.19-rc1 释出,龙芯为内核加入 32 位架构支持
linux·运维
牛奶咖啡137 小时前
Linux常见系统故障案例说明并修复解决(上)
linux·linux云计算·如何恢复linux中误删的数据·linux数据删除后的解决方法·分析修复linux无法启动故障·分析修复系统配置错误故障·linux系统资源配置错误修复
南棱笑笑生7 小时前
20251215给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-6.1】系统时统计eth1的插拔次数
linux·c语言·开发语言·rockchip