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

news2024/11/19 13:21:36

文章说明:

  • 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. 页面回收的流程图

在这里插入图片描述

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

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

相关文章

TS 36.213 V12.0.0-PUCCH过程

​本文的内容主要涉及TS 36.213,版本是C00,也就是V12.0.0。

2024 Midjourney 基础教程(⼆):了解 Midjourney Bot 和AI绘画使用技巧进阶教学

在上⼀篇⽂章中,我们学到了如何注册 Midjourney ,开通付费订阅,并画出了可能是⾃⼰的第⼀张 AI绘画。怎么样?这种将想象的画⾯,变为现实世界图⽚的感觉。 是否有种造物者的错觉,同时有种开盲盒的惊喜感&…

javaWebssh校园物业管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh校园物业管理系统是一套完善的web设计系统(系统采用ssh框架进行设计开发),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用 B/S模式开发。开发环境为TOMCAT7.…

2024年天津财经大学珠江学院专升本专业考试准考证打印及考试安排

天津财经大学珠江学院关于2024年高职升本科专业课考试准考证打印等事宜的通知 天津财经大学珠江学院2024年高职升本科专业课考试将于2024年1月14日举行,为确保考试顺利进行,在此提醒各位考生,请务必认真阅读并严格遵守以下事项。 一、考试地…

代码随想录刷题题Day29

刷题的第二十九天,希望自己能够不断坚持下去,迎来蜕变。😀😀😀 刷题语言:C Day29 任务 ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 …

AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(五)—— 项目-新闻头条-数据管理平台-ajax综合案例前端

愿许秋风知我意&#xff0c;解我心中意难平。 项目介绍 项目准备 推荐使用&#xff0c; 每个程序员都有自己的管理方式。 验证码登录 HTML结构&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><met…

.NetCore部署微服务(二)

目录 前言 概念 一 Consul注册服务中心 1.1 consul下载 1.2 consul运行 二 服务注册 2.1 安装Consul包 2.2 修改配置文件 2.3 注入Consul服务 2.3 修改Controller&#xff0c;增加HealthCheck方法 三 运行服务 3.1 docker运行服务 前言 上一篇讲到微服务要灵活伸缩…

第六讲_css盒子模式

css盒子模型 1. 长度单位2. 盒子模型的组成2.1 盒子模型内容2.2 盒子模型内边距2.3 盒子模型边框2.4 盒子模型外边距 1. 长度单位 px&#xff1a;像素em&#xff1a;相对于当前元素或父元素的 font-size 的倍数 <style>.parent {height: 500px;width: 500px;background…

控制注塑机PQ比例阀放大器

控制不带电反馈的单或双比例电磁铁的博世力士乐&#xff08;Bosch Rexroth&#xff09;、伊顿威格士&#xff08;EATON Vickers&#xff09;、油研&#xff08;YUKEN&#xff09;、贺德克&#xff08;HYDAC&#xff09;、大金&#xff08;DAIKIN&#xff09;、不二越&#xff0…

API(Math类,System类,Runtime类,Object类,Objects类,BigInteger类,BigDecimal类)

文章目录 课程目标1 Math类1.1 概述1.2 常见方法1.3 算法小题(质数)1.4 算法小题(自幂数)1.5 课后练习 2 System类2.1 概述2.2 常见方法 3 Runtime3.1 概述3.2 常见方法3.3 恶搞好基友 4 Object类4.1 概述4.2 常见方法 5 Objects类5.1 概述5.2 常见方法 6 BigInteger类6.1 引入…

微信小程序的生命周期函数有哪些?

面试官&#xff1a;说说微信小程序的生命周期函数有哪些&#xff1f; 一、是什么 跟vue、react框架一样&#xff0c;微信小程序框架也存在生命周期&#xff0c;实质也是一堆会在特定时期执行的函数 小程序中&#xff0c;生命周期主要分成了三部分&#xff1a; 应用的生命周期…

用React给XXL-JOB开发一个新皮肤(二):目录规划和路由初始化

目录 一. 简述二. 目录规划三. Vite 配置 3.1. 配置路径别名3.2. 配置 less 四. 页面 4.1. 入口文件4.2. 骨架文件4.3. 普通页面 五. 路由配置六. 预览启动 一. 简述 上一篇文章我们介绍了项目初始化&#xff0c;此篇文章我们会先介绍下当前项目的目录规划&#xff0c;接着对…

学习JavaEE的日子 day11 初识面相对象

day11 1.初识面相对象 1.1 类和对象的理解 类 * 类是对象的数据类型&#xff0c;类是具有相同属性和行为的一组对象的集合 * 简单理解&#xff1a;类就是对现实事物的一种描述 类的组成 * 属性&#xff1a;指事物的特征&#xff0c;例如&#xff1a;手机事物&#xff08;品牌…

【2023】java常用HTTP客户端对比以及使用(HttpClient/OkHttp/WebClient)

&#x1f4bb;目录 1、介绍2、使用2.1、添加配置2.1.1、依赖2.1.2、工具类2.1.3、实体2.1.4、Controller接口 2.2、Apache HttpClient使用2.3 、OkHttp使用2.4、WebClient使用 1、介绍 现在java使用的http客户端主要包括以下几种 而这些中使用得最频繁的主要是&#xff1a; A…

麒麟系统安装docker、mysql、clickhouse

1、查看麒麟系统版本信息 cat /etc/os-release 麒麟系统版本V10 64位操作系统 # uname -p x86_64 # uname -p aarch64 内核版本 # uname -r 4.19.90-24.4.v2101.ky10.x86_64 本操作为麒麟系统版本V10&#xff0c;x86_64操作系统 一&#xff0c;安装docker 文件&#xff1a…

基于uniapp封装的table组件

数据格式 tableData: [{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},/* {title: "2",elcInfo: [{…

spring-mvc数据绑定和表单标签库(介绍)

spring-mvc数据绑定和表单标签库 1. WEB-INF下页面跳转2. ModelAttribute来注解非请求处理方法3. 表单标签4. 其他标签5. IDEA tomcat控制台中文乱码问题处理 1. WEB-INF下页面跳转 容器启动后&#xff0c;如何默认显示web-inf目录下的系统首页。 2. ModelAttribute来注解非…

mysql生成到当前时间的时间序列,报表按时间补0

生成本月每日的时间序列 SELECT DATE_FORMAT(date_add( CONCAT(YEAR(Date(curdate())),‘-0’,MONTH(Date(curdate())),‘-’,‘01’), INTERVAL ( cast( help_topic_id AS signed) ) DAY ) ,‘%Y-%m-%d’ ) FROM mysql.help_topic WHERE help_topic_id < DAY ( curdate( ) …

示例说明 Makefile 中的 $(@F),及其用法示例$$dir $@ $< $^ %.c

备忘一个不错的开源编辑器CudaText 下载网址&#xff1a; CudaText - Browse /release at SourceForge.net CudaText 主页&#xff1a; CudaText - Home 1&#xff0c;含义及验证 在 Makefile 中&#xff0c;$(F) 表示当前规则的目标文件名&#xff08;不包括路径部分&…

在Linux中使用Apache HTTP服务器

Apache HTTP服务器&#xff0c;也被称为Apache&#xff0c;是全球使用最广泛的Web服务器软件之一。它以其稳定性、强大的功能和灵活性而闻名&#xff0c;尤其在Linux操作系统上表现得尤为出色。以下是关于如何在Linux中使用Apache HTTP服务器的详细指南。 1. 安装Apache 首先…