Linux内存管理:(六)页交换算法

news2024/11/15 15:24:56

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

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

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

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 引言

在Linux操作系统中,当内存充足时,内核会尽量多地使用内存作为文件缓存(page cache), 从而提高系统的性能。文件缓存页面会添加到文件类型的LRU链表中;当内存紧张时,文件缓存页面会被丢弃,或者把修改的文件缓存页面回写到存储设备中,与块设备同步之后便可释放出物理内存。现在的应用程序转向内存密集型,无论系统中有多少物理内存都是不够用的,因此Linux操作系统会使用存储设备作为交换分区,内核将很少使用的内存换出到交换分区,以便释放出物理内存,这个机制称为页交换(swapping),这些处理机制统称为页面回收(page reclaim)

在最近几十年操作系统的发展过程中,出现了很多页面交换算法,其中每个算法都有各自的优点和缺点。Linux内核中采用的页交换算法主要是经典LRU链表算法第二次机会(second chance)法

2. 经典LRU链表算法

LRU是Least Recently Used的缩写,意为最近最少使用。根据局部性原理,LRU假定最近不使用的页面在较短的时间内也不会频繁使用。在内存不足时,这些页面将成为被换出的候选者。内核使用双向链表来定义LRU链表,并且根据页面的类型将LRU链表分为LRU_ANON和LRU_FILE。每种类型根据页面的活跃性分为活跃LRU链表和不活跃LRU链表,所以内核中一共有如下5个LRU链表:

// 定义了各种 LRU 链表的类型
enum lru_list {
	// 不活跃匿名页面链表
    LRU_INACTIVE_ANON = LRU_BASE,
    // 活跃匿名页面链表
	LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
    // 不活跃文件映射页面链表
	LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
    // 活跃文件映射页面链表
	LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
    // 不可回收页面链表
	LRU_UNEVICTABLE,
	NR_LRU_LISTS
};

LRU链表之所以要分成这样,是因为当内存紧缺时总是优先换出文件映射的文件缓存页面 (LRU_FILE链表中的页面),而不是匿名页面。因为大多数情况下,文件缓存页面不需要被回写到磁盘,除非页面内容修改了(称为脏页),而匿名页面总是要在写入交换分区之后,才能被换出。LRU链表按照内存节点配置,也就是说,每个内存节点中都有一整套LRU链表,因此内存节点的描述符数据结构(pglist_data)中有一个成员lruvec指向这些链表。枚举类型变量lru_list 列举出上述各种LRU链表的类型,lruvec数据结构中定义了上述各种LRU类型的链表:

// 定义了各种 LRU 链表
struct lruvec {
	struct list_head		lists[NR_LRU_LISTS];
	...
};

// 内存节点的数据结构
typedef struct pglist_data {
	// 每个内存节点中都有一整套 LRU 链表,由 lruvec 指向
	struct lruvec		lruvec;
} pg_data_t;

万事从图说起,经典LRU链表算法如下图所示:

在这里插入图片描述

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

将页面加入 LRU 链表:

static void __lru_cache_add(struct page *page)
{
	// 这里使用了页向量数据结构,借助一个数组来保存特定数目的页,可以对这些页面执行同样的操作
	// 页向量会以“批处理的方式”执行,比单独处理一个页面的方式效率要高
	struct pagevec *pvec = &get_cpu_var(lru_add_pvec);

	get_page(page);
	// pagevec_add() 函数首先往 pvec->pages[] 数组里添加页面,
	// 如果没有空间了,则调用 __pagevec_lru_add() 函数把原有的页面添加到 LRU 链表中
	if (!pagevec_add(pvec, page) || PageCompound(page))
		__pagevec_lru_add(pvec);
	put_cpu_var(lru_add_pvec);
}

void lru_cache_add(struct page *page)
{
	...
	__lru_cache_add(page);
}

lru_to_page(&lru_list)list_del(&page->lru)函数的组合用于从LRU链表中获取页面。其中,lru_to_page()的实现如下:

#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))

lru_to_page()使用了(head)->prev,表示从链表的末尾获取页面。因此,LRU链表实现了 FIFO算法。最先进入LRU链表的页面在LRU中的时间会越长,老化时间也越长。

在系统执行过程中,页面总是在活跃LRU链表和不活跃LRU链表之间转移,不是每次访问内存页面都会发生这种转移,而是发生的时间间隔比较长。随着时间的推移,这会导致—种热平衡,最不常用的页面将慢慢移动到不活跃LRU链表的末尾,这些页面正是页面回收中最合适的候选者。

