文件编辑器要感知外部修改、搜索引擎要索引新文件、杀毒软件要扫描打开的文件——都需要”文件变化通知”。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
文章导航
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
CPU 调度:CFS 的虚拟运行时间与 EEVDF 的虚拟截止期
从比例份额调度的论文谱系出发,对照 Linux v6.6 与 v6.12 fair.c 源码,解释 CFS 的 vruntime、EEVDF 的 lag/eligible/deadline,并用确定性模拟复现延迟与 lag 的差异。
I/O 调度:在寻道、队列深度与公平性之间取舍
从电梯算法到 blk-mq,解释 Linux I/O 调度器为何在 HDD 上排序、在共享设备上保公平、在 NVMe 上常选择 none,并用确定性模拟展示寻道距离与队列尾延迟的取舍。
用户态内存分配器:size class、线程缓存与碎片边界
从 Wilson 分配器综述到 jemalloc、gperftools tcmalloc 与 mimalloc 的源码路径,解释 size class、线程缓存、跨线程释放和 RSS 碎片;用可复现实验说明不能只凭 allocator 名字下结论。
伙伴系统与 SLUB:Linux 物理页和小对象分配的边界
以 Linux v6.6 源码和确定性模拟为准,解释伙伴系统的 split/coalesce、migratetype 反碎片策略,以及 SLUB 的 per-CPU freelist、partial list 与安全加固。