Linux内存管理:(七)页面回收机制

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

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

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

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 触发页面回收

Linux内核中触发页面回收的机制大致有3个:

  • 直接页面回收机制。在内核态里调用页面分配接口函数alloc_pages()分配物理页面时,由于系统内存短缺,不能满足分配请求,因此内核会直接自陷到页面回收机制,尝试回收内存来解决当前的燃眉之急,这称为直接页面回收。
  • 周期性回收内存机制。这是kswapd内核线程的工作职责。当内核路径调用alloc_pages() 分配物理页面时,由于系统内存短缺,没法在低水位情况下分配出内存,因此会唤醒kswapd内核线程来异步回收内存。
  • slab收割机(slab shrinker)机制。这是用来回收slab对象的。当内存短缺时,直接页 面回收和周期性回收内存两种机制都会调用slab收割机机制来回收slab对象。slab机制分配的内存主要用于slab对象和kmalloc接口,也可用于内核空间的内存分配。

注意:

  • 直接回收内存的进程主体是调用者本身。
  • 直接回收内存是同步回收,这会阻塞调用者进程的执行。
  • kswapd本身是内核线程,它和调用者的关系是异步的。如test进程尝试调用alloc_pages()来分配内存,当发现在低水位情况下无法分配出内存时,它唤醒kswapd内核线程。这时,kswapd 内核线程就开始执行页面回收工作了。test进程会继续尝试其他办法来分配内存,如调用直接回收内存机制。

页面回收机制的主要调用路径如下图所示:

在这里插入图片描述

下面将根据源码围绕这张图中的关键部分进行讲解。

2. kswapd内核线程

kswapd是Linux内核中一个非常重要的内核线程,它负责在内存不足的情况下回收页面。kswapd内核线程初始化时会为系统中每个NUMA内存节点创建一个名为“kswapd%d”的内核线程。

触发周期性回收内存机制的逻辑如下所示:

在这里插入图片描述

balance_pgdat()函数是回收页面的主函数,其主体函数是一个很长的while循环,简化后的代码框架如下:

static int balance_pgdat(pg_data_t *pgdat, int order, int classzone_idx)
{
	...

restart:
	// 使用 sc.priority 表示页面扫描粒度或者优先级
	sc.priority = DEF_PRIORITY;
	do {
		...
		// 检查这个内存节点中是否有合格的 zone,其水位高于高水位并且能分配出 2 的 sc.priority 次方个连续的物理页面
		balanced = pgdat_balanced(pgdat, sc.order, classzone_idx);
		if (!balanced && nr_boost_reclaim) {
			nr_boost_reclaim = 0;
			goto restart;
		}

		// 若符合条件,则跳转到 out 标签处
		if (!nr_boost_reclaim && balanced)
			goto out;

		...
		// 对匿名页面的活跃 LRU 链表进行老化
		age_active_anon(pgdat, &sc);

		...
		// 回收页面的核心函数
		if (kswapd_shrink_node(pgdat, &sc))
			raise_priority = false;

		...

		if (raise_priority || !nr_reclaimed)
			// 不断加大扫描粒度
			sc.priority--;
	} while (sc.priority >= 1);

	if (!sc.nr_reclaimed)
		pgdat->kswapd_failures++;

out:
	if (boosted) {
		...
		// 若设置了 boosted,则唤醒 kcompactd 内核线程
		wakeup_kcompactd(pgdat, pageblock_order, classzone_idx);
	}

	...
	// 返回已经回收的页面数量
	return sc.order;
}

3. shrink_node()函数

shrink_node()函数用于扫描和回收内存节点中所有可回收的页面,还会做一些数据的统计和反馈工作:

