Linux内存管理:(五)反向映射RMAP

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

  • 参考资料及图片来源:《奔跑吧Linux内核》

  • Linux 5.0内核源码注释仓库地址:

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 前置知识:page数据结构中的相关字段

本文主要对反向映射RMAP进行讲解,在讲解之前,我们先了解下page数据结构中与RMAP相关的几个字段:

  • mapping:表示页面所指向的地址空间。内核中的地址空间通常有两个不同的地址空间,—个用于文件映射页面,如在读取文件时,地址空间用于将文件的内容数据与装载数据的存储介质区关联起来;另—个用于匿名映射。内核使用一个简单直接的方式实现了“一个指针,两种用途”,mapping成员的最低两位用于判断是否指向匿名映射或KSM页面的地址空间。如果指向匿名页面,那么mapping成员指向匿名页面的地址空间数据结构anon_vma。
  • _refcount:表示内核中引用该页面的次数。
    • 当_refcount的值为0时,表示该页面为空闲页面或即将要被释放的页面
    • 当_refcount的值大于0时,表示该页面已经被分配且内核正在使用,暂时不会被释放
  • _mapcount:表示这个页面被进程映射的个数,即已经映射了多少个用户PTE。每个用户进 程都拥有各自独立的虚拟空间和一份独立的页表,所以可能出现多个用户进程地址空间同时映射到一个物理页面的情况,RMAP系统就是利用这个特性来实现的。_mapcount主要用于RMAP系统中。
    • 若_mapcount等于-1,表示没有PTE映射到页面
    • 若_mapcount等于0,表示只有父进程映射到页面。匿名页面刚分配时,初始化为0。

2. RMAP的背景

用户进程在使用虚拟内存的过程中,从虚拟内存页面映射到物理内存页面时,PTE保留这个记录,page数据结构中的_mapcount记录有多少个用户PTE映射到物理页面。用户PTE是指用户进程地址空间和物理页面建立映射的PTE,不包括内核地址空间映射物理页面时产生的PTE。有的页面需要迁移,有的页面长时间不使用,需要交换到磁盘。在交换之前,必须找出哪些进程使用这个页面,然后解除这些映射的用户PTE。一个物理页面可以同时被多个进程的虚拟内存映射,但是一个虚拟页面同时只能映射到一个物理页面。

在Linux 2.4内核中,为了确定某一个页面是否被某个进程映射,必须遍历每个进程的页表,因此工作量相当大,效率很低。在Linux2.5内核开发期间,提出了反问映射(Reverse Mapping,RMAP)的概念。

3. RMAP的主要数据结构

RMAP的主要目的是从物理页面的page数据结构中找到有哪些映射的用户PTE,这样页面回收模块就可以很快速和高效地把这个物理页面映射的所有用户PTE都解除并回收这个页面。

为了达到这个目的,内核在页面创建时需要建立RMAP的“钩子”,即建立相关的数据结构,RMAP系统中有两个重要的数据结构:一个是anon_vma,简称AV;另一个是anon_vma_chain,简称AVC。

anon_vma 数据结构:

// 主要用于连接物理页面的 page 数据结构和 VMA 的 vm_area_struct 数据结构
struct anon_vma {// 指向 anon_vma 数据结构的根节点struct anon_vma *root;		/* Root of this anon_vma tree */// 保护 anon_vma 数据结构中链表的读写信号量struct rw_semaphore rwsem;	/* W: modification, R: walking the list */// 引用计数atomic_t refcount;...// 指向父 anon_vma 数据结构struct anon_vma *parent;	/* Parent of this anon_vma */// 红黑树根节点。anon_vma 内部有一颗红黑树struct rb_root_cached rb_root;
};

在这里插入图片描述

anon_vma_chain 数据结构:

