Linux下获取全局page fault统计应读取/proc/stat中pgmajfault和pgminfault行的第二字段,二者之和为总缺页次数;需两次采样相减再除以时间间隔计算频率,不可依赖getrusage或跨平台方案。

Linux 下用 /proc/stat 读取全局 page-fault 统计
Linux 内核把 page fault 总数(含 minor 和 major)写在 /proc/stat 的 pgpgin、pgpgout、pgmajfault、pgminfault 等字段里,但注意:pgmajfault 和 pgminfault 才是你要的“分页错误次数”。前者是 major page fault(需磁盘 I/O),后者是 minor(仅内存映射调整)。
实操建议:
- 打开
/proc/stat,逐行扫描以pgmajfault和pgminfault开头的行;一行里第二个字段就是累计值(十进制整数) - 要算“频率”,必须做两次采样再相减,比如间隔 1 秒读两次,差值除以时间即为平均每秒 fault 数
- 别直接用
cat /proc/stat | grep pgmajfault在 shell 里临时看——C++ 程序里要用std::ifstream按行解析,避免因行顺序变动或空格数量变化导致解析失败 - 字段位置固定,但不同内核版本可能新增字段,务必按字段名匹配,而非按列索引硬编码
C++ 代码里如何安全提取 pgmajfault 和 pgminfault
直接 std::stoi() 解析第二列有风险:空格可能不一致,行末可能有不可见字符,或某次读取时内核正刷新该文件导致某字段缺失。稳妥做法是跳过首空格、用 std::istringstream 分词,且检查是否至少读到两个 token。
示例片段(非完整程序,仅核心逻辑):
立即学习“C++免费学习笔记(深入)”;
std::ifstream stat("/proc/stat");
std::string line;
long maj = 0, min = 0;
while (std::getline(stat, line)) {
if (line.substr(0, 12) == "pgmajfault ") {
std::istringstream iss(line.substr(12));
if (iss >> maj) { /* 成功 */ }
} else if (line.substr(0, 12) == "pgminfault ") {
std::istringstream iss(line.substr(12));
if (iss >> min) { /* 成功 */ }
}
}
注意:substr(0, 12) 是因为 "pgmajfault " 长度为 12(含尾随空格),比用 find + substr 更快也更确定;但若未来内核改名(如加前缀),这里会失效——所以生产环境建议用 line.compare(0, 12, "pgmajfault ") == 0 更健壮。
为什么不用 getrusage(RUSAGE_SELF) 或 getrusage(RUSAGE_CHILDREN)
这两个调用返回的是**当前进程**的累计 page fault 数(ru_majflt 和 ru_minflt),不是系统全局频率。如果你要监控整个系统的内存压力,它们完全没用——哪怕你 root 权限运行,getrusage 也不提供系统级统计。
常见误解:
- 误以为
RUSAGE_SYSTEM存在(它不存在,POSIX 不定义该常量) - 用
clock_gettime(CLOCK_MONOTONIC)配合getrusage算自己进程的 fault 率——这只能反映单个进程行为,和系统负载无关 - 试图从
/proc/[pid]/stat里读maj_flt字段来汇总所有进程——开销大、竞态强、漏掉短命进程
macOS 和 Windows 完全不支持这种统计方式
macOS 没有 /proc/stat,其等价机制是 sysctl 的 vm.stats.vm.v_pgfaults(minor)和 v_vmmajfaults(major),但需要 sysctlbyname() 调用,且字段名易随 macOS 版本变更;Windows 则依赖 PDH(Performance Data Helper)查询 \Memory\Pages/sec 计数器,但该值混合了 page-in 和 page-out,无法拆出 fault 类型。
所以跨平台方案基本不存在——如果你的程序必须跑在多系统上,要么只支持 Linux,要么明确文档注明:“仅 Linux 有效”,并在其他平台返回错误或 0。
真正容易被忽略的是采样时机:/proc/stat 的更新不是实时的,而是按内核软中断周期(通常几毫秒)批量更新。连续两次读取间隔小于 10ms,差值很可能为 0;但间隔太久(如 5 秒)又可能掩盖突发 spike。实践中 1–2 秒是较平衡的选择。


















