madvise MADV_FREE对文件页统计的影响及原理

一、背景

madvise系统调用是一个与性能优化强相关的一个系统调用。madvise系统调用包括使用madvise函数,也包含使用posix_fadvise函数。如我们可以使用posix_fadvise传入POSIX_FADV_DONTNEED来清除文件页的page cache以减少内存压力。

这篇博客里,我们讲的是madvise(addr,size,MADV_FREE)这个调用,要注意,这个调用只能针对匿名内存,对于文件页的对应的内存是调用这个会报错(这一点会在下面 3.1 里讲到)。

在下面第二章里,我们贴出测试源码并说明测试方法并展示测试结果。在第三章里,我们给出相关细节的原理分析。

二、测试程序源码及效果展示

2.1 测试源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>#define PAGE_SIZE 4096ull
#define NUM_PAGES 1024*512ull // 分配 2G 的内存int main() {// 分配一块大的匿名内存size_t size = NUM_PAGES * PAGE_SIZE;void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED) {perror("mmap");return EXIT_FAILURE;}printf("Allocated %zu bytes of anonymous memory at %p\n", size, addr);// 触发缺页异常printf("Accessing a page to trigger a page fault...\n");memset(addr, 0, size); // 访问第一页getchar();// 在这里可以观察到缺页异常的发生// 使用 madvise 将未使用的页面标记为 MADV_DONTNEEDif (madvise(addr, size, MADV_FREE) != 0) {perror("madvise MADV_FREE");munmap(addr, size);return EXIT_FAILURE;}printf("Marked memory as MADV_FREE\n");getchar();printf("Reuse the memory!\n");memset(addr, 0, size); // 访问第一页// 等待 10 秒getchar();// 再次使用 madvise 将内存标记为 MADV_DONTNEED(这在实际情况下是没有必要的,因为上面已经释放了)// 只是为了演示if (madvise(addr, size, MADV_DONTNEED) != 0) {perror("madvise MADV_DONTNEED");munmap(addr, size);return EXIT_FAILURE;}printf("Re-marked memory as MADV_DONTNEED\n");getchar();// 解除映射if (munmap(addr, size) != 0) {perror("munmap");return EXIT_FAILURE;}getchar();printf("Unmapped memory\n");return EXIT_SUCCESS;
}

2.2 编写一个内核模块用来抓取调用栈和调用信息

编写了一个内核模块,用来抓取madvise这个调用栈和调用信息。

2.2.1 内核模块源码

下面的这个代码是改写的之前在分析vdso内容时写的内核模块(vdso概念及原理,vdso_fault缺页异常,vdso符号的获取_x86架构的vdso-CSDN博客 里 2.3.1 一节里的代码),改写了一下,所以名字里包含了vdso字样。

关键的改动即注册kprobe的callback时设了lru_lazyfree_fn这个接口:

然后加入了一个pid的条件控制:

在指定的pid进程内才打印堆栈,也只打印一次,然后统计执行的次数,并打印:

完整的代码如下:

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/osmonitor.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <trace/events/kmem.h>
#include <linux/ptrace.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <linux/sched/task_stack.h>
#include <linux/nmi.h>
#include <asm/apic.h>
#include <linux/version.h>
#include <linux/sched/mm.h>
#include <asm/irq_regs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/stop_machine.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhaoxin");
MODULE_DESCRIPTION("Module for vdso_fault debug.");
MODULE_VERSION("1.0");static int pid = 0;
module_param(pid, int, 0);struct kprobe _kp1;static bool _blog = false;int getfullpath(struct inode *inode,char* i_buffer,int i_len)
{struct dentry *dentry;//printk("inode = %ld\n", inode->i_ino);//spin_lock(&inode->i_lock);hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {char *buffer, *path;buffer = (char *)__get_free_page(GFP_KERNEL);if (!buffer)return -ENOMEM;path = dentry_path_raw(dentry, buffer, PAGE_SIZE);if (IS_ERR(path)){continue;   }strlcpy(i_buffer, path, i_len);//printk("dentry name = %s , path = %s", dentry->d_name.name, path);free_page((unsigned long)buffer);}//spin_unlock(&inode->i_lock);return 0;
}static bool blog = false;
static u64 runtimes = 0;int kprobecb_vdso_fault_pre(struct kprobe* i_k, struct pt_regs* i_p)
{if (current->pid == pid) {if (!blog) {blog = true;dump_stack();}runtimes ++;printk("run lru_lazyfree_fn %llu times!\n", runtimes);}return 0;
}int kprobe_register_func_vdso_fault(void)
{int ret;memset(&_kp1, 0, sizeof(_kp1));_kp1.symbol_name = "lru_lazyfree_fn";_kp1.pre_handler = kprobecb_vdso_fault_pre;_kp1.post_handler = NULL;ret = register_kprobe(&_kp1);if (ret < 0) {printk("register_kprobe fail!\n");return -1;}printk("register_kprobe success!\n");return 0;
}void kprobe_unregister_func_vdso_fault(void)
{unregister_kprobe(&_kp1);
}static int __init testvdso_init(void)
{kprobe_register_func_vdso_fault();return 0;
}static void __exit testvdso_exit(void)
{kprobe_unregister_func_vdso_fault();
}module_init(testvdso_init);
module_exit(testvdso_exit);