// 起枢纽的作用,比如连接父子进程间的 struct anon_vma 数据结构
struct anon_vma_chain {// 指向 VMA。可以指向父进程的 VMA,也可以指向子进程的 VMA,具体情况需要具体分析struct vm_area_struct *vma;// 指向 anon_vma 数据结构。可以指向父进程的 anon_vma,也可以指向子进程的 anon_vma,具体情况需要具体分析struct anon_vma *anon_vma;// 链表节点,通常把 anon_vma_chain 添加到 vma->anon_vma_chain 链表中struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */// 红黑树节点,通常把 anon_vma_chain 添加到 anon_vma->rb_root 的红黑树中struct rb_node rb;			/* locked by anon_vma->rwsem */...
};

4. 父进程产生匿名页面

父进程为自己的进程地址空间VMA分配物理内存时,通常会产生匿名页面。例如:

用户态malloc()分配虚拟内存→ 用户进程写内存→ 内核发生缺页异常→ do_anonymous_page()

父进程产生匿名页面时的状态如下图所示:

在这里插入图片描述

  • 父进程的每个VMA中有一个anon_vma数据结构(下文用AVp来表示),vma->anon_vma指向AVp
  • 和VMAp相关的物理页面page->mapping都指向AVp
  • 有一个anon_vma_chain数据结构,其中avc->vma指向VMAp,avc->av指向AVp
  • 把anon_vma_chain添加到VMAp->anon_vma_chain链表中
  • 把anon_vma_chain添加到AVp->anon_vma红黑树中

5. 根据父进程创建子进程

父进程通过fork()系统调用创建子进程时,子进程会复制父进程的VMA数据结构的内容,并且会复制父进程的PTE内容到子进程的页表中,实现父、子进程共享页表。多个不同子进程中的虚拟页面会同时映射到同一个物理页面。另外,多个不相干的进程的虚拟页面可以通过KSM机制映射到同—个物理页面中,这里暂时只讨论前者。为了实现RMAP系统,在子进程复制父进程的VMA时,需要添加RMAP“钩子”。

父进程通过fork调用创建子进程时,RMAP机制的流程如下图所示:

在这里插入图片描述

为了使读者有更真切的理解,下文将根据流程图围绕源代码进行讲解这个过程:

父进程fork子进程->do_fork()->copy_process()->copy_mm()->dup_mm()->dup_mmap()

// 复制父进程的地址空间
static __latent_entropy int dup_mmap(struct mm_struct *mm,struct mm_struct *oldmm)
{...// 遍历父进程所有的 VMAfor (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {struct file *file;...// 新建一个临时用的 VMA 数据结构 tmp,复制父进程 VMA 数据结构的内容到 tmptmp = vm_area_dup(mpnt);...tmp->vm_mm = mm;...// anon_vma_fork() 为子进程创建相应的 anon_vma 数据结构} else if (anon_vma_fork(tmp, mpnt))...// 把 tmp 添加到子进程的红黑树中__vma_link_rb(mm, tmp, rb_link, rb_parent);...// 复制父进程的 PTE 到子进程页表中retval = copy_page_range(mm, oldmm, mpnt);...
}

父进程fork子进程->do_fork()->copy_process()->copy_mm()->dup_mm()->dup_mmap()->anon_vma_fork

