Linux内存管理:(九)内存规整

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

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

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

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 引言

伙伴系统以页面为单位来管理内存,内存碎片也是基于页面的,即由大量离散且不连续的页面组成的。从内核角度来看,出现内存碎片不是好事情,有些情况下物理设备需要大段的连续的物理内存,如果内核无法满足,则会发生内核错误。对于内存碎片化,需要重新规整一下,因此本文叫作内存规整,—些文献中称其为内存紧凑,它是为了解决内核碎片化而出现的一个功能

内核中去碎片化的基本原理是按照页面的可移动性将页面分组。迁移内核本身使用的物理内存的实现难度和复杂度都很大,因此目前的内核不迁移内核本身使用的物理页面。对于用户进程使用的页面,实际上通过用户页表的映射来访问。用户页表可以移动和修改映射关系,不会影响用户进程,因此内存规整是基于页面迁移实现的

2. 内存规整的基本原理

在 Linux 2.6.24 内核中集成了社区专家 Mel Gorman 的 Anti-fragmentation 补丁,其核心思想是把内存页面按照可移动、可回收、不可移动等特性进行分类。可移动的页面通常是指用户态程序分配的内存,移动这些页面仅仅需要修改页表映射关系,代价很低;可回收的页面是指不可以移动但可以释放的页面。按照这些类型分类页面后,就容易释放出大块的连续物理内存。

内存规整机制(如下图所示):有两个方向的扫描者,一个从zone头部向zone尾部方向扫描,查找哪些页面是可以迁移的;另一个从zone尾部向zone头部方面扫描,查找哪些页面是空闲页面。当这两个扫描者在zone中间碰头或者已经满足分配大块内存 的需求时(能分配出所需要的大块内存并且满足最低的水位要求),就可以退出扫描了。

在这里插入图片描述

Linux 内核中触发内存规整的途径

  • 手动触发。通过写1到 /proc/sys/vm/compact_memory 节点,会手动触发内存规整。它会扫描系统中所有的内存节点上的 zone,对每个 zone 都会做一次内存规整。
  • kcompactd 内核线程。和页面回收 kswapd 内核线程—样,每个内存节点会创建一个 kcompactd 内核线程,名称为”kcompactd0”“kcompactd1”等。
  • 直接内存规整。和页面回收—样,当页面分配器发现在低水位情况下无法满足页面分配时,会进入慢速路径。在慢速路径里,除了唤醒 kswapd 内核线程外,还会调用函数 __alloc_pages_direct_compact(),尝试整合出一大块空闲内存。

注意:页面回收是基于内存节点的,而内存规整机制是基于zone来进行扫描和规整的

3. 直接内存规整

内存规整的一个重要的应用场景是在分配大块连续物理内存(order> 1),低水位(WMARK_LOW)情况下分配失败时,唤醒 kswapd 内核线程,但依然无法分配出内存,因此调 用 __alloc_pages_direct_compact() 来压缩内存,并尝试分配出所需要的内存,这个过程如下图所示。

在这里插入图片描述

紧接着,__alloc_pages_direct_compact():

// 压缩内存,并尝试分配出所需要的内存
// gfp_mask: 传递给页面分配器的分配掩码
// order: 请求分配页面的大小,其大小为 2 的 order 次幂个物理页面
// alloc_flags: 页面分配器内部使用的分配标志位
// ac: 页面分配器内部使用的分配上下文描述符
// prio: 内存规整的优先级
// compact_result: 内存规整后返回的结果
static struct page *
__alloc_pages_direct_compact(gfp_t gfp_mask, unsigned int order,
		unsigned int alloc_flags, const struct alloc_context *ac,
		enum compact_priority prio, enum compact_result *compact_result)
{
	...

	// 遍历内存节点中所有的 zone ,在每个 zone 上进行内存规整
	*compact_result = try_to_compact_pages(gfp_mask, order, alloc_flags, ac,
									prio);

