/proc/[pid]/stat中第13个字段maj_flt记录硬页错误累计值,需两次采样做差并用实际时间间隔归一化计算每秒次数,避免sleep或usleep导致的时序偏差及/proc读取自身引发的干扰。

Linux下读取/proc/[pid]/stat无法直接拿到硬页错误次数
硬页错误(major page fault)在/proc/[pid]/stat里没有独立字段——第12个字段min_flt是软页错误,第13个字段maj_flt才是硬页错误累计值。但注意:它只记录自进程启动以来的**总数**,不是每秒速率,更不提供波动细节。
所以不能靠单次读取maj_flt得出“每秒次数”,必须做差值计算,并控制采样间隔。
- 读取路径必须是
/proc/self/stat(当前进程)或/proc/<var>pid</var>/stat(指定进程) -
maj_flt是第13个以空格分隔的字段(索引从1开始),需跳过括号内的comm字段(第2项),实际解析时建议用std::istringstream逐字段提取 - 字段顺序依赖内核版本,但自2.6以来
maj_flt稳定在第13位;若需强兼容,可先读/proc/[pid]/status查voluntary_ctxt_switches等锚点字段辅助定位
用clock_gettime(CLOCK_MONOTONIC, ...)做高精度时间差基准
硬页错误本身发生频率低(通常几秒才一次),但要捕捉“每秒波动”,就得保证两次采样间隔尽量接近1秒,且避免sleep(1)这种不可靠等待——系统调度可能让实际间隔偏差几百毫秒,导致速率计算失真。
正确做法是:记录起始时间 → 循环检查当前时间是否 ≥ 起始+1秒 → 读取新maj_flt → 计算差值 → 更新起始时间。
- 不要用
std::chrono::steady_clock:虽然语义正确,但在glibc 2.31+前某些场景下与CLOCK_MONOTONIC存在微小偏差 - 避免
usleep(1000000):内核调度延迟可能使实际休眠远超1秒,尤其在负载高时 - 示例逻辑片段:
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); double start_sec = ts.tv_sec + ts.tv_nsec / 1e9;
每秒硬页错误数 = (当前maj_flt − 上次maj_flt) ÷ 实际间隔秒数
直接除以1会掩盖真实波动——比如两次采样间隔是1.023秒,而你硬除1,结果就偏高2.3%。尤其当硬页错误本身稀疏(如每5秒一次),误差会被放大。
真正有意义的“每秒次数”必须用实际经过时间归一化,否则无法横向比较或绘图。
- 每次更新后保存
maj_flt和对应时间戳,下次用(cur_maj - last_maj) / (cur_time - last_time) - 首次采样无历史值,跳过输出或设为0
- 若差值为0,仍应输出
0.0(表示该秒无硬页错误),而非跳过——这是波动的关键信息 - 注意整数溢出:
maj_flt是unsigned long,32位系统上最大约42亿;若进程运行极久且频繁触发硬页错误,两次差值可能溢出,建议用uint64_t存累积值
硬页错误波动剧烈时,/proc/[pid]/stat读取本身可能触发额外硬页错误
这很讽刺但真实:频繁打开/读取/proc/self/stat(尤其在内存紧张时),会导致该文件对应的内核态数据结构被换入,反而引发新的硬页错误,干扰观测结果。
这不是bug,而是/proc文件系统的设计特性——它的内容在读取时动态生成,需要访问进程描述符、内存管理结构等,这些页可能不在内存中。
- 采样间隔不宜低于500ms;1秒是平衡精度与干扰的合理下限
- 避免在信号处理函数(如
SIGPROF)里读取/proc/self/stat,上下文切换开销更大 - 若需更高频观测(如分析某段代码触发硬页错误的峰值),改用
perf_event_open()监听PERF_COUNT_SW_PAGE_FAULTS_MAJ事件——它走内核tracepoint路径,不经过/proc,无此副作用
硬页错误的“每秒波动”本质是稀疏事件的时间密度估计,关键不在采样多快,而在时间戳对齐和归一化严谨性;/proc读取的副作用容易被忽略,但实测中它真能让观测值比真实值高出一个数量级。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