2.2.2 抓到的madvise的调用栈和调用信息

抓到的堆栈:

如下图可以看到执行了524280次,是0x7FFF8,离2G的0x80000个4k page的0x80000的个数差了8:

这个差值来自于下图里的红色框出逻辑的批处理逻辑的判断:

2.3 看/proc/meminfo和free -h的测试结果

我们关注运行测试程序期间及之前和之后的/proc/meminfo和free -h的状态变化。

2.3.1 对比观察free -h的变化

程序运行前,buff/cache是14G,free是106G:

执行程序之后,并触发2G的缺页异常之后:

看free -h的变化是,buff/cache不变,free减少2G,从106G到104G:

然后再运行madvise(addr, size, MADV_FREE):

从free -h看是没有变化的:

所以,madvise(addr, size, MADV_FREE)的执行对free -h的统计是不产生变化的。

2.3.2 对比观察/proc/meminfo的变化

观察的脚本是:

watch -n 0.1 "cat /proc/meminfo | grep -E 'MemFree|Buffers|Cached|Active|Inactive|\(anon\)|\(file\)|AnonPages|Mapped'"

我们只观察我们需要重点关注这几项。

执行程序前是:

触发2G的缺页异常之后:

可以看到MemFree如预期减少2G,Active统计增加2G,Active(anon)统计增加2G,Active(file)统计不增加。对于AnonPages统计项,是增加了2G,对于Mapped统计项,未变动。

再继续运行程序,调用madvise(addr, size, MADV_FREE)之后:

可以如上图看到,在这个调用的前后情况来看,MemFree无变化,Buffers/Cached都无变化,Active里减少2G到了Inactive里,即从Active(anon)里减少了2G到了Inactive(file)里。

而对于AnonPages统计项和Mapped统计项,这个madvise(addr, size, MADV_FREE)调用无变动。

三、原理分析

3.1 madvise(addr, size, MADV_FREE)不能用于文件页

我们把 2.1 里的源码修改一下,修改过后的源码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>#define PAGE_SIZE 4096ull
#define NUM_PAGES 1024*512ull // 分配 2G 的内存int main() {size_t size = NUM_PAGES * PAGE_SIZE;int fd = open("temp_file.bin", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);if (fd == -1) {perror("open");return EXIT_FAILURE;}if (ftruncate64(fd, size) == -1) {perror("ftruncate");close(fd);return EXIT_FAILURE;}// 分配一块大的匿名内存void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("mmap");return EXIT_FAILURE;}printf("Allocated %zu bytes of anonymous memory at %p\n", size, addr);// 触发缺页异常printf("Accessing a page to trigger a page fault...\n");memset(addr, 0, size); // 访问第一页getchar();// 在这里可以观察到缺页异常的发生// 使用 madvise 将未使用的页面标记为 MADV_DONTNEEDif (madvise(addr, size, MADV_FREE) != 0) {perror("madvise MADV_FREE");munmap(addr, size);return EXIT_FAILURE;}//posix_fadvise(fd, 0, size, POSIX_FADV_DONTNEED);printf("Marked memory as MADV_FREE\n");getchar();printf("Reuse the memory!\n");memset(addr, 0, size); // 访问第一页// 等待 10 秒getchar();// 再次使用 madvise 将内存标记为 MADV_DONTNEED(这在实际情况下是没有必要的,因为上面已经释放了)// 只是为了演示if (madvise(addr, size, MADV_DONTNEED) != 0) {perror("madvise MADV_DONTNEED");munmap(addr, size);return EXIT_FAILURE;}printf("Re-marked memory as MADV_DONTNEED\n");getchar();// 解除映射if (munmap(addr, size) != 0) {perror("munmap");return EXIT_FAILURE;}getchar();printf("Unmapped memory\n");return EXIT_SUCCESS;
}

运行后看到madvise(addr, size, MADV_FREE)这句话调用出错:

所以madvise(addr, size, MADV_FREE)的这个调用只能用于匿名内存。

3.2 madvise(addr, size, MADV_FREE)会将该匿名内存挪到Inactive(file)里

有关这个统计项的迁移的核心逻辑即调用madvise(addr, size, MADV_FREE)时最终调用到:

