文件编辑器要感知外部修改、搜索引擎要索引新文件、杀毒软件要扫描打开的文件——都需要”文件变化通知”。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
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。