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

news2025/1/9 18:14:07

文章说明:

  • 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,那么根据页面迁移机制会重试一次。若返回其他错误值,那么根据页面迁移机制就会放弃这个页面。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VUE+bpmn.js实现工作流

1、安装bpmn.js npm install bpmn-js7.3.1 // 我安装的版本是7.3.1npm install bpmn-js-properties-panel0.37.2npm install bpmn-moddle7.1.3 npm install --save camunda-bpmn-moddle 2、配置axios,在main.js中引入axios import axios from axiosVue.proto…

Dcoker构建部署Java项目过程

目录 前言 一、打包 二、Docker File文件编写 一个简单的Docker File文件 三、上传文件 四、构建镜像 五、运行 六、端口开放 前言 使用Dcoker构建部署Java项目,发布到服务器 一、打包 我这里打包的是item-service这个module,clean-cpmpile-pa…

【深度学习每日小知识】Logistic Loss 逻辑回归

逻辑回归的损失函数 线性回归的损失函数是平方损失。逻辑回归的损失函数是对数损失,定义如下: L o g L o s s ∑ ( x , y ) ∈ D − y log ⁡ ( y ′ ) − ( 1 − y ) log ⁡ ( 1 − y ′ ) LogLoss\sum_{(x,y)\in D}-y\log(y)-(1-y)\log(1-y) LogLoss…

测试老鸟汇总,接口测试总结与用例编写,一文策底概全...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、为什么要做接口…

Java SE入门及基础(9)

if选择结构 1. 基本if选择结构 语法 if ( 条件 ){ // 如果条件满足,则执行代码块 //代码块 } 案例 从控制台输入一个整数,如果该数字小于 10 ,则输出 10 与该数字的差值。 流程图 代码实现 public class Example1 { public s…

Dart 空感知操作符:??

示例 写了如下代码: var str1 "hello"; var str2 "world"; var result str1 ?? str2.toUpperCase(); //如果str1不为空,则执行后面的语句 print(result); 代码可以正常执行,但是报了如下错误: Warnin…

【网络安全】Nessus部署自动更新和端口权限开放

文章目录 Nessus 自动更新配置Nessus服务端口开放Nessus profession 版本需要开放端口Sensor ProxyTenable Security Center (TSC)Tenable OT Security (TOT)Tenable OT Security Enterprise Manager (IEM)Tenable OT Security Industrial Core Platform (ICP)Tenable OT Secur…

kafka除了作为消息队列还能做什么?

Kafka 最初是为大规模处理日志而构建的。它可以保留消息直到过期,并让各个消费者按照自己的节奏提取消息。 与其之前的竞品不同,Kafka 不仅仅是一个消息队列,它还是一个适用于各种情况的开源事件流平台。 让我们回顾一下流行的 Kafka 用例。 …

基于集成学习算法XGBoost农作物产量可视化分析预测系统

文章目录 基于集成学习算法XGBoost农作物产量可视化分析预测系统一、项目简介二、开发环境三、项目技术四、功能结构五、功能实现模型构建封装类用于网格调参训练模型系统可视化数据请求接口模型评分 0.5*mse 六、系统实现七、总结 基于集成学习算法XGBoost农作物产量可视化分析…

中间人攻击如何进行防护

中间人攻击(Man-in-the-Middle Attack,简称 MITM 攻击)是一种常见的网络攻击方式,攻击者通过截获两个通信实体之间的通信数据,并在此基础上进行篡改、窃取或伪造等恶意行为。这种攻击方式因其攻击手段的隐蔽性和难以防…

2024 年1月12日最热NLP大模型论文:Transformers are Multi-State RNNs

揭秘Transformer的无限可能,Meta研究发现Transformer其实是多状态RNN 引言:重新定义Transformer的视角 在自然语言处理(NLP)的领域,Transformer架构自2017年提出以来,已经成为了一种主流的模型&#xff0…

呼吸道病毒感染后,为何会引发细菌性肺炎?气道和肠道微生物组改变是关键

谷禾健康 病毒-细菌合并或继发感染 引起呼吸道感染的病毒是导致全世界高发病率和死亡率的原因,数十年来通常发生在冬季。在冬天,空气干燥,那些可能含有病毒的飞沫可以在空气中停留更长时间,并可以进一步传播。此外人的免疫力在冬季…

“Frontiers”系列多本期刊分区下跌,1本SCI被踢,2本SCI升为Top,还可投吗?

近期,2023年中科院分区正式发布,不少学者都很关心期刊变动情况。此次分区更新中,Frontiers出版社旗下的医学期刊表现让人大跌眼镜。 据汇总来看,32本大类医学SCI期刊中,Frontiers of Hormone Research直接从原来的医学…

照片模糊如何变清晰不妨试试这款软件吧

很多人希望能把模糊的图片或照片变得很清晰,或者把一个只有几十KB的小图变成有几M大小的高清大图。一般来说,一张模糊或打了马赛克的图片本身很多细节信息就没有或被删除了,就像一本书缺了很多页,我们是可能百分百的还原出它原来的…

云服务器ECS_GPU云服务器_AIGC_弹性计算-阿里云

阿里云高性能云服务器60%单实例最大性能提升,35Gbps内网带宽,网络增强&通用型云服务器、本地SSD型云服务器、大数据型云服务器、GPU异构型云服务器,阿里云百科aliyunbaike.com分享阿里云高性能云服务器: 阿里云高性能云服务器…

小红书年终“礼物营销”玩法:种拔一体,实现品效破圈

恰逢年末,用户送礼需求旺盛,小红书推出“礼物季”,品牌们纷纷入局,话题上线18天浏览量破9亿。“礼物营销”覆盖全年营销节点,贯穿始终,礼赠场景下用户消费决策链路缩短,种拔一体,帮助…

【Golang】二进制字符串转换为数字

在本文中,我们将探讨如何使用 Go 语言将十六进制字符串转换为二进制字符串,将不定长整型补码字符串转换为数字,以及如何将 IEEE754 标准的单精度(32位)和双精度(64位)浮点数字符串转换为数字。最…

数据科学低代码工具思考2—现状分析

数据科学工具伴随着计算机技术的发展也在持续的演进。数据库、大数据以及人工智能等时代标志性技术的出现,对数据科学工具的能力也有了更高的要求。一般而言,工具发展的趋势都是首先会出现一个能够支持数据科学计算的开发框架,方便用户能够更…

GC2003七通道NPN 达林顿管,专为符合标准 TTL 而制造

GC2003 内部集成了 7 个 NPN 达林顿晶体管,连接的阵列,非常适合逻辑接口电平数字电路(例 如 TTL,CMOS 或PMOS 上/NMOS)和较高的电流/电压,如电灯电磁阀,继电器,打印机或其他类似的负…

PMP应试小技巧,赶紧码住!

虽然单靠应试技巧是万万不行的,但是在较好地掌握了项目管理知识的基础上,应试技巧可以使我们得到更高的分数。 1、一定要认真阅读答案的全部四个选项。千万不要看到某个选项是正确的,就不看其它选项,因为可能还有更正确的选项。P…