	...

	// 尝试分配内存
	page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);

	...

	return NULL;
}

__alloc_pages_direct_compact()->try_to_compact_pages():

enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
		unsigned int alloc_flags, const struct alloc_context *ac,
		enum compact_priority prio)
{
	...
	// 首先遍历内存节点中所有的 zone ,然后在每个 zone 上调用 compact_zone_order() 函数进行内存规整
	for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
								ac->nodemask) {
		...

		status = compact_zone_order(zone, order, gfp_mask, prio,
					alloc_flags, ac_classzone_idx(ac));
		...
	}

	return rc;
}

__alloc_pages_direct_compact()->try_to_compact_pages()->compact_zone_order():

// 初始化内部使用的 compact_control 数据结构,然后调用 compact_zone 进行内存规整
static enum compact_result compact_zone_order(struct zone *zone, int order,
		gfp_t gfp_mask, enum compact_priority prio,
		unsigned int alloc_flags, int classzone_idx)
{
	...
	struct compact_control cc = {
		.nr_freepages = 0,
		.nr_migratepages = 0,
		.total_migrate_scanned = 0,
		.total_free_scanned = 0,
		.order = order,
		.gfp_mask = gfp_mask,
		.zone = zone,
		.mode = (prio == COMPACT_PRIO_ASYNC) ?
					MIGRATE_ASYNC :	MIGRATE_SYNC_LIGHT,
		.alloc_flags = alloc_flags,
		.classzone_idx = classzone_idx,
		.direct_compaction = true,
		.whole_zone = (prio == MIN_COMPACT_PRIORITY),
		.ignore_skip_hint = (prio == MIN_COMPACT_PRIORITY),
		.ignore_block_suitable = (prio == MIN_COMPACT_PRIORITY)
	};
	...

	ret = compact_zone(zone, &cc);

	...

	return ret;
}

__alloc_pages_direct_compact()->try_to_compact_pages()->compact_zone_order()->compact_zone():

// 内存规整的核心函数,主要用于“兵分两路”扫描一个 zone,找出可以迁移的页面以及空闲页面。这两路“兵”会在 zone 的中间汇合,
// 然后调用页面迁移的接口函数进行页面迁移,最终整理出大块空闲页面
// zone:表示待扫描的 zone
// cc:表示内存规整中内部使用的控制参数(这个参数需要被调用者初始化)
static enum compact_result compact_zone(struct zone *zone, struct compact_control *cc)
{
	...
    // compaction_suitable() 主要根据当前的 zone 水位来判断是否需要进行内存规整
	ret = compaction_suitable(zone, cc->order, cc->alloc_flags,
							cc->classzone_idx);
    ...
    // 内存规整的核心处理部分
	// compact_finished() 函数用于判断内存规整是否可以结束了,内存规整结束的条件有两个:
	//	1. cc->migrate_pfn 和 cc->free_pfn 两个指针相遇(这两个指针从 zone 的一头一尾向中间方向运行)
	// 	2. 判断 zone 里面 order 对应的迁移类型的空闲链表是否有成员(zone->free_area[order].free_list
	//	   [MIGRATE_MOVABLE]),最好 order 对应的 free_area 链表中正好有成员,即有空闲页块,或者大于
	//     order 的空闲链表里有空闲页块,或者大于 pageblock_order 的空闲链表中有空闲页块。若对应的迁移
	//     类型中的空闲链表没有空闲对象,那么假设可以从其他迁移类型中“借”一些空闲块过来
	while ((ret = compact_finished(zone, cc)) == COMPACT_CONTINUE) {
    	...
        // isolate_migratepages() 用于扫描并且寻觅 zone 中可迁移的页面,可迁移的页面会被添加到 cc->
		// migratepages 链表中
		switch (isolate_migratepages(zone, cc)) {
        	...
        }
        
        // migrate_pages() 是页面迁移的核心函数,从 cc->migratepages 链表中获取页面,然后尝试迁移页面 
		err = migrate_pages(&cc->migratepages, compaction_alloc,
				compaction_free, (unsigned long)cc, cc->mode,
				MR_COMPACTION);
    }
    ...(代码逻辑如下图所示)
}