folio_mark_lazyfree调用了lru_lazyfree_fn

在lru_lazyfree_fn里完成了统计上的迁移。

这个迁移从folio_mark_lazyfree函数的注释里也可以清晰的看到描述:

可以看到,这个迁移的原因之一就是加速回收逻辑。因为我们系统里的大部分内存回收都是回收的inactive file里的页。

我们再来看一下lru_lazyfree_fn函数的实现:

可以从上图里看到,红色框出的注释里清楚地写到,Lazyfree的这部分folio需要清楚swapbacked的flag,为的是和普通的匿名页相区别。怎么理解呢,因为普通的匿名页都是指已经完成了物理页的分配并会继续使用的部分,而这部分调用madvise MADV_FREE则是不再继续使用的部分,所以内核并不需要在内存紧张时把它们交换到swap分区里去,因为这部分page上面的数据用户已经标记了不再使用了。

关于这个folio是不是file的lru的判断,内核的函数folio_is_file_lru如下:

3.3 /proc/meminfo里的Mapped和cgroup的memory.stat里的file一样都不会统计到该MADV_FREE出来的内存

在上面的实验里,我们也看到了/proc/meminfo里的Mapped统计项是不会统计到该madvise MADV_FREE出来的内存的。

同样的对于memory.stat里的file项的统计也是一样的,也是不统计到该madvise MADV_FREE出来的内存的:

其实/proc/meminfo里的Mapped这个名字更加贴切,也不容易产生误解。即产生过文件映射的部分。但要注意,shm_open创建出来的共享内存,由于有tmpfs文件系统下的文件映射,所以也要包含到/proc/meminfo里的Mapped的统计,也同样的包含到memory.stat里的file的统计。

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

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

相关文章

于键值(KV)的表

基于键值&#xff08;KV&#xff09;的表 将行编码为键值&#xff08;KVs&#xff09; 索引查询&#xff1a;点查询和范围查询 在关系型数据库中&#xff0c;数据被建模为由行和列组成的二维表。用户通过SQL表达他们的意图&#xff0c;而数据库则神奇地提供结果。不那么神奇的…

2025年邵阳市工程技术研究中心申报流程、条件、奖补

一、邵阳市工程技术研究中心申报条件 &#xff08;一&#xff09;工程技术研究中心主要依托科技型企业组建&#xff0c;依托单位应具有以下条件&#xff1a; 1. 具有较强技术创新意识的领导班子和技术水平高、工程化实践经验丰富的工程技术研发队伍&#xff0c;其中固定人员…

Python+AI提示词出租车出行轨迹预测:梯度提升GBR、KNN、LR回归、随机森林融合及贝叶斯概率异常检测研究

原文链接&#xff1a;tecdat.cn/?p41693 在当今数字化浪潮席卷全球的时代&#xff0c;城市交通领域的海量数据如同蕴藏着无限价值的宝藏等待挖掘。作为数据科学家&#xff0c;我们肩负着从复杂数据中提取关键信息、构建有效模型以助力决策的使命&#xff08;点击文末“阅读原文…

系统重装——联想sharkbay主板电脑

上周给一台老电脑重装系统系统&#xff0c;型号是lenovo sharkbay主板的电脑&#xff0c;趁着最近固态便宜&#xff0c;入手了两块长城的固态&#xff0c;装上以后插上启动U盘&#xff0c;死活进不去boot系统。提示 bootmgr 缺失&#xff0c;上网查了许久&#xff0c;终于解决了…

python连接Elasticsearch并完成增删改查

python库提供了elasticsearch模块,可以通过以下命令进行快速安装,但是有个细节需要注意一下,安装的模块版本要跟es软件版本一致,此处举例:7.8.1 pip install elasticsearch==7.8.1 首先连接elasticsearch,以下是免密示例 from elasticsearch import Elasticsearch# El…

PDF嵌入图片

