Linux下读取/proc/[pid]/stat第10、11字段获取minflt和majflt累计值,需两次采样并用clock_gettime(CLOCK_MONOTONIC)精确计算真实时间差dt,再除得每秒缺页中断速率。

Linux下读取/proc/[pid]/stat获取缺页中断原始数据
Linux内核把进程的缺页中断统计直接暴露在 /proc/[pid]/stat 文件第10和第11个字段(从1开始计数):分别是 minflt(minor page faults)和 majflt(major page faults)。这两个值是自进程启动以来的累计次数,不是每秒速率。
要得到“每秒次数”,必须做两次采样并计算差值。注意:不能只读一次就除以时间——那是常见误解。
- 用
getpid()获取当前进程 PID,拼出路径如/proc/12345/stat - 按空格或制表符分割该文件内容,跳过前9个字段,第10个是
minflt,第11个是majflt - 字段之间可能有多个空格,推荐用
std::istringstream配合>>提取(自动跳过空白),而非strtok - 字段值是十进制整数,直接读入
long即可;溢出风险极低(即使运行数月,long也足够)
用clock_gettime(CLOCK_MONOTONIC)实现精确1秒间隔采样
用 sleep(1) 或 std::this_thread::sleep_for(1s) 不可靠:调度延迟、系统负载都可能导致实际间隔远超1秒,造成速率计算失真。真正需要的是“从t0开始,等待恰好1秒的单调时钟流逝”。
clock_gettime(CLOCK_MONOTONIC, &ts) 是唯一能支撑这种需求的POSIX接口。它不受系统时间调整影响,精度通常达纳秒级。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 第一次调用得到起始时间
t0,同时读取minflt0/majflt0 - 循环中用
clock_nanosleep或手动 busy-wait + 检查差值,但更稳妥的做法是:每次采样后计算下次目标时间t_next = t0 + n * 1e9,再用clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t_next) - 不要用
CLOCK_REALTIME:NTP校正或手动改系统时间会破坏连续性
计算每秒速率时区分 minor/major 并处理采样漂移
两次采样时间差 dt 很少正好等于1.0秒(比如 1.002345 秒),直接用差值除以1会引入误差。必须用真实 dt 计算:
double dt_sec = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9; double minflt_per_sec = (minflt1 - minflt0) / dt_sec; double majflt_per_sec = (majflt1 - majflt0) / dt_sec;
另外注意边界情况:
- 若
minflt1 == minflt0且dt_sec极小(如因调度卡顿导致两次读在同个时钟滴答),结果会是 0 —— 这是合理行为,不是 bug - 若进程刚启动,
minflt可能在首秒激增(如加载大量代码段、初始化堆),此时数值偏高属正常 - major fault 长期为 0 是常见现象;一旦出现,通常意味着发生了磁盘 I/O(如 mmap 文件缺页、交换区换入),值得告警
完整示例:最小可行 C++17 实现
以下代码不依赖第三方库,仅用 POSIX 和标准库,编译需加 -lrt(用于 clock_nanosleep):
#include <iostream>
#include <fstream>
#include <sstream>
#include <unistd.h>
#include <sys/time.h>
#include <time.h><p>int main() {
pid_t pid = getpid();
std::string stat_path = "/proc/" + std::to_string(pid) + "/stat";
struct timespec ts;</p><pre class='brush:php;toolbar:false;'>long minflt0, majflt0, minflt1, majflt1;
clock_gettime(CLOCK_MONOTONIC, &ts);
auto t0 = ts;
// 第一次读取
std::ifstream f(stat_path);
for (int i = 0; i < 9; ++i) f >> std::ws; // 跳过前9个字段
f >> minflt0 >> majflt0;
while (true) {
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t0, nullptr);
clock_gettime(CLOCK_MONOTONIC, &ts);
auto t1 = ts;
std::ifstream f2(stat_path);
for (int i = 0; i < 9; ++i) f2 >> std::ws;
f2 >> minflt1 >> majflt1;
double dt = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
std::cout << "minflt/s: " << (minflt1 - minflt0) / dt
<< ", majflt/s: " << (majflt1 - majflt0) / dt << "\n";
minflt0 = minflt1;
majflt0 = majflt1;
t0 = ts;
t0.tv_nsec += 1000000000;
if (t0.tv_nsec >= 1000000000) {
t0.tv_nsec -= 1000000000;
t0.tv_sec++;
}
}}
真实部署时,别让这个循环无限跑——至少加个 SIGINT 处理和最大迭代次数。另外,/proc/[pid]/stat 的字段顺序在不同内核版本稳定(自 2.6 起未变),但若跨平台(如 musl libc 环境),建议用 libprocps 替代手撕解析。

