在这里插入图片描述

__alloc_pages_direct_compact()->try_to_compact_pages()->compact_zone_order()->compact_zone()->isolate_migratepages():

// 主要作用是扫描并且寻觅 zone 中可迁移的页面,扫描步长是按照页块大小来进行的
// zone:表示正在扫描的 zone
// cc:表示内存规整的内部使用的控制参数
static isolate_migrate_t isolate_migratepages(struct zone *zone,
					struct compact_control *cc)
{
	...
	// isolate_mode 表示分离模式,判断是否支持异步分离模式(ISOLATE_ASYNC_MIGRATE)
	const isolate_mode_t isolate_mode =
		(sysctl_compact_unevictable_allowed ? ISOLATE_UNEVICTABLE : 0) |
		(cc->mode != MIGRATE_SYNC ? ISOLATE_ASYNC_MIGRATE : 0);

	// cc->migrate_pfn 表示上次扫描结束时的页帧号,然后从这次 cc->migrate_pfn 开始扫描
	low_pfn = cc->migrate_pfn;
	// pageblock_start_pfn() 表示向页块起始地址对齐
	block_start_pfn = pageblock_start_pfn(low_pfn);
	// block_start_pfn 表示这次扫描的起始页帧号
	if (block_start_pfn < zone->zone_start_pfn)
		block_start_pfn = zone->zone_start_pfn;

	...
	// 以 block_end_pfn 为起始页帧号开始扫描,查找的步长以 pageblock_nr_pages 为单位
	// Linux 内核以页块为单位来管理页的迁移属性
	for (; block_end_pfn <= cc->free_pfn;
			low_pfn = block_end_pfn,
			block_start_pfn = block_end_pfn,
			block_end_pfn += pageblock_nr_pages) {

		...

		// pageblock_pfn_to_page() 函数返回这个页块中第一个物理页面的 page 数据结构
		page = pageblock_pfn_to_page(block_start_pfn, block_end_pfn,
									zone);
		...
		// 判断页块的迁移类型
		// 对于异步类型的内存规整,只支持迁移类型为可移动(MOVABLE)的页块
		// 对于同步模式的内存规整,要判断页块迁移类型。若当前的页块的迁移类型和请求页面的迁移类型不一致,那么会跳过这个页块
		if (!suitable_migration_source(cc, page))
			continue;

		// isolate_migratepages_block() 函数对页块里的页面执行分离任务
		low_pfn = isolate_migratepages_block(cc, low_pfn,
						block_end_pfn, isolate_mode);

		...
	}

	...
}

__alloc_pages_direct_compact()->try_to_compact_pages()->compact_zone_order()->compact_zone()->isolate_migratepages()->isolate_migratepages_block():

在这里插入图片描述

由上图可知,和页面迁移类似,有两类页面适合做内存规整:

  • 传统的LRU页面,如匿名页面和文件映射页面
  • 非LRU页面,即特殊的可以迁移的页面,如根据zsmalloc机制和virtio-balloon机制分配的页面

但是对于传统的LRU页面,并不是所有的页面都适合做内存规整,有一些特殊的情况我们需要考虑:

  • 在伙伴系统中的页面
  • 混合页面
  • 不在LRU链表中的页面
  • 被锁住的匿名页面
  • 对于异步模式来说,获取zone->zone_pgdat->lru_lock自旋锁失败的页面
  • 标记了PG_unevictable的页面不适合
  • 对于异步模式来说,正在回写中的页面(标记了PG_writeback的页面)不适合
  • 对于异步模式来说,没有定义mapping->a_ops->migratepage()方法的脏页不合适