// 主要作用是把 VMA 绑定到子进程的 anon_vma 数据结构中
// 参数 vma 表示子进程的 VMA
// 参数 pvma 表示父进程的 VMA
int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma)
{...// 若父进程没有 anon_vma 数据结构,就不需要绑定了if (!pvma->anon_vma)return 0;// anon_vma_clone() 函数:// 遍历父进程VMA中的anon_vma_chain链表寻找anon_vma_chain实例,这里称这个实例为pavc;// 分配一个新的anon_vma_chain数据结构,这里称为anon_vma_chain枢纽;// 通过pavc找到父进程VMA中的anon_vma;// 把这个anon_vma_chain枢纽挂入子进程的VMA的anon_vma_chain链表中,同时把anon_vma_chain枢纽添加到属于父进程的anon_vma->rb_root的红黑树中,使子进程和父进程的VMA之间有一个联系的纽带;error = anon_vma_clone(vma, pvma);if (error)return error;// 若子进程的 VMA 已经创建了 anon_vma 数据结构,说明绑定已经完成if (vma->anon_vma)return 0;// 分配属于子进程的 anon_vma 和 anon_vma_chainanon_vma = anon_vma_alloc();...// get_anon_vma() 增加 anon_vma 数据结构中的 refcount,注意这里增加的是父进程的 anon_vma 中的引用计数get_anon_vma(anon_vma->root);...// 把 anon_vma_chain 挂入子进程的 vma->anon_vma_chain 链表中,同时把 anon_vma_chain 加入子进程的 anon_vma->rb_root 红黑树中// 至此,子进程的 VMA 和父进程的 VMA 之间的纽带建立成功anon_vma_chain_link(vma, avc, anon_vma);...
}

6. 子进程发生写时复制

如果子进程的VMA发生写时复制,那么page->mmapmg指针指向子进程VMA对应的anon_vma数据结构。在do_wp_page()函数中处理写时复制的情况。流程如下图所示:

子进程和父进程共享的匿名页面,子进程的VMA发生写时复制->缺页中断发生->handle_pte_fault()->do_wp_page()->wp_page_copy()->分配一个新的匿名页面->page_add_new_anon_rmap()->__page_set_anon_rmap()使用子进程的anon_vma来设置page->mapping

子进程发生写时复制时,RMAP机制的流程如下图所示:

在这里插入图片描述

7. RMAP的应用

RMAP的典型应用场景如下:

  • kswapd内核线程为了回收页面,需要断开所有映射到该匿名页面的用户PTE
  • 页面迁移时,需要断开所有映射到匿名页面的用户PTE

RMAP的核心函数是try_to_unmap(),内核中的其他模块会调用此函数来断开一个页面的所有映射:

bool try_to_unmap(struct page *page, enum ttu_flags flags)
{struct rmap_walk_control rwc = {.rmap_one = try_to_unmap_one,.arg = (void *)flags,.done = page_mapcount_is_zero,.anon_lock = page_lock_anon_vma_read,};...if (flags & TTU_RMAP_LOCKED)rmap_walk_locked(page, &rwc);elsermap_walk(page, &rwc);// 判断 page 的 _mapcount:// 若 _mapcount 为 -1,说明所有映射到这个页面的用户 PTE 都已经解除完毕,因此返回 true// 否则返回 truereturn !page_mapcount(page) ? true : false;
}

rmap_walk_control 数据结构:

// 用于统一管理 unmap 操作
struct rmap_walk_control {void *arg;// 表示具体断开某个 VMA 上映射的 PTEbool (*rmap_one)(struct page *page, struct vm_area_struct *vma,unsigned long addr, void *arg);// 表示判断一个页面是否断开成功int (*done)(struct page *page);// 实现一个锁机制struct anon_vma *(*anon_lock)(struct page *page);// 表示跳过无效的 VMAbool (*invalid_vma)(struct vm_area_struct *vma, void *arg);
};

以匿名页面为例来介绍RMAP的应用:

try_to_unmap()->rmap_walk()->rmap_walk_anon()

