Linux内存管理:(八)页面迁移

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

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

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

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 可迁移页面

页面迁移机制支持两大类内存页面:

  • 传统LRU页面,如匿名页面和文件映射页面
  • 非LRU页面,如zsmalloc或者virtio-balloon页面,以virtio-balloon页面为例,它也有页面迁移的需求,之前的做法是在virtio-balloon驱动中进行迁移操作和相应的逻辑。如果其他的驱动也想做类似的页面迁移,那么它们就不能复用与virtio-balloon驱动相关的代码,必须重新写一套代码,这样会造成很多代码的重复和冗余。为了解决这个问题,内存管理的页面迁移机制提供相应的接口来支持这些非LRU页面的迁移。

2. 页面迁移流程

页面迁移的本质是将页面的内容迁移到新的页面。这个过程中会分配新页面,将旧页面的内容复制到新页面,断开旧页面的映射关系,并把映射关系映射到新页面,最后释放旧页面。页面迁移的整个流程图如下所示:

在这里插入图片描述

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

页面迁移(page migration)在Linux内核的主函数是migrate_pages()函数:

// 页面迁移的主函数
// from: 将要迁移页面的链表
// get_new_page: 申请新内存的页面的函数指针
// put_new_page: 迁移失败时释放目标页面的函数指针
// private: 传递给 get_new_page 的参数
// mode:迁移模式
// reason: 迁移的原因
int migrate_pages(struct list_head *from, new_page_t get_new_page,
		free_page_t put_new_page, unsigned long private,
		enum migrate_mode mode, int reason)
{
	...
				rc = unmap_and_move(get_new_page, put_new_page,
						private, page, pass > 2, mode,
						reason);

			....
}

migrate_pages()->unmap_and_move()

static ICE_noinline int unmap_and_move(new_page_t get_new_page,
				   free_page_t put_new_page,
				   unsigned long private, struct page *page,
				   int force, enum migrate_mode mode,
				   enum migrate_reason reason)
{
	...

	// 分配一个新的页面
	newpage = get_new_page(page, private);
	if (!newpage)
		return -ENOMEM;

	if (page_count(page) == 1) {
		...
		// 刚分配的页面需要调用 put_new_page() 回调函数
		if (put_new_page)
			put_new_page(newpage, private);
		...
	}

	// 尝试迁移页面到新分配的页面中
	rc = __unmap_and_move(page, newpage, force, mode);
	if (rc == MIGRATEPAGE_SUCCESS)
		set_page_owner_migrate_reason(newpage, reason);

out:
	// 若返回值不等于 -EAGAIN,说明可能迁移没成功
	if (rc != -EAGAIN) {
		...
	}
	
	// 若返回值等于 MIGRATEPAGE_SUCCESS,说明迁移成功,释放页面
	if (rc == MIGRATEPAGE_SUCCESS) {
		...
	// 处理迁移没成功的情况,把页面重新添加到可移动的页面里。释放刚才新分配的页面
	} else {
		....
	}

