Linux内存管理:(十一)页面分配之慢速路径

news2024/11/20 14:27:53

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

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

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

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 水位管理和分配优先级

页面分配器是按照zone的水位来管理的,zone的水位分成3个等级,分别是高水位(WMARK_HIGH)、低水位(WMARK_LOW) 以及最低警戒水位(WMARK_MIN)。最低警戒水位下的内存是系统预留的内存,通常情况下普通优先级的分配请求是不能访问这些内存的,但是在特殊情况下是可以用来救急的。页面分配器可以通过分配掩码的不同来访问最低警戒水位以下的内存,如__GFP_HIGH__GFP_ATOMIC以及__GFP_MEMALLOC等,如下表所示:

在这里插入图片描述

页面分配器的zone水位管理流程如下图所示:

在这里插入图片描述

补充说明:

  • 页面分配器中的快速和慢速路径是以低水位线能否成功分配内存为分界线的
  • 在慢速路径上,首先唤醒kswapd内核线程,异步扫描LRU链表和回收页面
  • 随着kswapd内核线程不断地回收内存,zone中的空闲内存会越来越多,当zone水位重新返回高水位之上时,zone的水位平衡了,kswapd内核线程停止工作重新进入睡眠状态
  • “页面分配之快速路径”见:Linux内存管理:(一)伙伴系统-CSDN博客,下文将详细介绍慢速路径

2. __alloc_pages_slowpath() 函数

__alloc_pages_slowpath()函数是页面分配慢速路径中的核心函数,该函数分配页面的流程如下图所示:

在这里插入图片描述

相应的__alloc_pages_slowpath()函数注解如下所示:

// gfp_mask:表示调用页面分配器时传递的分配掩码
// order:表示需要分配页面的大小,大小为 2 的 order 次幂个连续物理页面
// ac:表示页面分配器内部使用的控制参数数据结构
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
						struct alloc_context *ac)
{
	// can_direct_reclaim 表示是否允许调用直接页面回收机制
	// 那些隐含了 __GFP_DIRECT_RECLAIM 标志位的分配掩码都会使用直接页面回收机制
	bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM;
	// costly_order 表示会形成一定的内存分配压力。PAGE_ALLOC_COSTLY_ORDER 定义为3,如当分配请求
	// order 为 4 时,即要分配 64KB 大小的连续物理内存,会给页面分配器带来一定的内存压力
	const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER;
	...
	// 检查是否在非中断上下文中滥用 __GFP_ATOMIC,使用 __GFP_ATOMIC 会输出一次警告
	// __GFP_ATOMIC 表示调用页面分配器的进程不能直接回收页面或者等待,调用者通常在中断上下文中。
	// 另外,__GFP_ATOMIC 是优先级比较高的分配行为,它允许访问部分的系统预留内存
	if (WARN_ON_ONCE((gfp_mask & (__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)) ==
				(__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)))
		gfp_mask &= ~__GFP_ATOMIC;

retry_cpuset:
	...
	// gfp_to_alloc_flags() 重新设置分配掩码 gfp_mask
	alloc_flags = gfp_to_alloc_flags(gfp_mask);

	// 重新计算首选推荐的 zone,因为我们可能在快速路径中修改了内存节点掩码或者使用 cpuset 机制做了修改。
	ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
					ac->high_zoneidx, ac->nodemask);
	...

	if (alloc_flags & ALLOC_KSWAPD)
		// 唤醒 kswapd 内核线程
		wake_all_kswapds(order, gfp_mask, ac);

	// 因为在 gfp_to_alloc_flags() 函数中调整了分配掩码 alloc_flags,所以将最低警戒水位(ALLOC_WMARK_MIN)
	// 作为判断条件。尝试以最低警戒水位为条件,判断是否能分配内存
	page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
	if (page)
		goto got_pg;

	// 若以最低警戒水位为条件还不能分配成功,在 3 种情况下可以考虑尝试先调用直接内存规整机制来解决
	// 页面分配失败的问题:
	//  1. 允许调用直接页面回收机制
	//  2. 高成本的分配需求 costly_order。这时,系统可能有足够的空闲内存,但是没有满足分配需求的连续页面,
	//     调用内存规整机制可能能解决这个问题。或者对于请求,分配不可迁移的多个连续物理页面(即order大于0)
	//  3. 不允许访问系统预留内存。gfp_pfmemalloc_allowed() 表示是否允许访问系统预留的内存,若返回 ALLOC_NO_WAIERMARKS,
	//     表示不用考虑水位;若返回0,表示不允许访问系统保留的内存
	// 同时满足上述 3 种情况,才会调用 __alloc_pages_direct_compact() 函数尝试内存规划
	if (can_direct_reclaim &&
			(costly_order ||
			   (order > 0 && ac->migratetype != MIGRATE_MOVABLE))
			&& !gfp_pfmemalloc_allowed(gfp_mask)) {
		page = __alloc_pages_direct_compact(gfp_mask, order,
						alloc_flags, ac,
						INIT_COMPACT_PRIORITY,
						&compact_result);
		...
	}

