Linux 中的 inotify 是什么

介绍 Linux inotify 文件系统事件监控机制的工作原理、核心 API、可监控事件类型、典型使用场景与常见坑点,并给出 C 和命令行的最小示例。

语速

如果你做过开发工具的热重载、文件同步,或者写过日志实时监控的脚本,大概率绕不开一个东西——inotify

它是 Linux 内核给你的一个「文件系统变化订阅器」:注册一个文件描述符,告诉它盯着哪个目录,之后有任何风吹草动,它就通过这个 fd 把事件推送给你,免去了 busy polling。

本文聊聊它的原理、用法和踩坑点。

是什么

inotify(inode notify)是 Linux 内核提供的一种文件系统事件监控机制(kernel subsystem),自 Linux 2.6.13 起被引入。它允许应用程序实时监控文件或目录的变化,而无需通过轮询(polling)这种低效方式。

核心概念

inotify 基于**文件描述符(fd)**工作,应用程序通过一组系统调用与它交互:

系统调用作用
inotify_init()创建一个 inotify 实例(旧接口,无 flags)
inotify_init1()创建一个 inotify 实例,可指定 IN_NONBLOCK/IN_CLOEXEC
inotify_add_watch()把某个文件/目录加入监控,返回一个 watch 描述符(wd)
read()从 fd 读取发生的事件
inotify_rm_watch()移除监控
close()关闭 fd,自动清理所有监控

可监控的事件类型

常见的 event mask 包括:

  • IN_CREATE / IN_DELETE —— 文件/目录被创建、删除
  • IN_MODIFY —— 文件内容被修改
  • IN_MOVED_FROM / IN_MOVED_TO —— 移动(重命名)
  • IN_ATTRIB —— 属性变化(权限、属主、时间戳等)
  • IN_ACCESS / IN_CLOSE_WRITE / IN_OPEN —— 访问、打开、关闭
  • IN_DELETE_SELF / IN_MOVE_SELF —— 被监控对象自身被删除/移动

还可以组合 IN_ALL_EVENTSIN_ONLYDIRIN_DONT_FOLLOW 等控制标志。

一个最小示例(C)

阻塞模式下,read() 会一直等到有事件才返回(教学场景最直观):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <sys/inotify.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // 0 = 阻塞模式;要非阻塞请传 IN_NONBLOCK,但需配合 poll/select + 循环使用
    int fd = inotify_init1(0);
    if (fd < 0) { perror("inotify_init1"); exit(1); }

    // 监控当前目录;换成你想监控的路径即可
    int wd = inotify_add_watch(fd, ".",
                               IN_CREATE | IN_MODIFY | IN_DELETE);
    if (wd < 0) { perror("inotify_add_watch"); exit(1); }

    char buf[4096];
    // 阻塞读,直到有事件到来
    ssize_t len = read(fd, buf, sizeof(buf));
    if (len < 0) { perror("read"); exit(1); }

    // read 读出来的是变长的 inotify_event 数组
    for (char *p = buf; p < buf + len; ) {
        struct inotify_event *ev = (struct inotify_event *)p;
        if (ev->len > 0) {
            printf("wd=%d mask=%u name=%s\n", ev->wd, ev->mask, ev->name);
        } else {
            printf("wd=%d mask=%u (no name)\n", ev->wd, ev->mask);
        }
        p += sizeof(struct inotify_event) + ev->len;
    }

    inotify_rm_watch(fd, wd);
    close(fd);
    return 0;
}

关键点:

  • read() 读出来的是变长的 inotify_event 结构体数组,按 sizeof(struct inotify_event) + ev->len 步进解析。
  • ev->len == 0(如 IN_DELETE_SELF 这类被监控对象自身的事件)时 name 无效,要单独处理。
  • 示例用阻塞模式 + 单次 read() 演示「等到一个事件就处理」;生产代码里通常会包一层 while 循环持续读,或非阻塞 fd 配合 poll/select/epoll 做事件循环。

命令行工具

不想写代码也能用,inotify-tools 提供了两个好用的命令:

1
2
3
4
5
6
7
8
# 安装
sudo apt install inotify-tools

# 监控某个目录的所有事件
inotifywait -m -r /path/to/dir

# 只关心修改和创建
inotifywait -m -e modify,create /var/log

排障、写临时脚本时非常顺手。

典型使用场景

  1. 开发工具热重载 —— 监控源码变化自动重启服务,比如 nodemonvitewebpack-dev-server
  2. 守护进程 / 同步工具 —— rsync 类增量同步、Dropbox / 网盘客户端的文件变动检测。
  3. 桌面环境 —— 文件管理器(Nautilus、Dolphin)自动刷新目录视图。
  4. 日志监控 —— 实时 tail 日志文件,新行写入即推送。
  5. 安全审计 / 入侵检测 —— 监控关键配置目录是否被篡改。

使用注意与限制

这一节是真正值钱的部分,实际项目里踩过的坑基本都在这。

不递归。 监控目录时,inotify 只报告直接子项的变化。要监控整棵目录树,你得自己递归 inotify_add_watch。好在主流库都帮你封装了这点。

资源上限。 受内核参数约束,单实例能加的 watch 数量是有限的:

1
2
3
4
5
# 查看当前上限
cat /proc/sys/fs/inotify/max_user_watches      # 默认常为 8192 或 524288

# 调大
sudo sysctl fs.inotify.max_user_watches=524288

超出会报 ENOSPC(注意是 ENOSPC 不是 EMFILE,第一次遇到很容易懵)。在大项目里用 VSCode、WebStorm 时报这个错很常见,根因就是 watch 数被打满了。

事件可能被合并。 内核不保证每个字节写入都对应一个 IN_MODIFY 事件,短时间内多次修改可能被合并成一条。事件读取跟不上时队列还会溢出,此时内核会发一个 IN_Q_OVERFLOW 事件(mask 中带这个位、wd 为 -1)提醒你「有事件丢了」,这也是为什么在生产代码里要尽快消费事件,别让 read 长时间不读。

非 Linux 系统要适配。 macOS 用 kqueue / FSEvents,Windows 用 ReadDirectoryChangesW。跨平台时别直接裸调 inotify,用封装库更省心。

跨平台封装库

注意 C/C++ 这块:libinotify / inotify-tools 本质是 Linux inotify 的便利封装,并不跨平台。要真正在 macOS/Windows 之间统一,用 libuvboost::asio 这类事件循环库更合适。

语言Linux 原生封装跨平台方案
C/C++libinotifyinotify-toolslibuvboost::asio
Node.jsfs.watchchokidar
Pythonpyinotifywatchdog
Gofsnotify/fsnotify(内部按平台分派)
Rustnotify

理解了 inotify 的原理,再去看 chokidarfsnotifynotify 这类库的实现就会顺畅很多——它们在不同平台上分别落到 inotify(Linux)、kqueue/FSEvents(macOS)、ReadDirectoryChangesW(Windows),抽象层之下就是这套机制。

小结

一句话:inotify 是 Linux 内核内置的「文件系统变化订阅器」——注册一个 fd,告诉它盯着哪里,之后的变化就以事件形式推送过来,免去了 busy polling。

它是几乎所有文件热重载、实时同步工具在 Linux 上的底层基石。