// pgdat 表示内存节点
// sc 表示扫描的控制参数
static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)
{
	...

	do {
		...
		// 遍历 memory cgroup,调用 shrink_node_memcg() 回收页面
		do {
			...
            // 基于内存节点的页面回收函数,它会被 kswapd 内核线程和直接页面回收机制调用
			shrink_node_memcg(pgdat, memcg, sc, &lru_pages);
			node_lru_pages += lru_pages;

			if (sc->may_shrinkslab) {
				// shrink_slab() 调用内存管理系统中的 shrinker 接口,用于回收 slab 对象
				shrink_slab(sc->gfp_mask, pgdat->node_id,
				    memcg, sc->priority);
			}

			// vmpressure() 函数通过计算 scanned/reclaimed 比例来判断内存压力
			vmpressure(sc->gfp_mask, memcg, false,
				   sc->nr_scanned - scanned,
				   sc->nr_reclaimed - reclaimed);

			...
		} while ((memcg = mem_cgroup_iter(root, memcg, &reclaim)));

		...

		// 判断当前进程是否是 kswapd 内核线程
		if (current_is_kswapd()) {
			// 若当前系统回写的页面数量等于这一轮页面扫描的数量,说明这些系统有大量回写页
			// 面,因此应该设置 PGDAT_WRITEBACK,表示发现有大量页面正在等待回写到磁盘
			if (sc->nr.writeback && sc->nr.writeback == sc->nr.taken)
				set_bit(PGDAT_WRITEBACK, &pgdat->flags);

			// 若当前系统的脏页数量等于正在块设备 I/O 上进行回写数据的页面数量,说明系统有大量
			// 页面堵塞在块设备的 I/O 操作上,因此应该设置 PGDAT_CONGESTED,表示内存节点中发
			// 现有大量脏页拥堵在一个 BDI 设备中
			if (sc->nr.dirty && sc->nr.dirty == sc->nr.congested)
				set_bit(PGDAT_CONGESTED, &pgdat->flags);

			// 若当前系统还没有开始回写的脏页数量等于这一轮扫描的文件映射的页面数量,说明系统有
			// 大量脏页面,因此应该设置 PGDAT_DIRTY,表示发现有大量的脏文件页面
			if (sc->nr.unqueued_dirty == sc->nr.file_taken)
				set_bit(PGDAT_DIRTY, &pgdat->flags);

			// 统计数据有 immediate 个页面,说明在处理正在回写的页面时发现已经有大量的页面在等待回写,
			// 因此需要调用 congestion_wait() 函数让页面等待 100ms
			if (sc->nr.immediate)
				congestion_wait(BLK_RW_ASYNC, HZ/10);
		}

		...
		// 当前页面回收者是直接页面回收者的情况下:
        // current_may_throttle() 判断当前回写设备是否拥堵,若拥堵则睡眠一段时间来缓解拥堵情况。
		// 若成功回收了 sc->nr_reclaimed 个页面,返回 true
		if (!sc->hibernation_mode && !current_is_kswapd() &&
		   current_may_throttle() && pgdat_memcg_congested(pgdat, root))
			wait_iff_congested(BLK_RW_ASYNC, HZ/10);

	// 通过这一轮中回收页面的数量和扫描页面的数量来判断是否需要继续扫描
	} while (should_continue_reclaim(pgdat, sc->nr_reclaimed - nr_reclaimed,
					 sc->nr_scanned - nr_scanned, sc));

	...

	return reclaimable;
}

4. shrink_active_list()函数

shrink_active_list()函数用于扫描活跃LRU链表,包括匿名页面或者文件映射页面,把最近一直没有人访问的页面添加到不活跃LRU链表中。

// nr_to_scan:待扫描页面的数量
// lruvec:LRU 链表集合
// sc:页面扫描控制参数
// lru:待扫描的 LRU 链表类型
static void shrink_active_list(unsigned long nr_to_scan,
			       struct lruvec *lruvec,
			       struct scan_control *sc,
			       enum lru_list lru)
{
	...
	// 定义 3 个临时链表
	LIST_HEAD(l_hold);	/* The pages which were snipped off */
	LIST_HEAD(l_active);
	LIST_HEAD(l_inactive);
	...
	// is_file_lru() 判断链表是否为文件映射的 LRU 链表
	int file = is_file_lru(lru);
	// 从 lruvec 中返回内存节点描述符 pgdat
	struct pglist_data *pgdat = lruvec_pgdat(lruvec);

	...

	// 在操作链表时,有一个保护 LRU 的自旋锁 pgdat->lru_lock
	spin_lock_irq(&pgdat->lru_lock);