retry:
	// 确保 kswapd 内核线程不会进入睡眠,因此我们又重新唤醒它
	if (alloc_flags & ALLOC_KSWAPD)
		wake_all_kswapds(order, gfp_mask, ac);

	// __gfp_pfmemalloc_flags() 判断是否允许访问系统预留的内存,若返回 0,表示不允许访问预留内存
	reserve_flags = __gfp_pfmemalloc_flags(gfp_mask);
	if (reserve_flags)
		alloc_flags = reserve_flags;

	// 原本的 alloc_flags 设置了 ALLOC_CPUSET,当 gfp_mask 设置了 __GFP_AaTOMIC 时会清除 ALLOC_CPUSET,
	// 表示调用者在中断上下文中。另外,reserve_flags 表示运行访问系统预留的内存。这两种情况下,我们重新计算
	// 首选推荐的 zone。
	if (!(alloc_flags & ALLOC_CPUSET) || reserve_flags) {
		ac->nodemask = NULL;
		ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
					ac->high_zoneidx, ac->nodemask);
	}

	// 重新调用 get_page_from_freelist() 尝试一次页面分配,若成功则返回退出
	page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
	if (page)
		goto got_pg;

	// 若调用者不支持直接页面回收,那么我们没有其他可以做的了,跳转到 nopage 处
	if (!can_direct_reclaim)
		goto nopage;

	// 若当前进程的进程描述符设置了 PF_MEMALLOC,那么会在 __gfP_pfmemalloc_flags() 函数中返回
	// ALLOC_NO_WATERMARKS,表示完全忽略水位条件,可以访问系统全部的预留内存。在 get_page_from_freelist()
	// 不用检查 zone 的水位即可直接分配内存,既然忽略水位的情况下都不能分配出物理内存,那只能跳转到 nopage 标签处。
	if (current->flags & PF_MEMALLOC)
		goto nopage;

	// 调用直接页面回收机制。经过一轮的直接内存规整之后会尝试分配内存,若成功,则返回 page 数据结构
	page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac,
							&did_some_progress);
	if (page)
		goto got_pg;

	// 调用直接内存规整机制。经过一轮的直接内存规整之后会尝试分配内存,若成功,则返回 page 数据结构
	page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
					compact_priority, &compact_result);
	if (page)
		goto got_pg;

	...
	// 若要分配大块的物理内存并且分配掩码中没有设置 __GFP_RETRY_MAYFAIL,那说明分配行为中不允许我们继续重试
	if (costly_order && !(gfp_mask & __GFP_RETRY_MAYFAIL))
		goto nopage;

	// should_reclaim_retry() 判断是否需要重试直接页面回收机制,若返回 0 则表示需要重试
	// did_some_progress 表示已经成功回收的页面数量
	if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
				 did_some_progress > 0, &no_progress_loops))
		goto retry;

	// should_compact_retry() 判断是否需要重试内存规整
	if (did_some_progress > 0 &&
			should_compact_retry(ac, order, alloc_flags,
				compact_result, &compact_priority,
				&compaction_retries))
		goto retry;

	// check_retry_cpuset() 判断是否重新尝试新的 cpuset,这个需要使能 CONFIG_CPUSETS 功能
	if (check_retry_cpuset(cpuset_mems_cookie, ac))
		goto retry_cpuset;

	// 所有的 cpuset 都重新尝试过后,若还是没法分配出所需要的内存,那么将使用 OOM killer 机制
	// __alloc_pages_may_oom() 函数会调用 OOM killer 机制来终止占用内存比较多的进程,从而释放出一些内存
	page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
	if (page)
		goto got_pg;

	// 如果被终止的进程是当前进程并且 alloc_flags 为 ALLOC_OOM 或者 gfp_mask 为 __GFP_NOMEMALLOC,那么跳转
	// 到 nopage 标签处
	if (tsk_is_oom_victim(current) &&
	    (alloc_flags == ALLOC_OOM ||
	     (gfp_mask & __GFP_NOMEMALLOC)))
		goto nopage;

	// did_some_progress 表示我们刚才终止进程后释放了一些内存,因此跳转到 retry 标签处重新尝试分配内存
	if (did_some_progress) {
		no_progress_loops = 0;
		goto retry;
	}

