inotify是Linux下文件变更监听的事实标准方案,需手动调用inotify_init1和inotify_add_watch,注意IN_CLOEXEC标志、IN_Q_OVERFLOW处理、非递归特性及IN_MOVED_TO/IN_MOVED_FROM配对识别。

Linux下用inotify实现文件变更监听
Linux原生不提供跨进程的文件系统Watcher API,inotify是事实标准方案,轻量、稳定、内核级支持。它不是C++标准库功能,需手动封装或借助第三方库(如libuv、boost.fs),但直接调用inotify_init1和inotify_add_watch最可控。
常见错误是漏掉IN_CLOEXEC标志导致子进程继承fd,或未处理IN_Q_OVERFLOW事件导致后续事件丢失。监听目录时默认不递归,要监控子目录必须逐层add_watch。
- 每个watcher对应一个inotify fd,一个fd可添加多个watch(通过不同
wd区分) -
IN_MOVED_TO和IN_MOVED_FROM需配对识别重命名,单独看IN_MOVED_TO可能误判为新建 - 事件读取必须用
read()一次性读完缓冲区,否则残留数据会阻塞下次poll() - 路径长度受
NAME_MAX限制,长路径需用AT_FDCWD+ 相对路径规避
Windows上用ReadDirectoryChangesW轮询替代
Windows没有类似inotify的异步通知机制,ReadDirectoryChangesW是唯一推荐方案——它本质是同步I/O,但配合IOCP或单独线程+超时WaitForSingleObject可模拟异步行为。别用FindFirstChangeNotification,它粒度粗(只分“文件”“目录”两级)、不返回具体文件名、且无法获取变更类型。
容易踩的坑:传入的lpBuffer必须用HeapAlloc或VirtualAlloc分配,栈内存会导致访问违规;bWatchSubtree设为TRUE才能监听子目录;每次调用后必须解析FILE_NOTIFY_INFORMATION链表,靠NextEntryOffset跳转,不能简单按结构体大小偏移。
立即学习“C++免费学习笔记(深入)”;
- 单次调用最多返回约64KB事件,大变更需循环读取直到
NextEntryOffset == 0 -
FILE_ACTION_RENAMED_OLD_NAME和FILE_ACTION_RENAMED_NEW_NAME成对出现,中间可能夹杂其他事件 - 监听网络路径(UNC)时需确保服务端支持,部分SMB版本会静默丢弃通知
跨平台封装要注意的兼容性断点
inotify和ReadDirectoryChangesW语义差异极大:前者事件即时、精确到文件级、支持多种掩码组合;后者事件延迟不定(通常毫秒级)、路径可能被截断、重命名需手动关联。跨平台库(如watchdog Python版、efsw C++版)常在Windows上做额外缓存比对来补全信息,但这引入了竞态和内存开销。
关键决策点:
- 是否需要递归监听?inotify需遍历目录树注册每个子目录;Windows可直接设
bWatchSubtree=TRUE - 是否接受短暂延迟?Windows下
ReadDirectoryChangesW最小超时约10ms,高频小文件写入可能合并事件 - 是否需支持符号链接?inotify默认跟随,Windows需显式用
FILE_FLAG_OPEN_REPARSE_POINT - 权限变更(
chmod、chown)在Windows无对应事件,Linux需额外监听IN_ATTRIB
避免阻塞主线程的典型线程模型
无论哪种平台,Watcher都不能在主线程里同步等待事件——read()或ReadDirectoryChangesW都会阻塞。正确做法是用独立线程+条件变量,或把fd/handle加入epoll/kqueue/IOCP进行多路复用。
示例简化逻辑(Linux):
int inotify_fd = inotify_init1(IN_CLOEXEC); int wd = inotify_add_watch(inotify_fd, "/path", IN_CREATE | IN_DELETE | IN_MOVED_TO); // 启动线程循环read(),解析struct inotify_event // 解析后通过std::queue或std::function回调通知业务层
Windows下更推荐IOCP:将CreateIoCompletionPort绑定到目录handle,每次ReadDirectoryChangesW后投递新请求,由IOCP线程池分发完成包。手动管理线程易出错,比如忘记CancelIoEx导致句柄泄漏。
真正麻烦的是事件去重和时序保证——两个.tmp文件连续创建又删除,可能被合并为一次IN_MOVED_TO,而业务层需要知道这是“写入完成”的信号。这种逻辑必须在Watcher之上再加一层状态机,不能依赖底层事件本身。


















