Linux 匿名页的生命周期
目录
匿名页的生成
匿名页生成时的状态
do_anonymous_page缺页中断源码
从匿名页加入Inactive lru引出 一个非常重要内核patch
匿名页何时回收
本文以Linux5.9源码讲述
匿名页的生成
- 用户空间malloc/mmap(非映射文件时)来分配内存,在内核空间发生缺页中断时,do_anonymous_page会产生匿名页,这是最主要的生成场景。
- 写时复制。缺页中断出现写保护错误,新分配的页面是匿名页,主要是do_wp_page和do_cow_fault。
- do_swap_page,从swap分区读回数据会分配匿名页。
匿名页生成时的状态
migrate type: moveable
page->_refcount: 2
page->_mapcount: 0
page->mapping: 指向vma中的anon_vma数据结构,跟rmap反向映射有关
page->index: 虚拟地址是vma中第几个页面,这个offset即为index
Lru :inactive aono lru
flags: [PG_Swapbacked | PG_lru]。页面支持swap,android上比如时zram压缩,注意没有设置PG_referenced.
- PG_swapbacked:匿名页do_anonymous_page调用page_add_new_anon_rmap时设置了该flag,代表可以交换到swap分区(比如android的zram)。内核有个函数叫PageSwapBacked,满足条件是两种页面:一是此处的anon page,另外一种是shmem page。
- moveable可以理解,因为匿名页面也会缺页中断do_anonymous_page的时候会填充页表,page mirgrate迁移的时候只要修改页表映射即可。参见 do_anonymous_page中的alloc_zeroed_user_highpage_movable。
- _refcount 等于2说明被内核中引用了两次。
- 第一次引用:alloc_pages从buddy中申请出来的page默认_refcount = 1。这个很好理解,被分配就相当于”出嫁“有了约束,相当于被引用(约束)了一次,释放回buddy之后意味了自由和无约束,那么_refcount = 0;
- 第二次引用:加入inactive lru。匿名页产生的时候会加入inactive anon lru中,参见do_anonymous_page代码中的lru_cache_add_inactive_or_unevictable
- _mapcount: 0,说明匿名页生成时,只有一个进程页表映射了该匿名页。设置该字段参见下面的page_add_new_anon_rmap函数。
- mapping:指向anon_vma结构
- 对于匿名页来讲,其mapping指向匿名映射的anon_vam数据结构(文件页对一个address_space)。
- 既然mapping字段对于不同类型的文件指向不同对象,内核可以利用该字段判定page是否是匿名页,即PageAnon函数:mapping指针的最低位不是0,那么就是匿名页。
-
#define PAGE_MAPPING_ANON 0x1 #define PAGE_MAPPING_MOVABLE 0x2 #define PAGE_MAPPING_KSM (PAGE_MAPPING_ANON | PAGE_MAPPING_MOVABLE) #define PAGE_MAPPING_FLAGS (PAGE_MAPPING_ANON | PAGE_MAPPING_MOVABLE)static __always_inline int PageAnon(struct page *page) {page = compound_head(page);return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0; }
- mapping字段赋值:参见do_anonymous_page的page_add_new_anon_rmap函数
-
/*** page_add_new_anon_rmap - add pte mapping to a new anonymous page* @page: the page to add the mapping to* @vma: the vm area in which the mapping is added* @address: the user virtual address mapped* @compound: charge the page as compound or small page** Same as page_add_anon_rmap but must only be called on *new* pages.* This means the inc-and-test can be bypassed.* Page does not have to be locked.*/ void page_add_new_anon_rmap(struct page *page,struct vm_area_struct *vma, unsigned long address, bool compound) {int nr = compound ? hpage_nr_pages(page) : 1;VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);__SetPageSwapBacked(page);if (compound) {VM_BUG_ON_PAGE(!PageTransHuge(page), page);/* increment count (starts at -1) */atomic_set(compound_mapcount_ptr(page), 0);__inc_node_page_state(page, NR_ANON_THPS);} else {/* Anon THP always mapped first with PMD */VM_BUG_ON_PAGE(PageTransCompound(page), page);/* increment count (starts at -1) */atomic_set(&page->_mapcount, 0);}__mod_node_page_state(page_pgdat(page), NR_ANON_MAPPED, nr);__page_set_anon_rmap(page, vma, address, 1); }/*** __page_set_anon_rmap - set up new anonymous rmap* @page: Page to add to rmap * @vma: VM area to add page to.* @address: User virtual address of the mapping * @exclusive: the page is exclusively owned by the current process*/ static void __page_set_anon_rmap(struct page *page,struct vm_area_struct *vma, unsigned long address, int exclusive) {struct anon_vma *anon_vma = vma->anon_vma;BUG_ON(!anon_vma);if (PageAnon(page))return;/** If the page isn't exclusively mapped into this vma,* we must use the _oldest_ possible anon_vma for the* page mapping!*/if (!exclusive)anon_vma = anon_vma->root;anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;page->mapping = (struct address_space *) anon_vma;page->index = linear_page_index(vma, address); }
do_anonymous_page缺页中断源码
/** We enter with non-exclusive mmap_lock (to exclude vma changes,* but allow concurrent faults), and pte mapped but not yet locked.* We return with mmap_lock still held, but pte unmapped and unlocked.*/
static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{struct vm_area_struct *vma = vmf->vma;struct page *page;vm_fault_t ret = 0;pte_t entry;...//从该函数名字就知道最终调用的伙伴系统申请了zero且moveable的页面//从伙伴系统中刚分配的页面:_refcount = 1,_mapcount = -1;page = alloc_zeroed_user_highpage_movable(vma, vmf->address);if (!page)goto oom;.../** The memory barrier inside __SetPageUptodate makes sure that* preceding stores to the page contents become visible before* the set_pte_at() write.*/__SetPageUptodate(page);...inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES);page_add_new_anon_rmap(page, vma, vmf->address, false);lru_cache_add_inactive_or_unevictable(page, vma);...
}
从匿名页加入Inactive lru引出 一个非常重要内核patch
上面有个很重要的点:anon page刚产生时候在5.9源码版本上加入的是Inactive anon lru列表中。而在更早的内核版本中,比如4.14的时候anon page还是加入active anon lru,这个点要特别注意,而内核改动这个逻辑主要是由于如下patch引入:
[PATCH v7 0/6] workingset protection/detection on the anonymous LRU list
说明:内核之所以如此修改主要是因为系统可能产生大量的仅used-once的anon page,如果将这些匿名页加入active page会导致active过度增长,进而active : inactive lru链表的比例失调,我们知道页面老化shrink的时候如果比例失调会触发shrink_active_list,那么这些used-once anon page就会将active lru中hot的page给老化到inactive anon lru链表中,这个patch将anon page创建后加入了inactive anon lru链表中。
不过万事有利也有弊,这个patch也说明了一个缺点:anon page加入了inactive anon lru,就是anon page更容易被换出释放掉。比如anon re-access interval介于inactive list但是小于active + inactive list的时候,就被换出了,而内核workingset的refault-distance算法正是为了解决这个问题,起初内核只对file-back page使用该算法,即算法只保护了file-back page,而在5.9内核中anon page也被该算法保护,所以也就可以将刚生成的anon page加入到inactive anon lru链表了。
匿名页何时回收
1. used-once
如果匿名页只使用一次,且如上面所述,anon page处于inactive anon lru之中,会经历两次老化才能释放页面,这也是"两次机会法"的体现,也就是说两次机会在访问和释放page的时候都会给page两次机会,不能稍有风吹草动就把page给释放,即两次shrink_page_list才能释放used-once anon page:
第一次shrink: 清理掉referenced_ptes和PG_referenced状态,page_check_references返回PAGEREF_KEEP
第二次shrink: 第一次shrink清理了标志状态,第二次shrink可直接回收了。
2.多次访问
第一种情况:访问间隔很短 - 迁移入active anon lru
当前anon page处于inactive anon lru链表中,推动其在inactive和inactive切换的驱动力也是页面老化(这个点非常重要):如果内存一直充足而不触发页面回收老化,那么anon page将一直保持在inactive 列表中,只有内存紧张触发page reclaim的时候才开始决定page何去何从:回收或者保持在inactive或者迁移到active列表中。
基于上面描述,由于页面re-access,那么pte访问重新置位,那么page_check_referenced返回PAGEREF_ACTIVATE,将该anon page迁移到active anon lru链表中。
static enum page_references page_check_references(struct page *page,struct scan_control *sc)
{int referenced_ptes, referenced_page;unsigned long vm_flags;referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup,&vm_flags);referenced_page = TestClearPageReferenced(page);if (referenced_ptes) {/** All mapped pages start out with page table* references from the instantiating fault, so we need* to look twice if a mapped file page is used more* than once.** Mark it and spare it for another trip around the* inactive list. Another page table reference will* lead to its activation.** Note: the mark is set for activated pages as well* so that recently deactivated but used pages are* quickly recovered.*/SetPageReferenced(page);//re-acess page触发该逻辑if (referenced_page || referenced_ptes > 1)return PAGEREF_ACTIVATE;/** Activate file-backed executable pages after first usage.*/if ((vm_flags & VM_EXEC) && !PageSwapBacked(page))return PAGEREF_ACTIVATE;return PAGEREF_KEEP;}/* Reclaim if clean, defer dirty pages to writeback */if (referenced_page && !PageSwapBacked(page))return PAGEREF_RECLAIM_CLEAN;return PAGEREF_RECLAIM;
}
第二种情况:访问间隔很长 - refault distance算法决定page到底迁入inactive还是active
如果访问间隔较长,两次老化shrink后就会将该anon page回收(anon page对于android上就是放入swap分区,即zram压缩中)。被回收之后再次访问时缺页称为refault,refault之后该内核会判定该anon page再回收释放时,到re-access refault时候,内核一共老化了多少页面,假设是num:
- num < inactive anon lru 那么将anon page加入inactive lru.
- inactive anon list < num < inactive anon lru + active anon lru,那么将anon page迁移到active anon lru中,这样可以尽量避免anon page被再次回收释放。
相关文章:

