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() 函数让页面等待 100msif (sc->nr.immediate)congestion_wait(BLK_RW_ASYNC, HZ/10);}...// 当前页面回收者是直接页面回收者的情况下:// current_may_throttle() 判断当前回写设备是否拥堵,若拥堵则睡眠一段时间来缓解拥堵情况。// 若成功回收了 sc->nr_reclaimed 个页面,返回 trueif (!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 中返回内存节点描述符 pgdatstruct pglist_data *pgdat = lruvec_pgdat(lruvec);...// 在操作链表时,有一个保护 LRU 的自旋锁 pgdat->lru_lockspin_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.mzph.cn/news/610589.shtml

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

相关文章

Linq练习

准备类以及数据 class Student {public int StudentId { get; set; }public string Name { get; set; }public Course Course { get; set; }public Student(int studentId, string name, Course course){StudentId studentId;Name name;Course course;}public void PrintIn…

深入MySQL——10

查询为何如此之慢 分为两种情况一种是查询后长时间不返回的,还有一种是查询很慢的 我们先来说第一种情况 长时间不返回 这种情况下就是锁阻塞导致不能返回,可以通过show processlist来查看语句处于什么状态,一般情况下会出现这几种状态&a…

Vue3:vue-cli项目创建及vue.config.js配置

一、node.js检测或安装: node -v node.js官方 二、vue-cli安装: npm install -g vue/cli # OR yarn global add vue/cli/*如果安装的时候报错,可以尝试一下方法 删除C:\Users**\AppData\Roaming下的npm和npm-cache文件夹 删除项目下的node…

从传统部署到无服务器计算:AI应用在AWS平台上的革新与飞跃

文章目录 《快速构建AI应用–AWS无服务器AI应用实战》内容简介作者简介目录 随着人工智能技术的不断发展,越来越多的企业开始将人工智能应用于各个业务场景,以提高效率、降低成本并创造新的商业模式。然而,传统的人工智能解决方案往往需要大量…

从零开始C++精讲:第一篇——C++入门

文章目录 前言一、C关键字二、命名空间2.1引子2.2命名空间定义2.3命名空间的使用 三、C输入和输出3.1输出3.2输入 四、缺省参数4.1全缺省4.2半缺省 五、函数重载5.1重载概念 六、引用6.1定义6.2引用的使用示例6.2.1引用作参数6.2.1引用作返回值 6.3传值、传引用效率比较6.4常引…

超维空间M1无人机使用说明书——01、ROS机载电脑使用说明——远程连接

引言:远程连接通常采用两种方式,一种是通过可视化软件,如VNC、Nomachine等,另外一种是使用SSH。各有优缺点,两种远程登录方式的优缺点做一个简单的对比: 1、SSH优缺点 优点:1、消耗网络资源 2、运行稳定 …

前端面试题集合六(高频)

1、vue实现双向数据绑定原理是什么&#xff1f; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>…

java SSM问卷调查系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM问卷调查管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

每天刷两道题——第十一天

1.1滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7], k 3 输出&…

面试宝典之微服务框架面试题

S1、集群与分布式有啥区别&#xff1f; &#xff08;1&#xff09;相同点&#xff1a; 分布式和集群都是需要有很多节点服务器通过网络协同工作完成整体的任务目标。 &#xff08;2&#xff09;不同点&#xff1a; 分布式是指将业务系统进行拆分&#xff0c;即分布式的每一个…

Java微服务架构实践指南

Java 微服务架构是一种在软件开发中越来越受欢迎的架构风格&#xff0c;它将复杂的单体应用程序拆分成一组小型、独立的服务。每个服务都可以独立开发、部署和扩展&#xff0c;并通过轻量级通信机制相互协作。以下是 Java 微服务架构的实践指南&#xff1a; 拆分服务&#xff…

SpringBoot 注解超全详解(整合超详细版本)

使用注解的优势&#xff1a; 采用纯java代码&#xff0c;不在需要配置繁杂的xml文件 在配置中也可享受面向对象带来的好处 类型安全对重构可以提供良好的支持 减少复杂配置文件的同时亦能享受到springIoC容器提供的功能 1注解详解&#xff08;配备了完善的释义&#xff09…

力扣433. 最小基因变化

广度优先搜索 思路&#xff1a; 经过分析可知&#xff0c;基因 A 突变到基因 B&#xff0c;需要满足以下条件&#xff1a; 序列 A 与序列 B 只有一个字符不同&#xff1b;变化字符在集合中&#xff1b;突变后的基因 B 一定在 bank 中&#xff1b;尝试搜索所有合法突变的基因集…

Pycharm中如何配置python环境(conda)

首先在pycharm中点击 "File" > "Settings" 再次点击如下操作&#xff1a; 点击Python Interpreter的最右侧按钮&#xff0c;点击Show All... 找到python文件 最后点击OK

若依项目的table列表中对每一个字段增加排序按钮(单体版和前后端分离版)

一、目标&#xff1a;每一个字段都添加上下箭头用来排序 只需要更改前端代码&#xff0c;不需要更改后端代码&#xff0c;后面会讲解原理 二、单体版实现方式&#xff1a; 1.在options中添加sortable:true 2.在需要排序的字段中添加sortable:true 三、前后端分离版 1.el-tab…

Open CASCADE学习|非线性方程组

非线性方程组是一组包含非线性数学表达式的方程&#xff0c;即方程中含有未知数的非线性项。解这类方程组通常比解线性方程组更为复杂和困难。 非线性方程组在很多领域都有应用&#xff0c;例如物理学、工程学、经济学等。解决非线性方程组的方法有很多种&#xff0c;包括数值…

Unity中打印信息的两种方式

不继承MonoBehaviour的普通C#类中打印信息&#xff1a; 使用Debug类的方法&#xff1a; Unity提供了Debug类&#xff0c;其中包含了一些用于打印信息的静态方法。以下是常用的几种方法&#xff1a; Debug.Log(message)&#xff1a;打印普通信息。Debug.LogWarning(message)&a…

面试题-DAG 有向无环图

有向无环图用于解决前后依赖问题&#xff0c;在Apollo中用于各个组件的依赖管理。 在算法面试中&#xff0c;有很多相关题目 比如排课问题&#xff0c;有先修课比如启动问题&#xff0c;需要先启动1&#xff0c;才能启动2 概念 顶点&#xff1a; 图中的一个点&#xff0c;比…

09、Kafka ------ 通过修改保存时间来删除消息(retention.ms 配置)

目录 通过修改保存时间来删除消息★ 删除指定主题的消息演示1、修改kafka检查过期消息的时间间隔2、修改主题下消息的过期时间3、查看修改是否生效4、先查看下主题下有没有消息5、添加几条消息看效果6、查看消息是否被删除 ★ 恢复主题的retention.ms配置1、先查看没修改前的te…

【开源】基于JAVA语言的教学过程管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 教师端2.2 学生端2.3 微信小程序端2.3.1 教师功能如下2.3.2 学生功能如下 三、系统展示 四、核心代码4.1 查询签到4.2 签到4.3 查询任务4.4 查询课程4.5 生成课程成绩 六、免责说明 一、摘要 1.1 项目介绍 基于JAVAVu…