3. 第二次机会法

当系统内存短缺时,LRU链表尾部的页面将会离开并被换出。当系统再需要这些页面时,这些页面会重新置于LRU链表的开头。显然,这个设计不是很巧妙,在换出页面时,没有考虑该页面是频繁 使用的,还是很少使用的。也就是说,频繁使用的页面依然会因为在LRU链表末尾而被换出

第二次机会法的改进是为了避免把经常使用的页面置换出去,设置了一个访问状态位(硬件控制的位,PTE_YOUNG),所以要检查页面的访问位。如果访问位是0,就淘汰这个页面;如果访问位是1,就给它第二次机会,并选择下一个页面来换出。当该页面得到第二次机会时,它的访问位被清零,如果该页面在此期间再次被访问过,则访问位设置为1。

Linux内核使用下面这两个标志位来是实现第二次机会法:

  • PG_active:表示该页面是否活跃
  • PG_referenced:表示该页面是否被引用过

mark_page_accessed() 函数将页面标记为活跃:

  • 如果 PG_active==0 && PG_referenced==1,则把该页面加入活跃LRU链表,并设置 PG_active=1,清除PG_reference 标志位
  • 如果 PG_active==0,则设置 PG_referenced 标志位

在扫描不活跃 LRU 链表时,page_check_references() 这个函数会被调用:

// page 表示要处理的物理页面的page数据结构
// sc 表示内部用来控制页面扫描的数据结构
static enum page_references page_check_references(struct page *page,
						  struct scan_control *sc)
{
	...

	// page_referenced() 检查该页面访问、引用了多少个PTE(referenced_ptes)
	referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup,
					  &vm_flags);
	// TestClearPageReferenced() 函数返回该页面 PG_referenced 标志位的值(referenced_page),并且清除该标志
	referenced_page = TestClearPageReferenced(page);

	...

	if (referenced_ptes) {
		...
		// 在内存短缺的情况下,kswapd 巧妙地释放了短时间内只访问一次的大量文件缓存
		SetPageReferenced(page);

		// referenced_ptes > 1 表示那些第一次在不活跃LRU链表中的共享文件映射页面(共享文件缓存),
		// 它们应该晋升到活跃 LRU 链表中,因为它们应该在活跃 LRU 链表中多待一段时间,以便其他用户可以再次访问到。
		if (referenced_page || referenced_ptes > 1)
			return PAGEREF_ACTIVATE;

		...
	}

	...
}

page_referenced() 函数用于判断页面是否被访问过,并返回引用的PTE的个数,即访问引用这个页面的用户进程空间虚拟页面的个数,核心思想是利用 RMAP 系统来统计访问、引用 PTE 的用户个数:

int page_referenced(struct page *page,
		    int is_locked,
		    struct mem_cgroup *memcg,
		    unsigned long *vm_flags)
{
	...
	// 定义 rmap_one() 函数的指针
	struct rmap_walk_control rwc = {
		.rmap_one = page_referenced_one,
		.arg = (void *)&pra,
		.anon_lock = page_lock_anon_vma_read,
	};

	*vm_flags = 0;
	// 判断 page->_mapcount 是否大于或等于 0
	if (!page_mapped(page))
		return 0;

	// 判断 page->mapping 是否有地址空间映射
	if (!page_rmapping(page))
		return 0;

	...

	// 遍历所有映射该页面的 PTE
	rmap_walk(page, &rwc);
	*vm_flags = pra.vm_flags;

	...

	return pra.referenced;
}

4. 页交换算法的优化

即使第二次机会算法可以尽可能地避免把经常使用地页面置换出去,但是实际上,一些场景下,某些页面经常被访问,但是在下一次访问之前在不活跃LRU链表中回收并释放了它们,因此又必须从存储系统中读取这些内容缓存页面,这会产生颠簸(thrashing)现象。

在学术界和Linux内核社区,页面回收算法的优化一直没有停止过,其中 Refault Distance 算法在Linux 3.5内核中加入,作者是社区专家 Johannes Weiner,该算法目前只针对页面高速缓存类型的页面

Refault Distance 的概念:第一次访问内容缓存称为fault,第二次访问该页称为refault。内容缓存页面第一次被移出LRU链表并回收的时刻称为E,第二次再访问该页面的时刻称为R,那么R-E的时间里需要移动的页面个数称为Refault Distance。

