/proc/self/io 是Linux进程I/O统计快照文件,含rchar(逻辑读字节数)、wchar(逻辑写字节数)、syscr/syscw(系统调用次数)、read_bytes/write_bytes(真实磁盘I/O字节数)、cancelled_write_bytes(丢弃写入量)。

什么是 /proc/self/io 的数据结构和字段含义
/proc/self/io 是 Linux 内核为每个进程暴露的伪文件,记录该进程自启动以来累计的 I/O 统计信息。它不是实时流式数据,而是快照式、只读的文本接口,每行形如 rchar: 123456789。关键字段包括:
-
rchar:应用层读取的字节数(含read()、recv()等,含缓存命中) -
wchar:应用层写入的字节数(含write()、send()等) -
syscr:read 类系统调用次数(不管每次读多少字节) -
syscw:write 类系统调用次数 -
read_bytes:实际发起到块设备的读字节数(即真正发生磁盘/SSD IO 的量) -
write_bytes:真正落盘的写字节数(绕过 page cache 的 direct I/O 或脏页回写后计入) -
cancelled_write_bytes:被截断或覆盖的写(如truncate()导致的脏页丢弃)
注意:rchar/wchar 和 read_bytes/write_bytes 数值差异极大——前者是“逻辑 I/O”,后者才是你真正想监控的“物理磁盘 I/O”。很多初学者误把 wchar 当成磁盘写量,结果监控完全失真。
如何用 C++ 安全读取并解析 /proc/self/io
不能直接用 fscanf 或逐行 getline 后暴力 sscanf,因为内核不保证字段顺序恒定(虽目前稳定,但文档未承诺)。正确做法是按行读取,匹配字段名前缀,再提取数值。同时要处理可能的并发更新导致的短读或格式错乱。
- 打开
/proc/self/io用std::ifstream或open()+read(),推荐后者避免 C++ iostream 的 locale 和缓冲干扰 - 每次读取建议分配至少 4KB 缓冲(该文件通常
- 对每行做
strncmp(line, "read_bytes: ", 12) == 0判断,再用strtoull(line + 12, nullptr, 10)提取值;避免std::stoull抛异常 - 必须检查
errno == 0且返回值非ULLONG_MAX,否则该行解析失败,跳过即可(内核偶尔插入空行或调试字段) - 不要假设文件一定存在——容器环境或某些 hardened kernel 可能禁用
proc的部分字段,需容错
示例关键片段:
立即学习“C++免费学习笔记(深入)”;
int fd = open("/proc/self/io", O_RDONLY);
if (fd == -1) return; // 处理 errno
char buf[4096];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0) return;
buf[n] = '\0';
char *line = buf;
while (line && *line) {
char *next = strchr(line, '\n');
if (next) *next = '\0';
if (strncmp(line, "read_bytes: ", 12) == 0) {
char *end;
unsigned long long val = strtoull(line + 12, &end, 10);
if (*end == '\0' && end != line + 12 && errno == 0) {
physical_read_bytes = val;
}
}
line = next ? next + 1 : nullptr;
}
两次读取差值才是有效 IO 增量,但要注意时间窗口陷阱
单次读取 /proc/self/io 只给总量,必须间隔采样取差才能得到“某段时间内的磁盘 IO”。但这里有两个隐蔽坑:
- 进程可能被调度暂停(如被抢占、睡眠),导致两次读取间隔远大于预期,而
read_bytes在此期间仍在后台回写脏页——你测出的“峰值”其实是延迟累积,不是瞬时吞吐 - 如果进程 fork 出子进程,子进程会继承父进程的
/proc/self/io初始值(因为 fork 时统计已存在),但后续各自累加;若你在父进程中采样,会把子进程的磁盘写也算进来(除非子进程立即 exec) - 高频采样(如 open()+read()+close() 本身就要 ~1μs,频繁调用反而污染观测目标
实用建议:采样周期设为 500ms–2s,用 clock_gettime(CLOCK_MONOTONIC, &ts) 记录时间戳,差值用于计算 B/s;若需更高精度,应改用 perf_event_open() 监控 syscalls:sys_enter_read 等事件,而非依赖 /proc。
为什么不用 /proc/[pid]/io 而坚持用 /proc/self/io
用 /proc/self/io 是最轻量、无竞争的方式——它本质是内核通过 current 指针直接访问当前 task_struct 的 ioac 字段,零拷贝、无锁(仅需禁止抢占)。而 /proc/[pid]/io 需要:
- 先通过 pid 查找 task_struct,涉及哈希表查找和 RCU 锁
- 即使查的是自己(
getpid()后拼路径),也要走完整 procfs lookup 流程,慢一个数量级 - 在多线程程序中,若从非主线程读
/proc/[pid]/io,还可能因 pid 复用(线程退出后 pid 被回收)读到其他进程的数据
所以只要监控本进程,/proc/self/io 是唯一合理选择。别为了“看起来通用”而自废武功。
真正难的不是读这个文件,而是理解 read_bytes 和 write_bytes 背后依赖的内核 IO 路径(buffered vs direct、page cache 回收时机、writeback 线程行为)。同一份代码,在 ext4 和 XFS 上、有无 barrier、是否启用 swap,数值都可能差几倍——这些底层耦合点,比解析文本容易被忽略得多。



















