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);
}
相关推荐
HLC++18 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室19 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy1 天前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕1 天前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫1 天前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生1 天前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.111 天前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器