Refault Distance概念再加上第一次访问的时刻,可以用一个公式来概括第一次和第二次访问的间隙(readdistance)。

在这里插入图片描述

如果页面想一直保持在LRU链表中,那么read_distance不应该比内存的大小还大;否则,该页面永远会被移出LRU链表。因此,下式成立:

在这里插入图片描述

换句话说,Refault Distance可以理解为不活跃LRU链表的“财政赤字”:

  • 如果不活跃LRU 链表的长度至少再延长到Refault Distance,就可以保证该内容缓存在第二次访问之前不会被移出 LRU链表并释放内存;
  • 否则,就要把该内容缓存重新加入活跃LRU链表加以保护,以防颠簸。

在理想情况下,内容缓存的平均访问间隙要大于不活跃LRU链表的大小、小于总的内存大小。Refault Distance 算法如下图所示:

在这里插入图片描述

  • T0时刻表示第一次访问一个内容缓存。这时会调用 add_to_page_cache_lru() 函数分配一个shadow来存储zone->inactive_age(inactive_age原子变量成员用于记录文件缓存不活跃LRU链表中的移出操作和激活操作的计数)值。每当有页面被升级为活跃LRU链表中的页面时,zone->inactive_age值会加1;每当有页面被移出不活跃LRU链表时,zone->inactive_age值也加1
  • T1时刻,该页面被移出LRU链表并从LRU链表中回收释放, 因此把当前T1时刻的zone->inactive_age的值编码存放到 shadow 中
  • T2时刻,第二次访问该页面,因此要计算Refault Distance,Refault Distance=T2-T1,如果Refault Distance≤NR_active, 说明该内容缓存极有可能在下一次读时已经被移出LRU链表,因此要人为地激活该页面并且将其加入活跃LRU链表中

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

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

相关文章

【AI】CycleGan对抗生成网络遥感影像生成地图效果测试

今天看到一个有趣的项目,CycleGan对抗生成网络把马生成成斑马,还有一个测试用例是用遥感影像生成平面地图的效果,效果如下图所示,我大学是遥感专业,看到遥感影像就触动了我的原神,于是原神启动,…

@Transactional 事务注解

第一、先简单介绍一下Spring事务的传播行为 所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量&…

C++学习笔记——返回对象

一、返回对象 当我们说一个函数返回对象时,意味着该函数的返回值是一个对象。这种情况下,函数可以通过创建对象的副本、返回对象的引用或者返回对象的指针来实现。 返回对象的副本: 当一个函数返回对象的副本时,函数内部会创建一…

VSCode使用MinGW编译器,配置C/C++环境

目录 一、安装VSCode 二、安装MinGW编译器 1、配置环境变量 2、测试配置是否成功 三、配置VSCode 1、安装所需扩展 2、新建代码存放文件夹 3、添加配置文件 4、配置文件内容 (1)c_cpp_properties.json (2)launch.json …

11 个 Python全栈开发工具集

前言 以下是专注于全栈开发不同方面的 Python 库;有些专注于 Web 应用程序开发,有些专注于后端,而另一些则两者兼而有之。 1. Taipy Taipy 是一个开源的 Python 库,用于构建生产就绪的应用程序前端和后端。 它旨在加快应用程序开发&#xf…

数字战场上的坚固屏障:雷池社区版(WAF)

黑客的挑战 智能语义分析算法: 黑客们常利用复杂技术进行攻击,但雷池社区版的智能语义分析算法能深入解析攻击本质,即使是最复杂的攻击手法也难以逃脱。 0day攻击防御: 传统防火墙难以防御未知攻击,但雷池社区版能有效…

云卷云舒:kubernetes简介

Kubernetes是由google公司在2014年发布的一款开源的容器编排引擎,用于容器化应用程序的自动化部署、扩展与管理。它能够编排多种容器任务,涵盖虚拟机集群管理、负载均衡以及网络流量分配等等。2017年,aws、微软云、阿里云等等著名的云计算公司…

用Linux的视角来理解缓冲区概念

缓冲区的认识 缓冲区(buffer)是存储数据的临时存储区域。当我们用C语言向文件中写入数据时,数据并不会直接的写到文件中,中途还经过了缓冲区,而我们需要对缓冲区的数据进行刷新,那么数据才算写到文件当中。…

Js-基础语法(二)

