Page Fault 是 CPU 访问虚拟内存时触发的正常机制,非错误;监控关键在于每秒发生次数及 minor/major 比例,因高频 fault 会显著拖慢程序并暴露内存或 I/O 瓶颈。

什么是 Page Fault,为什么它值得监控
Page Fault 不是错误,而是 CPU 访问虚拟内存时触发的正常机制:当访问的页不在物理内存(RAM)中,内核会从磁盘(如 swap)或按需分配(如 mmap 的匿名页)加载该页。但高频 Page Fault 会显著拖慢程序——尤其是 minor fault 频繁发生时(说明工作集远超物理内存),或 major fault 突增(暗示 I/O 瓶颈或内存严重不足)。
监控的关键不是“有没有”,而是“每秒多少次”以及“minor vs major 的比例”。Linux 下最直接、开销最低的方式是读取 /proc/[pid]/stat 中的第 10 和第 12 字段(minflt 和 majflt),它们是自进程启动以来的累计值。
如何用 C++ 每秒采样并计算增量
核心思路:定期读取 /proc/self/stat,解析第 10(minor faults)和第 12(major faults)字段,与上一次值做差,再除以时间间隔。
注意点:
-
/proc/self/stat是单行文本,字段用空格分隔,但第 2 字段(comm)本身可能含空格(被括号包裹),所以不能简单std::istringstream >>全部字段;必须跳过前 2 个字段后,再逐个读取数值 - 字段索引从 1 开始计数:第 10 个是
minflt,第 12 个是majflt(中间第 11 个是cminflt,即子进程 minor fault 总和,通常忽略) - 读取频率建议 ≥100ms,太密(如 10ms)会导致
stat文件频繁 open/read,反而干扰测量;太疏(如 5s)会掩盖突发抖动 - 使用
std::chrono::steady_clock而非system_clock,避免系统时间调整导致负增量
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <chrono>
#include <iostream><p>int64_t read_page_faults(bool minor) {
std::ifstream f("/proc/self/stat");
if (!f.is_open()) return -1;
std::string line;
std::getline(f, line);
std::istringstream iss(line);
// 跳过前两个字段(pid 和 comm)
std::string dummy;
iss >> dummy >> dummy;
// 读到第 10 个(minor)或第 12 个(major)
int64_t val = 0;
for (int i = 3; i > val)) return -1;
}
return val;
}</p>
<p>int main() {
auto prev = std::chrono::steady_clock::now();
auto prev_min = read_page_faults(true);
auto prev_maj = read_page_faults(false);</p>
<pre class="brush:php;toolbar:false;">while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<:chrono::milliseconds>(now - prev).count() / 1000.0;
auto curr_min = read_page_faults(true);
auto curr_maj = read_page_faults(false);
if (curr_min >= 0 && curr_maj >= 0 && elapsed > 0) {
double min_rate = (curr_min - prev_min) / elapsed;
double maj_rate = (curr_maj - prev_maj) / elapsed;
std::cout <p>}</p></:chrono::milliseconds>
对比 /proc/[pid]/status 更直观但开销更高
/proc/[pid]/status 提供了更易读的字段:voluntary_ctxt_switches、nonvoluntary_ctxt_switches、以及 MMU page-faults 相关的 min_flt 和 maj_flt(注意命名是下划线,且单位仍是累计值)。
但它的问题明显:
- 文件更大(~1.5KB),解析需全文本搜索关键词,比
stat的纯空格分隔慢 3–5 倍 - 字段顺序不固定(不同内核版本略有差异),依赖字符串匹配,鲁棒性差
- 频繁读取会增加 VFS 层压力,尤其在容器环境或高并发进程中
除非你已经在解析 status 获取其他信息(如 VmRSS),否则为 Page Fault 单独读它不划算。
容易被忽略的陷阱:fork 后子进程计数重置,但父进程不变
minflt 和 majflt 是每个进程独立累计的。如果主程序 fork 出大量子进程(比如 worker 模型),你监控的是当前进程(/proc/self),那子进程的 fault 完全不会计入——这是正确行为,但容易误判“整体内存压力”。
若需监控整个进程树(例如一个服务及其所有子进程),必须:
- 遍历
/proc/[pid]/task/[tid]/stat(线程级,但线程共享 fault 计数) - 或扫描
/proc/[pid]/children(需 4.1+ 内核),递归收集所有子进程的stat - 或改用
perf stat -e page-faults,minor-faults,major-faults -p [pid]外部工具,它能聚合进程树
真正影响性能的,往往不是单个进程的 fault 率,而是它是否持续增长却无法回落——这说明内存泄漏或缓存未命中率恶化。盯住趋势,比盯住瞬时值重要得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











