真实捕获文件访问需用inotify监听IN_ACCESS/IN_OPEN事件,配合非阻塞read与epoll;内存聚合计数(unordered_map)、异步批量落盘;处理IN_IGNORED重建watch;导出用writev+tab格式。

用 fstat + inotify 捕获真实访问事件,而非轮询
Linux 下“文件被访问”不能靠修改时间(atime)判断——它默认是 relatime 或 noatime 模式,且写磁盘开销大、精度低。真正实时、轻量的方案是监听内核事件:inotify 是唯一能捕获 IN_ACCESS(需内核 2.6.37+)、IN_OPEN、IN_READ 等细粒度事件的机制。
关键点:
-
inotify_add_watch(fd, path, IN_ACCESS | IN_OPEN)才能捕获只读打开行为;仅IN_OPEN会漏掉 mmap、openat等间接访问 - 必须用非阻塞
read()配合epoll或poll(),否则线程会被卡住;单次read()可能返回多个struct inotify_event - 事件中的
wd是 watch descriptor,不是文件描述符,不能直接lseek或read - 注意
len字段:事件结构体后紧跟变长文件名,需用(char*)ev + sizeof(struct inotify_event)提取
用 std::unordered_map<:string uint64_t></:string> 做内存计数,避免频繁刷盘
高频访问场景下,每来一次事件就写一次磁盘(如追加到日志文件)会严重拖慢性能,甚至压垮 I/O。正确做法是:内存中聚合计数,异步批量落盘。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 键用绝对路径(
realpath(path, nullptr)规范化),避免软链接导致重复计数 - 值类型选
uint64_t,防止极端情况下溢出(比如每秒百万次访问) - 不要用
std::map——插入/查找复杂度 O(log n),在万级文件时明显劣于unordered_map的平均 O(1) - 若需跨进程共享,改用
boost::interprocess::unordered_map+ 共享内存,但会增加依赖和同步复杂度
处理 IN_IGNORED 和 watch 失效,防止统计中断
当被监控文件被删除、重命名或所在目录 unmount 时,内核会自动发送 IN_IGNORED 事件,对应 watch descriptor 失效。此时若继续向该 wd 添加新路径,inotify_add_watch 会失败并返回 -1,errno = EINVAL。
必须做两件事:
- 在事件循环中检查
ev->mask & IN_IGNORED,立即从本地 watch 表中移除该wd,并记录日志(如 “watch on /tmp/log.txt ignored: file removed”) - 对重要路径(如配置目录),收到
IN_IGNORED后主动调用inotify_add_watch重建 watch,但需加防抖——避免因 mv + cp 导致反复重建 - 不要假设
inotify会自动恢复 watch;它不会,这是常见断点
导出统计结果时用 writev 批量写入,规避小包 syscall 开销
当外部请求当前统计(如通过 Unix socket 或信号触发 dump),若对每个文件调用一次 write(),在数千文件时 syscall 次数爆炸。更高效的是拼成 iovec 数组,单次 writev() 输出。
示例片段:
std::vector<iovec> iov;
for (const auto& [path, cnt] : counters_) {
std::string line = path + "\t" + std::to_string(cnt) + "\n";
iov.push_back({.iov_base = const_cast<void*>(line.data()), .iov_len = line.size()});
}
writev(fd_out, iov.data(), iov.size());
注意:
- 避免用
std::endl或operator<<到std::ofstream——流操作带缓冲锁,多线程下易争用 - 输出格式用 tab 分隔,比 JSON 轻量,也方便
awk '{sum += $2} END {print sum}'快速汇总 - 若需原子替换旧统计文件,先写临时文件(
XXXXXX后缀),再rename(),避免读取时遇到截断
最常被忽略的是路径生命周期管理:inotify 不跟踪文件 inode,只认路径字符串。如果程序运行中目录被 mv,旧 watch 立即失效,而新路径不会自动被监控——必须由上层逻辑感知并重建,这点没有银弹。



















