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.pswp.cn/news/903187.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903187.shtml
英文地址,請注明出處:http://en.pswp.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…