Linux 通过 /proc/self/stat 第10、12字段获取 minor/major 缺页数,macOS 用 task_info() 获取总缺页数;二者语义与精度不同,应分平台实现而非统一接口。

Linux 下用 /proc/self/stat 读取 minor/major page faults
Linux 内核把当前进程的页面错误统计写在 /proc/self/stat 的第 10 和第 12 字段:第 10 是 minor page faults(软缺页,内存中能找到但未映射进页表),第 12 是 major page faults(硬缺页,需从磁盘加载)。直接读这个文件最轻量、无依赖。
注意字段索引从 1 开始,且 /proc/self/stat 是空格分隔的单行文本,中间可能含空格的字段(如可执行文件名)已被括号包裹,但 page fault 字段本身是纯数字,安全可按空格切分。
- 用
std::ifstream打开/proc/self/stat,逐个读取前 12 个字段(跳过第 1–9,取第 10 和第 12) - 不要用
std::string::find_first_of(' ')手动分割——括号内空格会导致错位;推荐用>>流提取操作符逐字段读取 - major fault 计数增长意味着真实 I/O 开销,minor fault 多只反映工作集变大或 TLB miss,两者语义不同,别混用
macOS 下用 task_info() + TASK_EVENTS_INFO
macOS 没有 /proc,得走 Mach API。核心是调用 task_info() 获取 task_events_info_t 结构体,其中 faults 字段即 total page faults(不分 minor/major)。
这个值是自进程启动以来的累计值,不能直接反映“当前”瞬时状态,但这是 macOS 唯一可用的原生指标。
立即学习“C++免费学习笔记(深入)”;
- 需要包含
<mach/mach.h>和<mach/task_info.h> - 先用
mach_task_self()获取当前 task port,再传给task_info(),第三个参数传TASK_EVENTS_INFO,第四个参数传task_events_info_t *地址,第五个参数传sizeof(task_events_info_t) - 返回
KERN_SUCCESS才可信;失败常见原因是权限不足或结构体大小不匹配(不同 SDK 版本字段数可能变) - 返回的
faults是 uint64_t,但文档没说明是否含 swap-in/out 次数,实际观测基本等价于 Linux 的 minor+major 总和
跨平台封装建议:别试图统一接口
Linux 和 macOS 的 page fault 定义、粒度、获取路径完全不同,强行抽象成一个函数只会增加误用风险。更务实的做法是:
- 用
#ifdef __linux__/#ifdef __APPLE__分支分别实现,各自注释清楚数据含义和更新时机 - 避免封装成“get_current_page_faults()”这种模糊命名;Linux 版明确叫
get_linux_page_faults(),返回std::pair<long, long>(minor, major);macOS 版叫get_macos_total_faults(),返回uint64_t - 如果上层逻辑真要对比,必须意识到:Linux 的 major fault ≈ macOS 的 I/O 相关 fault,但 macOS 不提供细分,所以比较只能粗粒度
容易被忽略的陷阱:fork 后计数不继承、多线程不隔离
/proc/self/stat 和 task_info() 返回的都是**当前进程**的累计值,不是线程级,也不是子进程继承的副本。
- fork 出子进程后,子进程的 page fault 计数从 0 开始,父进程的值不变——这点常被误认为“共享”
- 同一进程内多个线程触发缺页,全部计入该进程总计数,无法区分哪个线程导致;想定位热点线程得用 perf 或 eBPF
- 程序重启后计数归零,但容器或 systemd service 可能重用 PID,别把旧进程残留值当新进程初值
- 某些场景(如 mmap + MAP_POPULATE)会预加载页面,减少运行时 major fault,但
/proc/self/stat仍会计入预加载过程中的 fault —— 这属于正常行为,不代表运行时性能差


