运算符 赋值运算符 赋值运算符:对变量进行赋值的运算符 已经学过的赋值运算符: 将等号右边的值赋予给左边, 要求左边必须是一个容器 其他赋值运算符: - */% 使用这些运算符可以在对变量赋值时进行快速操作 一元运算符 众多的 JavaScrip…

10个提高 Python Web 开发效率的VS Code插件

VS Code具有灵活、便捷和丰富的可用插件库,是Web开发人员中非常受欢迎的代码编辑器。 本文介绍10个VS Code插件,它们可以提高你作为Web开发人员的工作效率。 1. Live Preview Live Preview插件支持在VS Code的小型浏览器中查看网站。因此,无…

40-特殊运算符delete,new,.getDate,.setDate,运算符优先级

1.delete删除. 数组 // 可以删除数组元素,可以删除对象键值对// 删除数组的值,数组长度保持不变// 删掉的值变成emptyvar arr [1,2,3,4,5];delete arr[0];console.log(arr); 对象 var obj {"a":"aa","b":"bb&quo…

LeNet-5(fashion-mnist)

文章目录 前言LeNet模型训练 前言 LeNet是最早发布的卷积神经网络之一。该模型被提出用于识别图像中的手写数字。 LeNet LeNet-5由以下两个部分组成 卷积编码器(2)全连接层(3) 卷积块由一个卷积层、一个sigmoid激活函数和一个…

数据结构及单链表例题(下)

上次我们已经了解了单链表的数据结构定义以及创建单链表的两种方法,这节介绍几道例题. 文章目录 前言 一、已知L为带头结点的单链表,请依照递归思想实现下列运算 二、单链表访问第i个数据节点 三、在第i个元素前插入元素e 四、删除第i个结点 五、查找带头结点单链表倒数第…

Vivado开发FPGA使用流程、教程 verilog(建立工程、编译文件到最终烧录的全流程)

目录 一、概述 二、工程创建 三、添加设计文件并编译 四、线上仿真 五、布局布线 六、生成比特流文件 七、烧录 一、概述 vivado开发FPGA流程分为创建工程、添加设计文件、编译、线上仿真、布局布线(添加约束文件)、生成比特流文件、烧录等步骤&a…

公司内部核心文件数据\资料防泄密软件系统,防止未经授权文件、文档、图纸、源代码、音视频...等数据资料外泄,自动智能透明加密保护!

为了保护公司内部的核心文件和数据资料,防止未经授权的外泄,使用自动智能透明加密保护软件系统是非常重要的。 这样的系统可以通过以下方式实现防泄密: 自动智能加密:该系统可以对公司内部的核心文件和数据资料进行自动智能加密&…

夏目友人帐OVA:和猫咪老师的初次跑腿、曾几何时下雪之日 2013.12.15

夏目友人帐OVA 1、和猫咪老师的初次跑腿 / ニャンコ先生とはじめてのおつかい2、曾几何时下雪之日 / いつかゆきのひに 1、和猫咪老师的初次跑腿 / ニャンコ先生とはじめてのおつかい 和夏目一起外出的途中,猫咪老师因追蜻蜓遇到了一对迷路的龙凤胎兄妹。猫咪老师不…

僵尸毁灭工程手动存档工具

介绍 这是一个可以对僵毁游戏存档进行备份的小工具,其基本原理是对僵毁存档中数以万计的小文件做哈希值计算并保存下来,下一次备份时再对存档文件进行哈希值计算,每次备份只对两次计算结果中存在差异的文件进行复制与替换从而忽略掉大部分未…

汽车IVI中控开发入门及进阶(十二):手机投屏

前言: 汽车座舱有车载中控大屏、仪表/HUD多屏的显示能力,有麦克风/喇叭等车载环境更好的音频输入输出能力,有方控按键、旋钮等方便的反向控制输入能力,还有高精度的车辆数据等。但汽车座舱中控主机硬件计算能力升级迭代周期相对较长,汽车的应用和服务不够丰富。现在很多汽…

电脑USB接口不同颜色的含义

当你看到笔记本电脑或台式机的USB端口时,你会发现USB端口的颜色很多;这些颜色可不只是为了好看,实际上不同颜色代表着不同的性能,那么这些带颜色的USB端口都是什么含义呢,下面就具体介绍下不同颜色代表的含义。-----吴…

Oracle regexp_substr

select regexp_substr(123|456|789, [^|], 1, 2) from dual;