文件编辑器要感知外部修改、搜索引擎要索引新文件、杀毒软件要扫描打开的文件——都需要”文件变化通知”。Linux 经历了 dnotify → inotify → fanotify 三代。
一、先看图
flowchart LR
VFS[VFS 操作<br/>create/write/rename...] --> FSN[fsnotify 框架]
FSN --> INOT[inotify 后端<br/>per-watch fd]
FSN --> FAN[fanotify 后端<br/>per-mount/sb]
FSN --> AUDIT[audit 后端]
INOT --> APP1[用户进程<br/>read 事件]
FAN --> APP2[用户进程<br/>read + 拦截]
classDef kern fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef user fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class VFS,FSN kern
class INOT,FAN,AUDIT kern
class APP1,APP2 user
二、fsnotify 内核框架
所有文件通知共享 fsnotify 基础设施:
- VFS
操作点插桩(
fsnotify_create、fsnotify_modify、fsnotify_unlink等) - 后端(backend):inotify、fanotify、audit
- 事件分发:检查 mark(per-inode / per-mount / per-sb)→ 匹配 → 排队
三、dnotify(已废弃)
fcntl(dirfd, F_NOTIFY, DN_MODIFY | DN_CREATE);缺点:只能监控目录 fd、用 signal 通知(不安全)、不告诉你是哪个文件变了。
四、inotify
4.1 API
int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
int wd = inotify_add_watch(ifd, "/tmp", IN_CREATE | IN_MODIFY | IN_DELETE);
// read(ifd, buf, ...) → struct inotify_event
inotify_rm_watch(ifd, wd);4.2 优势
- 事件内容包含文件名
- 可以 epoll 组合
- 比 dnotify 好得多
4.3 限制
- 不递归:只监控一层目录
- 不跟踪 rename across directories:看到 IN_MOVED_FROM + IN_MOVED_TO,但需要 cookie 配对
- watch
数量限制:
fs.inotify.max_user_watches(默认 8192-65536) - overflow:事件队列满 → IN_Q_OVERFLOW → 丢事件
- 不能拦截:只通知,不能阻止操作
4.4 递归监控
应用需要手动递归添加 watch → 大目录树要加几万个 watch → 消耗内存(每 watch ~1KB 内核内存)。
sysctl fs.inotify.max_user_watches=524288VS Code、IntelliJ、webpack 都靠 inotify。
五、fanotify
5.1 特点
- per-mount / per-sb 监控:不需要逐目录加 watch
- 内容拦截(FAN_OPEN_PERM / FAN_ACCESS_PERM):可以拒绝文件操作
- FAN_REPORT_FID(5.1+):报告 file handle 而非只有 fd → 支持递归无需每目录 watch
5.2 API
int fan_fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_REPORT_FID, O_RDONLY);
fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
FAN_CREATE | FAN_DELETE | FAN_MODIFY | FAN_ONDIR,
AT_FDCWD, "/mnt");
// read(fan_fd, buf, ...) → struct fanotify_event_metadata5.3 拦截模式
// 读到 FAN_OPEN_PERM 事件
struct fanotify_response resp = {
.fd = event->fd,
.response = FAN_ALLOW, // 或 FAN_DENY
};
write(fan_fd, &resp, sizeof(resp));杀毒软件(ClamAV on-access scanner)用这个。
5.4 FAN_REPORT_FID
fanotify_init(FAN_REPORT_FID | FAN_REPORT_DIR_FID | FAN_REPORT_NAME, ...);事件包含目录 file handle + 文件名 → 可以定位到完整路径,不需要 per-directory watch。
六、对比
| 特性 | dnotify | inotify | fanotify |
|---|---|---|---|
| 粒度 | 目录 | 目录(+文件名) | mount/sb/文件系统 |
| 递归 | 否 | 手动 | FAN_REPORT_FID 原生 |
| 拦截 | 否 | 否 | FAN_*_PERM |
| 通知方式 | signal | fd (read) | fd (read) |
| 权限 | 用户 | 用户 | 需要 CAP_SYS_ADMIN |
七、高频场景与 overflow
npm install 创建几万文件 → inotify 事件风暴
→ queue overflow。
缓解:
- 增大
fs.inotify.max_queued_events(默认 16384) - 用 fanotify FAN_REPORT_FID(mount 级,更高效)
- 应用侧做 debounce / batch
八、audit 联动
audit 子系统也用 fsnotify:
auditctl -w /etc/passwd -p wa -k passwd_changes每次写 /etc/passwd 产生 audit 记录。与
inotify/fanotify 并行。
九、观察
# inotify watch 使用
find /proc/*/fdinfo -name '*' -exec grep -l inotify {} \; 2>/dev/null
cat /proc/sys/fs/inotify/max_user_watches
# fanotify
cat /proc/sys/fs/fanotify/max_user_marks十、小结
- fsnotify 是内核统一框架
- inotify 适合用户态工具(编辑器、文件同步),但不递归、不拦截
- fanotify 适合系统级监控(杀毒、审计),支持拦截和文件系统级 watch
- 高频场景注意 overflow 和内存开销
参考文献
Documentation/filesystems/fanotify.rstman 7 inotify、man 7 fanotifyfs/notify/- Amir Goldstein, “fanotify: scalable filesystem notification.” LPC 2019
工具
inotifywait/inotifywatch(inotify-tools)fatrace(fanotify 前端)auditctl/ausearchstrace -e inotify_add_watch
上一篇:xattr/ACL/capabilities 下一篇:OverlayFS
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内核内存调试
内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。
【操作系统百科】VFS 四层抽象
Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。
操作系统百科
Linux 6.x 视角下的操作系统系列索引:110 篇覆盖调度、虚拟内存、文件系统与 I/O、并发、隔离、可观测性,按主题、阅读路径与关键问题三种入口组织。
【操作系统百科】用户态分配器:jemalloc vs tcmalloc
jemalloc 与 tcmalloc 都想解决多线程分配器的老问题:锁争抢、碎片、RSS 膨胀与回收抖动。但两者把优化重点放在了不同位置:tcmalloc 更激进地把热路径推到 per-CPU,jemalloc 则把 arena、extent、decay 和 profiling 做成了一套更完整的内存治理工具箱。