需借助操作系统底层接口采集原始数据:一、读取/proc/[pid]/io与/proc/[pid]/stat结合时间戳采样估算;二、用io_uring提交带时间戳探测请求测微秒级延迟;三、perf_event_open监听block_rq_issue/complete获取块层端到端延迟;四、eBPF在VFS层插桩实现细粒度读写延迟直方图;五、Windows下通过ETW订阅存储IO事件提取TimeStamp计算延迟。

如果您需要在 C++ 程序中获取当前进程的磁盘 I/O 读写延迟统计信息,则需借助操作系统提供的底层接口直接采集原始计时与完成事件数据。以下是实现该目标的具体方法:
一、使用 Linux /proc/[pid]/io 与 /proc/[pid]/stat 结合时间戳采样
该方法通过周期性读取进程的 I/O 累计字节数与运行时间,结合两次采样间隔计算平均延迟估算值,适用于无特权但需快速估算的场景。
1、打开 /proc/self/io 文件,解析 rchar 和 wchar 字段获取字符层读写总量。
2、打开 /proc/self/stat 文件,提取第 14 项(utime)和第 15 项(stime)获得用户态与内核态累计时钟滴答数。
3、调用 clock_gettime(CLOCK_MONOTONIC, &ts) 记录采样起始与结束的高精度单调时间戳。
4、间隔固定毫秒(如 100ms)后重复步骤 1–3,计算 Δrchar、Δwchar、Δtime_ticks、Δreal_ns。
5、将 Δreal_ns 除以 Δread_syscalls 或 Δwrite_syscalls 的估算值(需额外跟踪系统调用次数,例如通过 ptrace 或 eBPF 辅助),得到平均单次 I/O 延迟近似值。
二、通过 Linux io_uring 提交带时间戳的探测请求
该方法利用 io_uring 的 SQE 中的 IOSQE_IO_DRAIN 与用户空间时钟配合,在内核提交路径中嵌入精确时间锚点,适用于已启用 io_uring 且要求微秒级精度的低延迟应用。
1、调用 io_uring_queue_init(32, &ring, 0) 初始化一个最小队列。
2、构造一个指向临时缓冲区的 IORING_OP_READ SQE,文件描述符设为当前进程打开的一个小尺寸普通文件(如 /dev/zero)。
3、在提交前调用 clock_gettime(CLOCK_MONOTONIC_RAW, &submit_ts) 获取提交时刻时间戳,并存入用户数据字段 user_data。
4、调用 io_uring_submit(&ring) 发起请求。
5、调用 io_uring_wait_cqe(&ring, &cqe) 阻塞等待完成,立即执行 clock_gettime(CLOCK_MONOTONIC_RAW, &complete_ts)。
6、从 cqe->user_data 恢复 submit_ts,计算差值得到本次 I/O 实际延迟。
三、使用 Linux perf_event_open 系统调用捕获块设备层延迟事件
该方法通过监听内核 block_rq_issue 与 block_rq_complete tracepoint,精确捕获每个 I/O 请求在块层的发出与完成时间,可分离出排队延迟与设备服务延迟,需 CAP_SYS_ADMIN 权限或 perf_events_paranoid ≤ 1。
1、构造 struct perf_event_attr,设置 type = PERF_TYPE_TRACEPOINT,并通过 sysfs 查找 block:block_rq_issue 的 id 值赋给 config。
2、调用 perf_event_open(&attr, 0, -1, -1, 0) 创建 issue 监听器,绑定至当前进程 tid。
3、对 block:block_rq_complete 重复步骤 1–2,创建 complete 监听器。
4、调用 mmap() 映射两个 perf event 的 ring buffer,启用事件流。
5、解析 mmap 区域中的 perf_event_header,提取 payload 中的 rwbs, sector, len, comm 字段,匹配 issue 与 complete 记录(基于相同 request 地址或 sector+timestamp 窗口)。
6、对每个成功配对的请求,计算 complete_timestamp - issue_timestamp 得到端到端块层延迟。
四、通过 eBPF 程序在 VFS 层注入延迟测量探针
该方法在 sys_read/sys_write 进入点与 generic_file_read_iter/generic_file_write_iter 返回点插入 kprobe/kretprobe,记录进程上下文与时间戳,适用于需区分不同文件路径或 inode 类型的细粒度分析。
1、编写 eBPF C 代码,在 SyS_read 入口处保存 bpf_ktime_get_ns() 与当前 pid_t 到 percpu_map。
2、在 generic_file_read_iter 返回点查找对应 pid 的起始时间,计算差值并写入延迟直方图 map。
3、对 SyS_write 与 generic_file_write_iter 执行相同逻辑,分别维护 read_lat、write_lat 两个 BPF_MAP_TYPE_HISTOGRAM maps。
4、使用 libbpf 加载 eBPF 对象,调用 bpf_object__open() 和 bpf_object__load() 完成加载。
5、主程序循环调用 bpf_map__lookup_elem() 读取直方图 map 内容,按桶聚合输出延迟分布。
五、Windows 平台使用 ETW 事件追踪获取磁盘 I/O 延迟
该方法通过订阅 Microsoft-Windows-Storage class 的 IO Read/Write Start 与 IO Read/Write Done 事件,解析 EventRecord 中的 TimeStamp 字段,适用于 Windows 桌面或服务器环境下的进程级延迟分析。
1、调用 EventRegister(&GUID_EVENT_TRACE_CLASS_STORAGE) 注册存储事件提供者。
2、构建 EVENT_FILTER_DESCRIPTOR 数组,设置 EVENT_FILTER_TYPE_PID 过滤器,仅接收当前进程 ID 的事件。
3、调用 EventSetInformation() 启用 EVENT_ENABLE_PROPERTY_PROCESS_ID 保证事件携带进程上下文。
4、在回调函数中判断 EVENT_HEADER.EventDescriptor.Id 是否为 42 (IO Read Start) 或 43 (IO Read Done),提取 EventHeader.TimeStamp.QuadPart。
5、使用 unordered_map
6、当收到对应 Done 事件时,查表取出 Start 时间戳,相减后除以 10000 转为微秒,存入本地延迟向量。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