Linux 匿名页的生命周期
目录 匿名页的生成 匿名页生成时的状态 do_anonymous_page缺页中断源码 从匿名页加入Inactive lru引出 一个非常重要内核patch 匿名页何时回收 本文以Linux5.9源码讲述 匿名页的生成 用户空间malloc/mmap(非映射文件时)来分配内存,在内核空间发生…...

设计模式概述与UML图
文章目录 一、设计模式概述1. 软件设计模式的产生背景2. 软件设计模式的概念3. 学习设计模式的必要性4. 设计模式分类(1)创建型模式(2)结构型模式(3)行为型模式 二、UML图1. 类图概述2. 类图作用3. 类图表示…...

使用Vscode编辑keil工程
一、需要安装的插件 1. Keil Assistant 2. C/C 3. 中文配置: 二、插件配置 1. Keil Assistant 添加Keil的安装路径 接下来就可以使用vscode编辑Keil的工程了,调试编译和下载程序需要返回到Keil中进行操作。 三、Vscode常用快捷键 可以自定义进行配置…...

编译工具:CMake(一) | 简介与安装
编译工具:CMake(一) | 简介与安装 1. CMake简介1.1CMake的特点 2. CMake 安装 这个是CMake的图标 1. CMake简介 cmake 是 kitware 公司以及一些开源开发者在开发几个工具套件(VTK)的过程中衍生品,最终形成体系,成为一…...