上述这些特殊情况的LRU页面也不适合做内存迁移。

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

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

相关文章

Pytest 测试框架与Allure 测试报告——Allure2测试报告-L3

目录&#xff1a; allure2报告中添加附件-图片 Allure2报告中添加附件Allure2报告中添加附件&#xff08;图片&#xff09;应用场景Allure2报告中添加附件&#xff08;图片&#xff09;-Python代码示例&#xff1a;allure2报告中添加附件-日志 Allure2报告中添加附件&#xff…

Linux的权限(3)

目录 文件类型 ​d目录文件 -普通文件 l链接文件 b块设备文件 p管道文件 c字符设备文件 文件权限 目录权限 umask 粘滞位 Q1umask权限默认值664/775 Q2"可执行性"权限 Q3"删除"权限 Q4怎么共享一批文件 【1】粘滞位 【2】添加交互人员到所…

第八讲_ArkTS装饰器(五)

ArkTS装饰器&#xff08;五&#xff09; 1. Watch装饰器1.1 Watch装饰器的特点1.2 Watch装饰器使用示例 2. ArkTS装饰器总结 1. Watch装饰器 Watch用于对状态变量的监听。如果需要关注某个状态变量的值是否改变&#xff0c;可以使用Watch为状态变量设置回调函数。 何为状态变…

基于DUP的网络聊天室

基于UDP的网络聊天室的使用&#xff08;select&#xff09;完成的服务器端 #include<head.h> typedef struct de {char name[10];struct sockaddr_in cin;struct de* next; }*linklist; //创建节点 linklist a_creat() {linklist p(linklist)malloc(sizeof(struct de));…

Flink SQL

Flink SQL 来源&#xff1a;B站尚硅谷 sql-client准备 Table API和SQL是最上层的API&#xff0c;在Flink中这两种API被集成在一起&#xff0c;SQL执行的对象也是Flink中的表&#xff08;Table&#xff09;&#xff0c;所以我们一般会认为它们是一体的。Flink是批流统一的处理…

LSTM学习笔记

上一篇文章中我们提到&#xff0c;CRNN模型中用于预测特征序列上下文的模块为双向LSTM模块&#xff0c;本篇中就来针对该模块的结构和实现做一些理解。 Bidirectional LSTM模块结构如下图所示&#xff1a; 在Pytorch中&#xff0c;已经集成了LSTM模块&#xff0c;定义如下&…

Spring成长之路—Spring MVC

在分享SpringMVC之前&#xff0c;我们先对MVC有个基本的了解。MVC(Model-View-Controller)指的是一种软件思想&#xff0c;它将软件分为三层&#xff1a;模型层、视图层、控制层 模型层即Model&#xff1a;负责处理具体的业务和封装实体类&#xff0c;我们所知的service层、poj…

【开源】基于JAVA语言的中学生家校互联系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 课堂表现模块2.3 考试成绩模块2.4 家校留言模块2.5 校园通知模块 三、系统设计3.1 用例设计3.2 实体类设计3.2.1 课堂表现实体类设计3.2.2 考试成绩实体类设计3.2.3 家校留言实体类设计3.2.4 校园通知实…

AWS 专题学习 P8 (ECS、EKS、Lambda、CloudFront、DynamoDB)

文章目录 什么是 Docker&#xff1f;操作系统上的 DockerDocker 镜像存储Docker vs. Virtual MachinesDocker 入门AWS 中的 Docker Containers Management Amazon ECSEC2 Launch TypeFargate Launch TypeECS 的 IAM RolesLoad Balancer IntegrationsData Volumes (EFS)ECS Serv…

HCIA——20应用层:C/S、P2P、peer

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

产品经理学习-产品运营《用户运营策略》