nopage:
	...
	// 若 gfp_mask 设置了 __GFP_NOFAIL,表示分配不能失败,那么只能想尽办法来重试
	if (gfp_mask & __GFP_NOFAIL) {
		...
		// 又一次尝试分配内存
		page = __alloc_pages_cpuset_fallback(gfp_mask, order, ALLOC_HARDER, ac);
		if (page)
			goto got_pg;

		...
	}
// 若 gfp_mask 没有设置 __GFP_NOFAIL,只能调用 warn_alloc() 来宣告这次内存分配失败了
fail:
	warn_alloc(gfp_mask, ac->nodemask,
			"page allocation failure: order:%u", order);
got_pg:
	return page;
}

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

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

相关文章

0202-2-存储器管理

第四章:存储器管理 存储器的层次结构 多层结构的存储系统 存储器的多层结构 CPU寄存器主存辅存 可执行存储器 寄存器和主存的总称访问速度快,进程可以在很少的时钟周期内用一条load或store指令完成存取。 主存储器与寄存器 高速缓存和磁盘缓存 程序的装入和链…

《金融时报》:直面“雪球”风波 究竟影响几何?

“他们给我推荐的时候说是只要市场不大跌,我就能按照年化20%获得收益,当时我看大盘走势,也认为跌那么多的概率不大。”李先生告诉《金融时报》记者,他当初被银行客户经理推荐“雪球”产品并头脑一热买了的时候,以为按照…

springboot集成 mysql快速入门demo

一、mysql环境搭建 采用docker-compose搭建,配置如下: docker-compose.yml version: 3 services:mysql:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7 # 原镜像mysql:5.7container_name: mysql_3306 …

C++入坑基础知识点

当学习了C语言之后,很多的小伙伴都想进一步学习C,但两者有相当一部分的内容都是重叠的,不知道该从哪些方面开始入门C,这篇文章罗列了从C到C必学的入门知识,学完就算是踏入C的大门了。 1. 命名空间 写C的时候&#xff…

LeetCode 热题 100 | 链表(上)

目录 1 基础知识 1.1 空指针 1.2 结构体 1.3 指针访问 1.4 三目运算符 2 160. 相交链表 3 206. 反转链表 4 234. 回文链表 菜鸟做题第三周,语言是 C 1 基础知识 1.1 空指针 使用 nullptr 来判断是否为空指针: if (headA nullptr) …

鸿蒙开发有必要学吗?看完这篇再决定吧

在科技的潮流中,每一次新操作系统的诞生都是对旧秩序的挑战与新机遇的孕育。鸿蒙操作系统的出现,无疑是近年来科技界最引人注目的事件之一。自华为于2019年正式推出鸿蒙系统以来,这一我们自主研发的操作系统不仅在国内引起巨大反响&#xff0…

常见的6种软件测试用例设计方法

常见的软件测试用例设计方法,个人认为主要是下面这6种: 流程图法(也叫场景法)等价类划分法边界值分析判定表正交法错误推测法 这6种常见方法中,我分别按照定义、应用场景、使用步骤、案例讲解这4个部分进行讲解。 所…

MySQL查询数据(十)

MySQL查询数据(十) 一、SELECT基本查询 1.1 SELECT语句的功能 SELECT 语句从数据库中返回信息。使用一个 SELECT 语句,可以做下面的事: **列选择:**能够使用 SELECT 语句的列选择功能选择表中的列,这些…

不一样的味觉体验:精酿啤酒与烤肉的绝妙搭配

