
用 inotify 监控 Linux 目录变更,别碰 FindFirstChangeNotification
Windows 下容易误用 FindFirstChangeNotification 做实时同步——它只通知“有变化”,不告诉你是哪个文件、什么操作(改名/删/写入),还得自己轮询比对,实际变成伪实时。Linux 上直接上 inotify 更靠谱:一次监听,事件带 IN_MOVED_TO、IN_CREATE、IN_MODIFY 等明确类型,还能读出 name 字段。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
inotify_init1(IN_CLOEXEC)创建 fd,避免 fork 后泄漏 - 每个要监控的目录调
inotify_add_watch(fd, path, IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM | IN_MODIFY),注意IN_MOVED_FROM和IN_MOVED_TO需配对识别重命名 -
read()读事件时,必须循环解析struct inotify_event,不能假设单次read()只返回一个事件 - 遇到
IN_Q_OVERFLOW表示队列溢出,说明你处理太慢或事件风暴来了,得记录告警,不能忽略
复制文件前先判断是否真需要同步:stat() + open(O_PATH) 比 system("rsync") 更可控
直接调 system("rsync -a --delete ...") 看似省事,但无法细粒度控制:比如跳过临时文件(*~、.swp)、忽略正在写的文件(IN_MODIFY 连续触发时)、或对大文件做增量拷贝。C++ 里更稳的做法是自己走系统调用链。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 收到
IN_CREATE或IN_MOVED_TO后,先stat()检查是不是普通文件、大小是否非零;跳过 socket、fifo、设备文件 - 对常规文件,用
open(path, O_RDONLY | O_NOATIME)打开再sendfile()(Linux)或copy_file_range()(5.3+),避免用户态缓冲区拷贝 - 若目标已存在,对比
st_mtime和st_size,mtime 相同且 size 相等就跳过,别盲目覆盖 - 写目标文件时用
O_TMPFILE+linkat()替代直接open(..., O_WRONLY | O_CREAT | O_TRUNC),防止同步中途崩溃留下半截文件
处理重命名和删除:靠事件配对 + 白名单缓存,别信单一事件
IN_MOVED_FROM 和 IN_MOVED_TO 的 cookie 字段是唯一配对依据,但它们可能不在同一次 read() 返回中——尤其高并发时。只依赖单个事件会把 “mv a b” 识别成“删 a + 新建 b”,导致备份目录多出冗余副本。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 维护一个
std::unordered_map<uint32_t std::string></uint32_t>缓存最近 2 秒内收到的IN_MOVED_FROM事件(key 是cookie,value 是原路径),超时自动清理 - 收到
IN_MOVED_TO时,查 cookie 是否在缓存里:命中则执行重命名操作(renameat2(AT_FDCWD, old_path, AT_FDCWD, new_path, RENAME_EXCHANGE)更安全);未命中才当新建处理 -
IN_DELETE和IN_DELETE_SELF要区分:前者删子项,后者删监控目录本身,后者需重新inotify_add_watch() - 所有路径操作前,用
realpath()标准化,避免软链接绕过监控
跨文件系统同步时,copy_file_range() 失败就 fallback 到 splice() + read()/write()
copy_file_range() 在同文件系统下能零拷贝,但跨 ext4 → btrfs 或 NFS 时直接返回 -EXDEV。这时候不能硬刚,得降级。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先试
copy_file_range(src_fd, &off_in, dst_fd, &off_out, len, 0),失败且errno == EXDEV再切方案 - 优先用
splice(src_fd, &off_in, dst_fd, &off_out, len, SPLICE_F_MOVE | SPLICE_F_NONBLOCK)(要求 src/dst 都是 pipe 或支持 splice 的文件) - 都不行再退到用户态:
read()4KB buffer +write(),但 buffer size 设为getpagesize()对齐,减少 TLB miss - 每次 write 后
fsync(dst_fd)不必要,改为每 1MB 或每 100msfdatasync()一次,平衡可靠性与吞吐
最易被忽略的是事件时序竞争:比如 IN_CREATE 和紧随其后的 IN_MODIFY 可能表示一个文件正在被写入,此时立即复制会拿到脏数据。得加简单状态机,对新文件标记 “pending_write”,等连续 100ms 无新事件才触发同步——这个延迟阈值,比任何算法都关键。



















