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 下最直接、开销最低的方式是读取
中的第 10 和第 12 字段(
和
),它们是自进程启动以来的累计值。
如何用 C++ 每秒采样并计算增量
核心思路:定期读取
,解析第 10(minor faults)和第 12(major faults)字段,与上一次值做差,再除以时间间隔。
注意点:
立即学习
“
C++免费学习笔记(深入)
”;
是单行文本,字段用空格分隔,但第 2 字段(comm)本身可能含空格(被括号包裹),所以不能简单
全部字段;必须跳过前 2 个字段后,再逐个读取数值
字段索引从 1 开始计数:第 10 个是
,第 12 个是
(中间第 11 个是
,即子进程 minor fault 总和,通常忽略)
读取频率建议 ≥100ms,太密(如 10ms)会导致
文件频繁 open/read,反而干扰测量;太疏(如 5s)会掩盖突发抖动
使用
而非
,避免系统时间调整导致负增量
}
对比 /proc/[pid]/status 更直观但开销更高
提供了更易读的字段:
、
、以及
相关的
和
(注意命名是下划线,且单位仍是累计值)。
C知道
CSDN推出的一款AI技术问答工具
下载
但它的问题明显:
文件更大(~1.5KB),解析需全文本搜索关键词,比
的纯空格分隔慢 3–5 倍
字段顺序不固定(不同内核版本略有差异),依赖字符串匹配,鲁棒性差
频繁读取会增加 VFS 层压力,尤其在容器环境或高并发进程中
除非你已经在解析
获取其他信息(如
),否则为 Page Fault 单独读它不划算。
容易被忽略的陷阱:fork 后子进程计数重置,但父进程不变
和
是
每个进程独立累计
的。如果主程序 fork 出大量子进程(比如 worker 模型),你监控的是当前进程(
),那子进程的 fault 完全不会计入——这是正确行为,但容易误判“整体内存压力”。
若需监控整个进程树(例如一个服务及其所有子进程),必须:
遍历
(线程级,但线程共享 fault 计数)
或扫描
(需 4.1+ 内核),递归收集所有子进程的
或改用
外部工具,它能聚合进程树
真正影响性能的,往往不是单个进程的 fault 率,而是它是否持续增长却无法回落——这说明内存泄漏或缓存未命中率恶化。盯住趋势,比盯住瞬时值重要得多。
/proc/[pid]/statminfltmajflt/proc/self/stat/proc/self/statstd::istringstream >>minfltmajfltcminfltstatstd::chrono::steady_clocksystem_clock
#include
#include
#include
#include
#include
#include
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 <= (minor ? 10 : 12); ++i) {
if (!(iss >> val)) return -1;
}
return val;
}
int main() {
auto prev = std::chrono::steady_clock::now();
auto prev_min = read_page_faults(true);
auto prev_maj = read_page_faults(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(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 << "minor: " << min_rate << "/s, major: " << maj_rate << "/s\n";
}
prev = now;
prev_min = curr_min;
prev_maj = curr_maj;
} /proc/[pid]/statusvoluntary_ctxt_switchesnonvoluntary_ctxt_switchesMMU page-faultsmin_fltmaj_fltstatstatusVmRSSminfltmajflt/proc/self/proc/[pid]/task/[tid]/stat/proc/[pid]/childrenstatperf stat -e page-faults,minor-faults,major-faults -p [pid]