在繁华的都市生活中,人们总是在寻找那份与众不同的味觉享受。当夏日的微风轻轻拂过,你是否想过,与三五好友围坐在一起,拿着Fendi Club啤酒与烤肉的绝妙搭配,畅谈生活点滴,感受那份惬意与自在? F…

2401Idea用GradleKotlin编译Java控制台中文出乱码解决

解决方法 解决方法1 在项目 build.gradle.kts 文件中加入 tasks.withType<JavaCompile> {options.encoding "UTF-8" } tasks.withType<JavaExec> {systemProperty("file.encoding", "utf-8") }经测试, 只加 tasks.withType<…

基于单片机的智能燃气灶控制系统设计

摘要&#xff1a;针对传统燃气灶存在不能防干烧、不能进行温度检测、不能进行火力自动调节等问题&#xff0c;设计了一种基于单片机控制的智能燃气灶&#xff0c;它通过单片机进行控制&#xff0c;由开关模块、测温模块、语音播报模块、火力控制模块和防空烧模块五个模块组成&a…

Lazysysadmin

信息收集 # nmap -sn 192.168.1.0/24 -oN live.port Starting Nmap 7.94 ( https://nmap.org ) at 2024-01-30 21:10 CST Nmap scan report for 192.168.1.1 (192.168.1.1) Host is up (0.00075s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nma…

Flutter解析后台发来的jwt字段数据,了解jwt是否过期

前言 了解JWT是什么可以看这一篇博文 JWT&#xff08;JSON Web Token&#xff09;详解以及在go-zero中配置的方法-CSDN博客 流程 采用jwt_decoder库 添加至pubspec.yaml jwt_decoder: ^2.0.1 解析字段 查看是否过期 获取过期时间和token颁发的年龄

面试八股文(2)

文章目录 1.ArrayList和LinkedList区别2.HashMap和HashTable区别3.线程的创建方式4.Java中异常处理5.Java序列化中某些字段不想进行序列化&#xff1f;6.Java序列化7.静态方法和实例方法8.List、Set、Map三者区别9.ArrayList和Vector区别10.HashMap和HashSet区别 1.ArrayList和…

【C++】 C++入门— 基于范围的 for 循环

C 基于范围的for循环1 使用样例2 使用条件3 完善措施 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 基于范围的for循环 1 使用样例 使用for循环遍历数组&#xff0c;我们通常这么写&#xff1a; …

博云科技与中科可控全面合作,探索前沿金融科技新机遇

2024年1月26日&#xff0c;博云科技与中科可控在昆山高新区成功举办合作签约仪式。昆山市委常委、昆山高新区党工委书记孙道寻、中科可控董事长聂华、博云科技董事长花磊等领导出席了本次签约仪式。 中科可控将利用其在先进计算和智造领域的优势&#xff0c;为博云科技提供有关…

el-table添加(取消,确认)

点击添加输入添加项&#xff0c;但是不想添加了&#xff0c;就点击取消&#xff0c;但是在打开之前输入的数据还在&#xff0c;在点击取消的时候数据清空 页面 数据没有清空的时候&#xff0c;点击取消之后&#xff0c;在打开数据还在 数据清空之后&#xff0c;在打开数据是没…

寒假作业2月2号

第一章 命名空间 一&#xff0e;选择题 1、编写C程序一般需经过的几个步骤依次是&#xff08;C &#xff09; A. 编辑、调试、编译、连接 B. 编辑、编译、连接、运行 C. 编译、调试、编辑、连接 D. 编译、编辑、连接、运行 2、所谓数据封装就是将一组数据和与这组数据有关…

为期 90 天的免费数据科学认证(KNIME)

从 2 月 1 日开始&#xff0c;KNIME 官方将免费提供 KNIME 认证 90 天。 无论您是刚刚迈入数据科学领域、已经掌握了一些技术&#xff0c;还是正在构建预测模型&#xff0c;都可以参加为期 90 天的 KNIME 认证挑战赛&#xff0c;完成尽可能多的认证并获得数据科学技能免费认证。…

嵌入式系统学习(一)

嵌入式现状&#xff08;UP经历&#xff09;&#xff1a; 大厂的招聘要求&#xff1a; 技术栈总结&#xff1a; 产品拆解网站&#xff1a; 52audio 方案查询网站iotku,我爱方案网&#xff0c; 主要元器件类型&#xff1a;