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.hqwc.cn/news/336654.html

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

相关文章

【Vue3】2-7 : 计算属性与侦听器区别与原理(二)

本书目录:点击进入 一、监听器 - watch: {} 1.1 目的 1.2 应用场景 1.3 语法 二、计算属性和侦听器的区别 三、实战 示例1:已知 n13 ,n24 求 n3 n1 * n2 ,改变n1,n2,求n3 > 代码 - 计算属性 > 代…

源码|redis7.2.2|sds

文章目录 前言Type && EncodingsdsencodingcreateStringObjectcreateEmbeddedStringObject总结 createRawStringObject总结 createStringObjectFromLongDouble总结 createStringObjectFromLongLongWithOptions总结 相关操作sdscatlen总结 阈值44sds VS C字符串 前言 从…

Golang协程池ants库的学习、使用及源码阅读,协程池与GMP模型关系的理解

前言 在工作时遇到了一个需要使用ants协程池的地方,因此顺带来学习一下他的原理。 协程池 Golang的资源还是偏少一些…因此先简单的参考学习了一下线程池。 类似于Java中的线程池,协程池也是为了减少协程频繁创建、销毁所带来资源消耗的问题。按默认每…

Multimodal Segmentation of Medical Images with Heavily Missing Data

F是mapping function 吐槽 图3太简单了吧。作者未提供代码

Mysql系列-1.Mysql基本使用

👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术🔥如果感觉博主的文章还不错的…

【大模型】大型模型飞跃升级—文档图像识别领域迎来技术巨变

写在前面 2023年12月31日,第十九届中国图象图形学学会青年科学家会议在广州举行,由中国图象图形学学会主办。 该会议的目标是促进青年科学家之间的交流与合作,以提升我国在图像图形领域的科研水平和创新能力。 由中国图象图形学学会和上海合合…

微信小程序Canvas画布绘制图片、文字、矩形、(椭)圆、直线

获取CanvasRenderingContext2D 对象 .js onReady() {const query = wx.createSelectorQuery()query.select(#myCanvas).fields({ node: true, size: true }).exec((res) => {const canvas = res[0].nodeconst ctx = canvas.getContext(2d)canvas.width = res[0].width * d…

毛泽东,如何熬过人生至暗时刻?

文章目录 一、8年内三次蒙冤1、第一次蒙冤2、第二次蒙冤3、第三次蒙冤 二、毛泽东是怎么面对逆境的?三、极致的乐观精神四、结语参考文献 一件事,你做对了,立了功,但结果却是严厉的惩罚,甚至让你回到原点,你…

《Shader开发实战》-笔记

一、初识游戏图形 1、什么是渲染? 渲染实际上就是创建图像的过程,在渲染过程中创建的图像被称为渲染或者帧,该图像(帧)以每秒多次在计算机屏幕上进行呈现,即帧率。 负责渲染图像(帧&#xff09…

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

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

FullCalendar简单的应用(新增,拖动,编辑事件)

大概效果: 以 react 为例: 日历组件所需依赖: (Plugin Index - Docs | FullCalendar) "fullcalendar/core": "^6.1.10", "fullcalendar/daygrid": "^6.1.10", "fullcalendar/interaction&quo…

新书速览|循序渐进Vue.js 3.x前端开发实战

Vue.js初学者和前端开发人员使用,网课、培训机构与大中专院校的教学用书 作者简介 张益珲 美国亚利桑那州立大学计算机工程技术硕士,架构师,从业近10年,多年大前端开发经验,曾就职于知名上市公司,主导开发…