// 断开一个匿名页面的所有映射
// 参数 page 表示需要解除映射的物理页面的 page 数据结构
// 参数 rwc 表示 rmap_walk_control 数据结构
// 参数 locked 表示是否已经加锁
static void rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc,bool locked)
{...if (locked) {// 若 locked 已经加锁// 调用 page_anon_vma() 函数来获取 anon_vma 数据结构anon_vma = page_anon_vma(page);/* anon_vma disappear under us? */VM_BUG_ON_PAGE(!anon_vma, page);} else {// 若 locked 没有加锁// rmap_walk_anon_lock() 函数除了要取回 anon_vma 数据结构外,还会申请一个锁anon_vma = rmap_walk_anon_lock(page, rwc);}...// 遍历 anon_vma->rb_root 红黑树中的 anon_vma_chain,从 anon_vma_chain 中可以// 得到相应的 VMA,然后调用 rmap_one() 来解除用户 PTEanon_vma_interval_tree_foreach(avc, &anon_vma->rb_root,pgoff_start, pgoff_end) {...}...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/322302.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据StarRocks(四) :常用命令

这次主要介绍生产工作中Starrocks时的常用命令 4.1 连接StarRocks 4.1.1 Linux命令行连接 [roothadoop1011 fe]# yum install mysql -y [roothadoop1011 fe]# mysql -h hadoop101 -uroot -P9030 -p4.1.2 Windows客户端 DBeaver 连接 4.2 常用命令 4.2.1 查看状态 1. 查看f…

Pytorch从零开始实战15

Pytorch从零开始实战——ResNeXt-50算法实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——ResNeXt-50算法实战环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook,使用Python3.8,Pytor…

智能穿戴时代 | 米客方德SD NAND的崭新优势

SD NAND在智能穿戴上的优势 SD NAND是一种可以直接焊接在智能穿戴设备主板上的存储芯片,其小型化设计有助于紧凑设备尺寸,同时提供可靠的嵌入式存储解决方案。 这种集成设计减少了空间占用,同时确保设备在高度活动的环境中更为稳定。SD NAND…

漏洞复现--天融信TOPSEC两处远程命令执行

免责声明: 文章中涉及的漏洞均已修复,敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

EasyRecovery2024永久免费版电脑数据恢复软件

EasyRecovery是一款操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序,它不会往源驱上写任何东西,也不会对源驱做任何改变。它支持从各种各样的存储介质恢复删除或者丢失的文件,其支持的媒体介质包括:硬盘驱动器、光驱、…

Linux第7步_设置虚拟机的电源

设置ubuntu代码下载源和关闭“自动检查更新”后,就要学习设置“虚拟机的电源”了。 用处不大,主要是了解”螺丝刀和扳手形状的图标“在哪里。 1、打开虚拟机,点击最右边的“下拉按钮”,弹出对话框,得到下图&#xff…

Electron介绍

前言 相信很多的前端小伙伴都想过一个问题,web技术是否可以用于开发桌面应用。答案当然是可以的,Electron框架就是其中的一种解决方案。 Electron介绍 Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 Electron 并不是一门新的…

丰田凯美瑞雾灯改双光透镜解决方案

丰田凯美瑞大灯采用CAN总线控制车灯的近光、远光、日行灯、转向灯信号,无法直接从车灯插头上直接获取近光远光信号。传统改灯需要拆开车灯通过光耦线阻取得近远光开光信号,工序繁琐且不美观。 厦门市创宇致诚电子科技推出一款丰田凯美瑞车灯信号解码器&a…

15 Linux 按键

一、Linux 按键驱动原理 其实案件驱动和 LED 驱动很相似,只不过区别在于,一个是读取GPIO高低电平,一个是从GPIO输出高低电平。 在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键…

JVM之对象创建

对象创建的流程 1.类加载检查 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。new指令对…

table的最后一行需要加底色

<tr class"font12" v-for"(item, index) in OrderAuditDiscountList.list" :key"index":class"OrderAuditDiscountList.list.length - 1 index ? blodfont : "> 其中&#xff1a; :class"OrderAuditDiscountList.list.le…

tolist()读取Excel列数据,(Excel列数据去重后,重新保存到新的Excel里)

从Excel列数据去重后&#xff0c;重新保存到新的Excel里 import pandas as pd# 读取Excel文件 file r"D:\\pythonXangmu\\quchong\\quchong.xlsx" # 使用原始字符串以避免转义字符 df pd.read_excel(file, sheet_namenameSheet)# 删除重复值 df2 df.drop_duplica…