所需依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.0.0</version><type>pom</type> </dependency>源码 /*** PDF工具*/ public class PdfUtils {/*** 嵌入图…

目标检测篇---faster R-CNN

目标检测系列文章 第一章 R-CNN 第二篇 Fast R-CNN 目录 目标检测系列文章&#x1f4c4; 论文标题&#x1f9e0; 论文逻辑梳理1. 引言部分梳理 (动机与思想) &#x1f4dd; 三句话总结&#x1f50d; 方法逻辑梳理&#x1f680; 关键创新点&#x1f517; 方法流程图关键疑问解答…

Seaborn模块练习题

1.使用tips数据集&#xff0c;创建一个展示不同时间段(午餐/晚餐)账单总额分布的箱线图 import seaborn as sns import matplotlib.pyplot as plt import pandas as pdsns.set_style("darkgrid") plt.rcParams["axes.unicode_minus"] Falsetips pd.read…

计算机网络 | 应用层(1)--应用层协议原理

&#x1f493;个人主页&#xff1a;mooridy &#x1f493;专栏地址&#xff1a;《计算机网络&#xff1a;自定向下方法》 大纲式阅读笔记 关注我&#x1f339;&#xff0c;和我一起学习更多计算机的知识 &#x1f51d;&#x1f51d;&#x1f51d; 目录 1. 应用层协议原理 1.1 …

论文导读 - 基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断

基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断 原论文地址&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0925400521014830 引用此论文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; WANG T, ZHANG H, WU Y, …

React中createPortal 的详细用法

createPortal 是 React 提供的一个实用工具&#xff0c;用于将 React 子元素渲染到 DOM 中的某个位置&#xff0c;而该位置与父组件不在同一个 DOM 层次结构中。这在某些特殊场景下非常有用&#xff0c;比如实现模态框、弹出菜单、固定定位元素等功能。 基本语法 JavaScript …

电池的寿命

思路&#xff1a; 首先&#xff0c;我们观察发现&#xff1a;由于每枚电池的使用时间不同&#xff0c;而我们又要减少浪费才能使所有电池加起来用得最久&#xff0c;不难发现&#xff1a;当n2时&#xff0c;输出较小值。 第一步&#xff1a;将电池分为两组&#xff0c;使两组…

LeetCode每日一题4.27

3392. 统计符合条件长度为 3 的子数组数目 问题 问题分析 统计符合条件的长度为 3 的子数组数目。具体条件是&#xff1a;子数组的第一个数和第三个数的和恰好为第二个数的一半。 思路 遍历数组&#xff1a;由于子数组长度固定为 3&#xff0c;我们可以通过遍历数组来检查每…

Linux日志处理命令多管道实战应用

全文目录 1 日志处理1.1 实时日志分析1.1.1 nginx日志配置1.1.2 nginx日志示例1.1.3 日志分析示例 1.2 多文件合并分析1.3 时间范围日志提取 2 问题追查2.1 进程级问题定位2.2 网络连接排查2.3 硬件故障追踪 3 数据统计3.1 磁盘空间预警3.2 进程资源消耗排名3.3 HTTP状态码统计…

0803分页_加载更多-网络ajax请求2-react-仿低代码平台项目

文章目录 1 分页1.1 url与分页参数1.2 分页组件与url1.3 列表页引用分页组件 2 加载更多2.1 状态2.2 触发时机2.3 加载数据2.4优化 结语 1 分页 1.1 url与分页参数 查询问卷列表接口&#xff0c;添加分页参数&#xff1a; page&#xff1a;当前页码&#xff08;第几页&#…

【技术追踪】基于扩散模型的脑图像反事实生成与异常检测(TMI-2024)

一种新颖的扩散模型双重采样策略&#xff0c;DDPM DDIM ~ 论文&#xff1a;Diffusion Models for Counterfactual Generation and Anomaly Detection in Brain Images 0、摘要 病理区域的分割掩模在许多医学应用中很有用&#xff0c;例如脑肿瘤和中风管理。此外&#xff0c;疾…

第十六届蓝桥杯大赛软件赛省赛第二场 C/C++ 大学 A 组

比赛还没有开始&#xff0c;竟然忘记写using namespace std; //debug半天没看明白 (平时cv多了 然后就是忘记那个编译参数&#xff0c;&#xff08;好惨的开局 编译参数-stdc11 以下都是赛时所写代码&#xff0c;赛时无聊时把思路都打上去了&#xff08;除了倒数第二题&#…

CentOS 7上Memcached的安装、配置及高可用架构搭建

Memcached是一款高性能的分布式内存缓存系统&#xff0c;常用于加速动态Web应用的响应。本文将在CentOS 7上详细介绍Memcached的安装、配置&#xff0c;以及如何实现Memcached的高可用架构。 &#xff08;1&#xff09;、搭建memcached 主主复制架构 Memcached 的复制功能支持…

告别进度失控:用燃尽图补上甘特图的监控盲区

在职场中&#xff0c;项目经理最头疼的莫过于“计划赶不上变化”。明明用甘特图排好了时间表&#xff0c;任务却总像脱缰野马——要么进度滞后&#xff0c;要么资源分配失衡。甘特图虽能直观展示任务时间轴&#xff0c;但面对突发风险或团队效率波动时&#xff0c;它更像一张“…

爬虫-oiwiki

我们将BASE_URL 设置为 "https://oi-wiki.org/" 后脚本就会自动开始抓取该url及其子页面的所有内容&#xff0c;并将统一子页面的放在一个文件夹中 import requests from bs4 import BeautifulSoup from urllib.parse import urljoin, urlparse import os import pd…