	return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()

// page:被迁移的页面
// newpage: 迁移页面的目的地
// force: 表示是否强制迁移。在 migrate_pages() 中,当尝试次数大于 2 时,会设置为 1(0 表示强制迁移)
// mode: 迁移模式
static int __unmap_and_move(struct page *page, struct page *newpage,
				int force, enum migrate_mode mode)
{
	...
	// __PageMovable() 函数用于判断这个页面是否属于非 LRU 页面,它是通过 page 数据结构中的 mapping 成员
	// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的
	bool is_lru = !__PageMovable(page);

	// trylock_page() 尝试给页面加锁,返回 true 则表示当前进程已经成功获取锁
	if (!trylock_page(page)) {
		// 如果尝试获取也锁不成功
		// 若满足 !force || mode == MIGRATE_ASYNC,则直接忽略这个页面,因为这种情况下没有必要睡眠等待页面释放锁
		if (!force || mode == MIGRATE_ASYNC)
			goto out;

		// 如过当前进程设置了 PF_MEMALLOC 标志位,表示当前进程可能处于直接内存压缩的内核路径上,通过睡眠等待页锁是
		// 是不安全的,所以直接忽略该页面
		if (current->flags & PF_MEMALLOC)
			goto out;

		// 其他情况下只能等待页锁的释放
		lock_page(page);
	}

	// 处理正在回写的页面,即设置了 PG_writeback 标志位的页面
	if (PageWriteback(page)) {
		// 只有当页面迁移的模式为 MIGRATE_ASYNC 或者 MIGRAIE_SYNC_LIGHT 且设置强制迁移(force=1)
		// 时,才会等待这个页面回写完成,否则直接忽略该页面,该页面不会被迁移
		switch (mode) {
		case MIGRATE_SYNC:
		case MIGRATE_SYNC_NO_COPY:
			break;
		default:
			rc = -EBUSY;
			goto out_unlock;
		}
		if (!force)
			goto out_unlock;
		// 等待页面回写完成
		wait_on_page_writeback(page);
	}

	// 处理匿名页面的 anon_vma 可能被释放的特殊情况,因为接下来 try_to_unmap() 函数运行完成时,
	// page->_mapcount 会变成 0。在页面迁移的过程中,我们无法知道 anon_vma 数据结构是否被释放了
	if (PageAnon(page) && !PageKsm(page))
		// page_get_anon_vma() 增加 anon_vma->refcount 引用计数防止其被其他进程释放
		anon_vma = page_get_anon_vma(page);

	// 尝试给 newpage 申请锁
	if (unlikely(!trylock_page(newpage)))
		goto out_unlock;

	// 若这个页面属于非 LRU 页面
	if (unlikely(!is_lru)) {
		// move_to_new_page() 函数中会通过驱动程序注册 migratepage() 函数来进行页面迁移
		rc = move_to_new_page(newpage, page, mode);
		goto out_unlock_both;
	}

	// 接下来的代码用于处理传统的 LRU 页面
	if (!page->mapping) {
		// 处理一个特殊情况。当一个交换缓存页面从交换分区被读取之后,它会被添加到 LRU 链表里,我们把它当作
		// 一个交换缓存页面。但是它还没有设置 RMAP,因此 page->mapping 为空。若调用 try_to_unmap() 可能
		// 会触发内核岩机,因此这里做特殊处理,并跳转到 out_unlock_both 标签处。
		VM_BUG_ON_PAGE(PageAnon(page), page);
		if (page_has_private(page)) {
			try_to_free_buffers(page);
			goto out_unlock_both;
		}
	// page_mapped() 判断该页面的 _mapcount 是否大于或等于 0,若大于或等于 0,说明有用户 PTE 映射该页面
	} else if (page_mapped(page)) {
		VM_BUG_ON_PAGE(PageAnon(page) && !PageKsm(page) && !anon_vma,
				page);
		// 对于有用户态进程地址空间映射的页面,调用 try_to_unmap() 解除页面所有映射的用户 PTE
		try_to_unmap(page,
			TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);
		page_was_mapped = 1;
	}

	// 对于已经解除完所有用户 PTE 映射的页面,调用 move_to_new_page() 把它们迁移到新分配的页面
	if (!page_mapped(page))
		rc = move_to_new_page(newpage, page, mode);

	if (page_was_mapped)
		// 迁移页表
        // 对于迁移页面失败的情况,调用 remove_migration_ptes() 删除迁移的 PTE
        remove_migration_ptes(page,
			rc == MIGRATEPAGE_SUCCESS ? newpage : page, false);

out_unlock_both:
	unlock_page(newpage);
out_unlock:
	/* Drop an anon_vma reference if we took one */
	if (anon_vma)
		put_anon_vma(anon_vma);
	unlock_page(page);
// 处理退出情况
out:
	if (rc == MIGRATEPAGE_SUCCESS) {
		// 对于非 LRU 页面,调用 put_page() 把 newpage 的 _refcount 减 1
		if (unlikely(!is_lru))
			put_page(newpage);
		// 对于传统 LRU 页面,把 newpage 添加到 LRU 链表中
		else
			putback_lru_page(newpage);
	}

	return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->move_to_new_page()

// 用于迁移旧页面到新页面中
static int move_to_new_page(struct page *newpage, struct page *page,
				enum migrate_mode mode)
{
	...
	// 判断页面是否属于传统的的 LRU 页面。通过 page 数据结构中的 mapping 成员
	// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的
	bool is_lru = !__PageMovable(page);

	...

	// 返回页面的 mapping
	mapping = page_mapping(page);

	if (likely(is_lru)) {
		// 若页面属于传统的 LRU 链表的页面,按以下几种情况处理
		// 若 mapping 为空,说明该页面是匿名页面但是没有分配交换缓存,那么调用 migrate_page() 函数来迁移页面
		if (!mapping)
			rc = migrate_page(mapping, newpage, page, mode);
		// 该页面实现了 migratepage(),那么直接调用 mapping->a_ops->migratepage() 来迁移页面
		else if (mapping->a_ops->migratepage)
			rc = mapping->a_ops->migratepage(mapping, newpage,
							page, mode);
		// 其他情况下
		else
			rc = fallback_migrate_page(mapping, newpage,
							page, mode);
	// 对于页面属于非 LRU 页面的情况,直接调用驱动程序为这个页面注册的 migratepage() 来迁移页面
	} else {
		...

		rc = mapping->a_ops->migratepage(mapping, newpage,
						page, mode);
		WARN_ON_ONCE(rc == MIGRATEPAGE_SUCCESS &&
			!PageIsolated(page));
	}

	// 处理迁移成功的情况
	if (rc == MIGRATEPAGE_SUCCESS) {
		...
	}
out:
	return rc;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->remove_migration_ptes()->remove_migration_pte()

static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,
				 unsigned long addr, void *old)
{
	...
	// page_vma_mapped_walk() 遍历页表,通过虚拟地址找到对应的 PTE
	while (page_vma_mapped_walk(&pvmw)) {
		...
		// 根据新页面和 vma 属性来生成一个 PTE
		pte = pte_mkold(mk_pte(new, READ_ONCE(vma->vm_page_prot)));
		...
		if (PageHuge(new)) {
			...
		} else{
			// 把新生成的 PTE 的内容写回到原来映射的页面中,完成 PTE 的迁移,这样用户进程地址空间就可以
			// 通过原来的 PTE 访问新页面
			set_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);

			// 把新页面添加到 RMAP 系统中
			if (PageAnon(new))
				page_add_anon_rmap(new, vma, pvmw.address, false);
			else
				page_add_file_rmap(new, false);
		}
		...
        
		// 更新相应的高速缓存
		update_mmu_cache(vma, pvmw.address, pvmw.pte);
	}

	return true;
}

migrate_pages()->unmap_and_move()->__unmap_and_move()->move_to_new_page()->migratepage()

struct address_space_operations {
    ...
	int (*migratepage) (struct address_space *,
			struct page *, struct page *, enum migrate_mode);
    ...
};

migratepage()方法会迁移旧页面的内容到新页面中,并且设置page对应的成员和属性。驱动实现的migratepage()方法在完成页面迁移之后需要显式地调用__ClearPageMovable()函数清除PAGE_MAPPING_MOVABLE标志位。如果迁移页面不成功,返回-EAGAIN,那么根据页面迁移机制会重试一次。若返回其他错误值,那么根据页面迁移机制就会放弃这个页面。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计----Springboot超市订单管理系统

项目介绍 该超市订单管理毕业设计基于jdk8版本开发,在部署时需要使用jdk8以上的版本。使用了目前流行的框架组合springbootmybatis的框架技术, 实现了供应商管理对供应商实现增删改查、订单管理对超市订单实现增删改查、用户管理等功能,适用…

解决Windows11 “我们无法设置移动热点”

目录 问题复现解决办法①启动网络适配器②打开移动热点③共享网络连接④连接移动热点总结 问题复现 因为交换机上网口限制,开发环境暂时没有WIFI设备,只有一根网线和一台笔记本电脑。于是开启笔记本电脑的WiFi共享服务。结果提示 “我们无法设置移动热点…

机器学习+大数据项目

一、特征工程 特征清洗 特征监控 特征选择 计算每一个特征和响应变量的相关性 通过L1正则项来选择特征 训练能对特征打分的预选模型 通过特征组合后再来选择特征 通过深度学习来进行特征选择

性能测试分析案例-定位服务吞吐量下降

环境准备 预先安装 docker、curl、wrk、perf、FlameGraph 等工具 sudo yum groupinstall Development Tools # 安装火焰图工具 git clone https://github.com/brendangregg/FlameGraph # 安装wrk git clone https://github.com/wg/wrk cd wrk && make && sud…

VUE---计算属性computed

概念: 基于 现有的数据 ,计算出来的 新属性 。 依赖 的数据变化, 自动 重新计算 。 语法: ① 声明在 computed 中,一个计算属性对应一个函数 ② 使用起来和普通属性一样使用 {{ 计算属性名 }},注意不…

N-137基于springboot,vue运动会报名管理系统

开发工具:IDEA 服务器:Tomcat9.0, jdk1.8 项目构建:maven 数据库:mysql5.7 系统分前后台,项目采用前后端分离 前端技术:vueAvueElementUI 服务端技术:springbootmybatis 本项…

网站建设网络设计营销类网站模板

★安装环境要求★ 服务器:Linux / Apache / IIS PHP版本:5.4及5.4以上,完美支持php7.4 MYSQL版本:5.0以上 PS:php版本推荐5.6,mysql推荐使用5.7 ★模板安装步骤★ 1、请将源码包里面的所有文件和文件夹上…

UV机-理光G5六彩一白一光油配置

UV机-理光G5六彩一白一光油配置

统计学-R语言-3

文章目录 前言给直方图增加正态曲线的不恰当之处直方图与条形图的区别核密度图时间序列图洛伦茨曲线计算绘制洛伦茨曲线所需的各百分比数值绘制洛伦茨曲线 练习 前言 本篇文章是介绍对数据的部分图形可视化的图型展现。 给直方图增加正态曲线的不恰当之处 需要注意的是&#…

flutter封装dio请求库,让我们做前端的同学可以轻松上手使用,仿照axios的使用封装

dio是一个非常强大的网络请求库,可以支持发送各种网络请求,就像axios一样灵活强大,但是官网没有做一个demo示例,所以前端同学使用起来还是有点费劲,所以就想在这里封装一下,方便前端同学使用。 官网地址&a…

探索检索增强生成(RAG)技术的无限可能:Vector+KG RAG、Self-RAG、多向量检索器多模态RAG集成

探索检索增强生成(RAG)技术的无限可能:VectorKG RAG、Self-RAG、多向量检索器多模态RAG集成 由于 RAG 的整体思路是首先将文本切分成不同的组块,然后存储到向量数据库中。在实际使用时,将计算用户的问题和文本块的相似…

【C语言刷题每日一题#牛客网BC107】矩阵转置

目录 问题描述 思路逐步分析 完整代码实现 结果测试 问题描述 思路逐步分析 首先,根据输入的描述,第一行输入的是两个整数n和m,分别表示一个矩阵(二维数组)的行和列,并且行和列不超过10 根据要求&…

AC修炼计划(AtCoder Beginner Contest 335)A-F

传送门: AtCoder Beginner Contest 335 (Sponsored by Mynavi) - AtCoder A,B,C,D还算比较基础,没有什么思路,纯暴力就可以过。 这里来总结一下E和F E - Non-Decreasing Colorful Path 最开始以为是树形…

用笨办法-刻意练习来提高自己的编程能力

尝试了很多学习方法,企图快速提高编程能力,但最终发现,唯有老老实实刻意练习1,在辛苦与时间积累下,逐渐提升能力,才是最有效的方式。 将自己的笨办法总结了一下,主要包含7个步骤: …

Mariadb和mysql数据库的区别和相同之处

目 录 一、maridb 和mysql在linux系统中广泛应用 二、MySQL数据库 三、MariaDB数据库 四、MariaDB和MySQL有哪些相同点 五、MariaDB和MySQL的不同点 一、mariadb 和mysql在linux系统中广泛应用 用linux(包括centos和Ubuntu)的都知道&a…

构建基于RHEL7(CentOS7)的OpenSSH9.5p1的RPM包和升级回退方案

本文适用:RHEL7系列,或同类系统(CentOS7等) 文档形成时期:2023年 因软件世界之复杂和个人能力之限,难免疏漏和错误,欢迎指正。 文章目录 环境准备安装依赖openssh-9.5p1-el7.spec内容构建RPM包下载安装前注意事项开启t…

python学习笔记9(程序的描述方式、程序的组织结构、顺序结构、选择结构1)

(一)程序的描述方式 自然语言、流程图、伪代码 (二)程序的组织结构 顺序、选择、循环 (三)顺序结构 (四)选择结构1 if 1、条件写法1 2、如果只有一个判断的写法 3、注意冒号和缩进…

14、MySQL高频面试题

1、内连接和外连接的区别 内连接和外连接都是数据库进行多表联查时使用的连接方式,区别在于二者获取的数据集不同 内连接指的是使用左表中的每一条数据分别去连接右表中的每一条数据,仅仅显示出匹配成功的那部分 外连接有分为左外连接和右外连接 左外…

rke2 Online Deploy Rancher v2.8.0 latest (helm 在线部署 rancher v2.8.0)

文章目录 1. 简介2. 预备条件3. 安装 helm4. 安装 cert-manager4.1 yaml 安装4.2 helm 安装 5. 安装 rancher6. 验证7. 界面预览 1. 简介 Rancher 是一个 Kubernetes 管理工具,让你能在任何地方和任何提供商上部署和运行集群。 Rancher 可以创建来自 Kubernetes 托…

RPA财务机器人在厦门市海沧医院财务管理流程优化汇总的应用

目前国内外研究人员对于RPA机器人在财务管理流程优化领域中的应用研究层出不穷,但现有研究成果主要集中在财务业务单一领域,缺乏财务管理整体流程一体化管控的研究。RPA机器人的功能绝非单一的财务业务处理,无论从自身技术发展,或…