	// isolate_lru_pages() 批量地把 LRU 链表的部分页面迁移到临时链表(l_hold链表)中,
	// 这样可以缩短加锁的时间
	nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &l_hold,
				     &nr_scanned, sc, isolate_mode, lru);

	// 增加内存节点中的 NR_ISOLATED_ANON 计数
	__mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, nr_taken);
	// 增加 recent_scanned[] 计数
	reclaim_stat->recent_scanned[file] += nr_taken;

	...

	// 页面迁移到临时链表 l_hold 后,释放 pgdat->lru_lock 自旋锁
	spin_unlock_irq(&pgdat->lru_lock);

	// while 循环扫描临时链表 l_hold 中的页面,有些页面会添加到 l_active 中,
	// 有些会添加到 l_inactive 中
	while (!list_empty(&l_hold)) {
		cond_resched();
		// lru_to_page() 从链表中取一个页面
		page = lru_to_page(&l_hold);
		list_del(&page->lru);

		// 如果页面是不可回收的,就把它放回不可回收的 LRU 链表中
		if (unlikely(!page_evictable(page))) {
			putback_lru_page(page);
			continue;
		}

		...
        
		// page_referenced() 函数返回该页面最近访问、引用 PTE 的个数
		// 若返回 0,表示最近没有访问、引用
		if (page_referenced(page, 0, sc->target_mem_cgroup,
				    &vm_flags)) {
			...
		}

		// 如果页面没有被引用,清除页面的 PG_Active 标志位并且将页面加入 l_inactive 链表中
		ClearPageActive(page);	/* we are de-activating */
		SetPageWorkingset(page);
		list_add(&page->lru, &l_inactive);
	}

	// 这段加锁期间,把 l_inactive 和 l_active 链表中的页面迁移到相应的 LRU 链表中
	spin_lock_irq(&pgdat->lru_lock);
	// 把最近引用的页面数量保存到 recent_rotated 中,以便下一次扫描时在
	// get_scan_count() 中重新计算匿名页面和文件映射页面 LRU 链表的扫描比值
	reclaim_stat->recent_rotated[file] += nr_rotated;

	nr_activate = move_active_pages_to_lru(lruvec, &l_active, &l_hold, lru);
	nr_deactivate = move_active_pages_to_lru(lruvec, &l_inactive, &l_hold, lru - LRU_ACTIVE);
	__mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken);
	spin_unlock_irq(&pgdat->lru_lock);

	mem_cgroup_uncharge_list(&l_hold);
	// l_hold 链表中是剩下的页面,可以释放
	free_unref_page_list(&l_hold);
	trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate,
			nr_deactivate, nr_rotated, sc->priority, file);
}

5. shrink_inactive_list()函数

shrink_inactive_list()函数扫描不活跃LRU链表以尝试回收页面,并且返回已经回收的页面的数量。该函数的逻辑过于复杂,因此这里用图来理解,如下图所示(感兴趣的道友可以根据该流程图去阅读该函数的源代码):

在这里插入图片描述

6. 页面回收的流程图

在这里插入图片描述

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

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

相关文章

vue中使用component中的is渲染组件如何使用,:is 等价 v-if渲染组件。

动态组件顾名思义动态加载不同的组件&#xff0c;is属性用于加载不同组件&#xff0c;传参使用属性传递 1、使用v-for遍历component&#xff0c;组件都会执行 <componentv-for"(item, index) in TAB_PANE":key"index":is"item.componentName"…

什么是springmvc(介绍)

什么是springmvc 1. 什么是springmvc2.项目中加入springmvc支持2.1 导入依赖2.2 springMVC配置文件2.3 web.xml配置2.4 中文编码处理 3. 编写一个简单的controller4. 视图层配置4.1 视图解析器配置4.2 静态资源配置4.2 编写页面4.3 页面跳转方式 5. SpringMVC处理请求的流程6. …

如何本地搭建DolphinScheduler并无公网ip远程访问管理界面

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

计算机网络教程题(详解)

1、一个网络的物理线路上抓到 011001 位串的波形如下 请问该线路采用了 (B)编码方式。 A.二进制编码 B.曼彻斯特编码 c.差分曼彻斯特编码 D.归零编码 曼彻斯特编码:每一周期分为两个相等的间隔&#xff0c;二进制”1“位在发送时&#xff0c;在第一个间隔中为高电压&#…

淘宝商品数据采集订单数据采集店铺数据采集API演示

淘宝商品数据采集代码 -- 请求示例 url 默认请求参数已经URL编码处理 curl -i "https://api-gw.cn/taobao/item_get/?key<您自己的apiKey>&secret<您自己的apiSecret>&num_iid652874751412&is_promotion1" 商品详情页与获取到的返回数据示…

我的128天创作纪念日

&#x1f308;前言 最近我一直忙着期末复习备考&#xff0c;前天还一不小心感冒了&#xff0c;现在头昏脑胀&#xff0c;说不出话来。刚刚查看CSDN的后台私信&#xff0c;发现官方提示今天是我的128天创作纪念日&#xff0c;正好有几天没有更新博客了&#xff0c;今天更一篇。…

4.MapReduce 序列化

目录 概述序列化序列化反序例化java自带的两种Serializable非Serializable hadoop序例化实践 分片/InputFormat & InputSplit日志 结束 概述 序列化是分布式计算中很重要的一环境&#xff0c;好的序列化方式&#xff0c;可以大大减少分布式计算中&#xff0c;网络传输的数…

【VRTK】【VR开发】【Unity】19-VRTK实现旋转运动

课程配套学习项目源码资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【背景】 在实际开发中,旋转运动也是时常需要模拟的重要运动类型。常见的场景有开关门,方向盘轮胎以及拉动拉杆等等。 旋转运动的实现可以基于物理系…

服务器日常维护要素,应该如何做好维护