⽤户画像与⽤户运营策略 什么是用户画像 对产品运营而言&#xff0c;用户画像就是对用户的各种特征贴上标签通过这些标签将用户分成不同的用户群体 为用户提供有针对性的服务。 制作用户画像是为了专注和精准 使产品的服务对象更加聚焦&#xff0c;更加专注&#xff1b;根据产…

红包封面免费送1000个,你设计,我出额度

相信最近大家或多或少都知道了吧&#xff0c;腾讯又又又给大家&#xff0c;准确的说是给一年勤奋的公众号/视频号博主一个福利 根据不同博主的粉丝、更新频度以及作品质量&#xff0c;给力博主们免费制作红包封面的福利 比如我这个号&#xff0c;有6000额度 那这6000个&#…

Python武器库开发-武器库篇之Fofa-API使用(四十六)

Python武器库开发-武器库篇之Fofa-API使用(四十六) FOFA&#xff08;FOcus Observation of Futures Assets&#xff09;是一款专业的网络资产搜索引擎&#xff0c;旨在帮助企业发现和评估网络上的潜在安全风险。FOFA的基本原理是通过搜索引擎的方式&#xff0c;按照关键词对互…

Civil 3D安装教程,免费使用,带安装包和工具,一分钟轻松搞的安装

前言 Civil 3D是一款面向基础设施行业的建筑信息模型&#xff08;BIM&#xff09;解决方案。它为基础设施行业的各类技术人员提供了强大的设计、分析以及文档编制功能&#xff0c;广泛适用于勘察测绘、岩土工程、交通运输、水利水电、市政给排水、城市规划和总图设计等众多领域…

【Spring 篇】MyBatis核心配置文件解密:数据之门的守护精灵

欢迎来到MyBatis的幕后花絮&#xff0c;今天我们将深入解析MyBatis的核心配置文件&#xff0c;这个神秘的数据之门的守护精灵。这份配置文件是连接你的应用程序和数据库之间的纽带&#xff0c;也是整个MyBatis舞台背后的幕后工作者。在这篇博客中&#xff0c;我们将揭开核心配置…

ES框架详解

ES框架详解 1、全文检索的介绍 ​ 那么对于一般的公司&#xff0c;初期是没有那么多数据的&#xff0c;所以很多公司更倾向于使用传统的数据库&#xff1a;mysql&#xff1b;比如我们要查找关键字”传智播客“&#xff0c;那么查询的方式大概就是:select * from table where …

vue3项目eslint配置、配置prettier(格式化配置)

文章链接: 全部配置链接 第一步:eslint配置、配置prettier(代码格式化):点击链接 (1) .eslint.cjs—eslint配置文件 (2).eslintignore—校验忽略文件 (3).prettierrc.json添加规则 (4).prettierignore忽略文件 prettierrc规范说明: npm install -D eslint-plugin-import…

修复uni-simple-router@2.0.7版本query参数null的bug

问题&#xff1a;query参数为null或者为空时&#xff0c;插件内部参数校验问题导致的会报错&#xff1a;TypeError: Cannot convert undefined or null to object at Function.keys 源码修改如下&#xff1a; 通过打补丁的方式修复query参数类型校验问题 1. 安装patch-packag…

SPI 动态服务发现机制

SPI&#xff08;Service Provier Interface&#xff09;是一种服务发现机制&#xff0c;通过ClassPath下的META—INF/services文件查找文件&#xff0c;自动加载文件中定义的类&#xff0c;再调用forName加载&#xff1b; spi可以很灵活的让接口和实现分离&#xff0c; 让API提…

Packet Tracer - 配置第 3 层交换和VLAN间路由

地址分配表 设备 接口 IP 地址/前缀 MLS VLAN 10 192.168.10.254 /24 MLS VLAN 10 2001:db8:acad:10::1/64 MLS VLAN 20 192.168.20.254 /24 MLS VLAN 20 2001:db8:acad:20::1/64 MLS VLAN 30 192.168.30.254/24 MLS VLAN 30 2001:db8:acad:30::1/64 MLS …