深度学习(34)—— StarGAN(1)
深度学习(34)—— StarGAN(1) 文章目录 深度学习(34)—— StarGAN(1)1. 背景2. 基本思路3. 整体流程4. StarGAN v2(1) 网络结构(2) mapping network(3) style encoder(4)Loss 和之前…...

ES6系列之let、const、箭头函数使用的坑
变量提升块级作用域的重要性箭头函数this的指向rest参数和arguments 1.ECMAScript与Js的关系 2.Babel转码器 Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在老版本的浏览器执行。这意味着,你可以用ES6的方式编写程序&…...

4.DNS和负载均衡
文章目录 coreDNS概念部署croeDNS测试 kubernetes多master集群结构master节点部署 负载均衡配置部署nginx做四层反向代理安装高可用 keepalivednginx监控脚本修改k8s中组件的配置文件 coreDNS 概念 coreDNS是kubernetes的默认DNS实现。可以为集群中的service资源创建一个资源名…...

【JavaEE进阶】Spring核心与设计思想
文章目录 一. Spring框架概述1. 什么是Spring框架2. 为什么要学习框架?3. Spring框架学习的难点 二. Spring 核心与设计思想1. 什么是容器?2. 什么是IoC?3. Spring是IoC容器4. DI(依赖注入)5. DL(依赖查找) 一. Spring框架概述…...
实习周记第三周
第二周总结 第二周主要是做了一些PC端细节内容。大的地方改的不多,但是小的细节蛮多。 值得一提的是,第二周做的微信小程序,改了很多逻辑。改逻辑需要与后端进行联调,收获很大,思路也愈发清楚。 记录做了什么是好习…...