维护服务器的目的是为了让服务器的性能保持最佳状态&#xff0c;发现问题及时解决&#xff0c;没有问题也可以对相关的应用和配置进行调优。但也有很多用户疑问&#xff0c;服务器具体会有哪些方面需要维护的&#xff0c;今天就一起来看看吧。 服务器日常维护&#xff0c;主要包…

面向对象编程的五大原则,你了解吗?

面向对象编程的五大原则&#xff0c;你了解吗&#xff1f; 一、面向对象编程的概念 面向对象编程&#xff0c;是一种程序设计范式&#xff0c;也是一种编程语言的分类。它以对象作为程序的基本单元&#xff0c;将算法和数据封装其中&#xff0c;程序可以访问和修改对象关联的…

科研绘图(一)山脊图

从今日开始&#xff0c;为大家开辟一个新的系列科研绘图。同一个竞赛下&#xff0c;大家都近乎相同的解题思路下。之所以能出现一等二等三等奖的区别很大部分都在于结果的可视化&#xff0c;为了能更好地帮助大家进行可视化&#xff0c;今后将专门推出一个可视化板块&#xff0…

B端产品经理学习-权限管理

目录 权限管理的原则 常见的权限管理模型 总结 对企业而言&#xff0c;最重要的一个资源就是有价值的专有信息&#xff0c;也就是自己知道&#xff0c;而其他企业不知道的信息&#xff0c;因此&#xff0c;专有信息是企业的重要竞争力&#xff0c;权限管理的价值体现在专有信…

centos下升级git版本

1 问题描述 centos7系统默认的git安装版本是1.8&#xff0c;但是在项目构建中发现git版本过低&#xff0c;导致构建AI模型环境时出现各种错误&#xff0c;于是用源码编译的方式进行升级&#xff0c;同时该文章也适用于安装新的git。 2. 升级安装 2.1 第一步卸载原有的git r…

HarmonyOS应用开发学习笔记 UI布局学习 相对布局 (RelativeContainer)

UI布局学习 之 相对布局 &#xff08;RelativeContainer&#xff09; 官方文档 一、关键字 RelativeContainer&#xff0c; alignRules&#xff08;适配规则&#xff09; Text(Text02).alignRules({left: { anchor: text01, align: HorizontalAlign.Start },top: { anchor: t…

精通推荐算法4:经典DNN框架特征交叉模型 Deep Crossing(面试必备)

微软2016年提出的Deep Crossing模型奠定了深度学习精排模型的基本架构&#xff0c;具有十分重要的意义。它采用“Embedding MLP”的结构&#xff0c;成为目前推荐算法的基本范式。通过深度神经网络&#xff0c;实现大规模特征自动组合&#xff0c;大大减少了对人工构造交叉组合…

(一)Spring Cloud 直击微服务作用、架构应用、hystrix降级

直击微服务作用 微服务架构: 遇到了什么问题? 将单体架构拆分成微服务架构后,如果保证多个服务(项目)正常运行? 哪个技术可以解决这个问题? 微服务技术 服务治理: 服务管理,维护服务与服务之间的关系 这个技术如何使用? netflix/网…

智能化输电线路定位技术:提升电网运行效率的未来发展方向

随着科技的不断发展&#xff0c;电力行业也在逐步引入智能化技术&#xff0c;以提高输电线路的运行效率和安全性。在这篇文章中&#xff0c;恒峰智慧科技将探讨一种新的输电线路定位技术——分布式行波测量技术&#xff0c;它如何帮助我们实现这一目标。 一、分布式故障定位及隐…

游泳耳机排行榜前四名,分享几款值得推荐的游泳耳机

游泳是一项全面锻炼身体的运动&#xff0c;然而&#xff0c;水的阻力有时让人感到运动的笨拙和单调。为了让游泳更具趣味性和挑战性&#xff0c;选择一款高品质的游泳耳机至关重要。以下是游泳耳机排行榜前四名&#xff0c;以及几款强烈推荐的游泳耳机&#xff0c;它们结合防水…

Python教程(22)——Python面向对象的属性和方法

在Python面向对象编程中&#xff0c;属性和方法是类及其对象的重要组成部分。如果说封装、继承和多态是面向对象编程中的思想&#xff0c;那么类的属性和方法就是骨架&#xff0c;因为有属性和方法&#xff0c;面向对象编程才变的有血有肉。 属性 属性是类或对象所拥有的数据&…

UWB 技术及应用

超宽带技术为工业自动化提供独特优势&#xff0c;是首要的室内定位技术。 UWB 因其相对于 RFID、BLE 或 WiFi 等同类技术的众多优势而被认为是室内定位技术的黄金标准。它是基于位置的自动化的理想解决方案。 UWB 结合了短光速脉冲&#xff0c;可在宽带宽上精确测量信号到达时…