11. 使用tomcat中碰到的一些问题
文章目录 问题一:Tomcat的startup.bat启动后出现乱码问题二:一闪而退之端口占用问题三:非端口问题的一闪而退问题四:服务器的乱码和跨域问题问题五: 在tomcat\webapps\下创建文件夹为什么tomcat重启就会丢失问题六:Tom…...
C++解决TCP粘包
目录 TCP粘包问题TCP客户端TCP服务端源码测试 TCP粘包问题 TCP是面向连接的,面向流的可靠性传输。TCP会将多个间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包发送,这样一个数据包里就可能含有多个消息的数据&#…...

最长快乐前缀——力扣1392
文章目录 题目描述KMP题目描述 KMP class Solution {public:string longestPrefix(string s) {int n = s...
使用java.util.List的containsAll()方法可能导致的问题
今天在偶然之间发现了一个bug,原因居然是使用了containsAll()方法,这个问题很简单,看以下代码就能发现很大的问题。 package collection;import java.util.ArrayList; import java.util.List;/*** author heyunlin* version 1.0*/ public cl…...

在线文本转语音播放 (TTS)
具体请前往:在线文本转语音播放(TTS)...
OPTEE之ARM安全扩展
目录 一、BTI(Branch Target Identification) 二、如何使能OP-TEE core的BTI 三、如何使能TA的BTI 一、BTI(Branch Target Identification) 分支目标识别(BTI)是ARMv8.5(及Armv9.0)扩展,它为间接分支及其目标提供了控制流完整性(CFI)保护,从而有助于限…...

Vue [Day4]
组件的三大组成部分 组件的样式冲突 scoped <style scoped></style>data 是一个函数 components/BaseButton.vue <template><div class"BaseButton"><button click"count--">-</button><span>{{ count }}</…...
google chrome 官方下载
官方渠道: 1、链接直接打开就可以下载,最新版实时更新。 32位(x86):https://dl.google.com/tag/s/installdataindex/update2/installers/ChromeStandaloneSetup.exe 64位(x64):htt…...

Misc取证学习
文章目录 Misc取证学习磁盘取证工具veracryto挂载fat文件DiskGenius 磁盘取证例题[RCTF2019]disk 磁盘[](https://ciphersaw.me/ctf-wiki/misc/disk-memory/introduction/#_2)内存取证工具volatility 内存取证例题数字取证赛题0x01.从内存中获取到用户admin的密码并且破解密码 …...
vue打包到jar资源访问被shiro拦截
1.shiro放发对静态资源的拦截// filterMap.put("/202307171550/**", "anon"); // filterMap.put("/config/**", "anon"); // filterMap.put("/index.html", "anon"); 2.装载资源访问 priva…...
选择排序(指针法)
描述 用选择法对10个整数排序。 输入 输入包含10个整数,用空格分隔。 输出 输出排序后的结果,用空格分隔。 输入样例 1 3 1 4 1 5 9 2 6 5 3 输出样例 1 1 1 2 3 3 4 5 5 6 9 输入样例 2 2 4 6 8 10 12 14 16 18 20 输出样例 2 2 4 6 8 1…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...

Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...