19 inotify 监控文件事件

基本流程

  1. 使用 inotify_init 创建一个 inotify 实例,返回值是文件描述符,用来读取 inotify 监控项信息。
  2. 使用 inotify_add_watch 对 inotify 实例创建或者修改监控项目。参数 fd 指代 inotify 实例,参数 pathname 表示要监控的文件或者目录,用户必须有其读取权限(只检查一次,创建监控项目成功之后即便权限发生变化监控也不会被移除)。函数的返回值是一个监控描述符(watch descriptor,wd)。
  3. 不断使用 read 系统调用去读取 fd 关联文件的信息,然后将字节流中的地址转换成 struct inotify_event * 类型,然后判断其信息。给 read 提供的缓冲区需要足够大,至少需要容纳 sizeof(struct inotify_event) + NAME_MAX + 1 个字节,不过 read 缓冲区越大读取的效率越高。
struct inotify_event {
   int      wd;       /* Watch descriptor */
   uint32_t mask;     /* Mask describing event */
   uint32_t cookie;   /* Unique cookie associating related
                         events (for rename(2)) */
   uint32_t len;      /* Size of name field */
   char     name[];   /* Optional null-terminated name */
};
  1. 使用 inotify_rm_watch 移除监控事件。
  2. 使用 close 关闭 inotify_init 创建的 fd。

inotify_init1

Linux 还有一个系统调用 inotify_init1,它比 inotify_init 多出来一个 flags 参数,可以用来指定 IN_CLOEXEC 或者 IN_NONBLOCK。这避免了稍后用 fcntl 设置 flags 的繁琐操作。

读取监控事件

受监控目录有文件发生事件时,name 字段是一个零字符结尾字符串;受监控目录本身发生事件时,len 为 0,name 没有有效内容。可以监控的事件类型相当多,具体可以参考手册。特殊事件包括:IN_IGNORED(监控项被内核或应用移除)、IN_Q_OVERFLOW(事件队列溢出)、IN_UNMOUNT(包含监控对象的文件系统被卸载)等。

如果连续发生两个相同的事件,内核会将其合并,也就是说 inotify 接口中同一事件发生多次(中间没有其他事件)被视作只发生一次。

inotify 事件还可以用 select、poll、epoll 等函数来监听。

inotify 的资源限制

/proc 文件系统中有其限制:

(py310) xxx $ cat /proc/sys/fs/inotify/max_user_instances
128
(py310) xxx $ cat /proc/sys/fs/inotify/max_user_watches
524288
(py310) xxx $ cat /proc/sys/fs/inotify/max_queued_events
16384

每个真实用户 ID 下的 inotify 实例和总的监听数是有限的。单个监听实例队列大小也有限制,队列容量不足时,系统会丢弃数据,并将 IN_Q_OVERFLOW 作为事件写到队列中。

旧的监控方式 dnotify

dnotify 顾名思义只能监听目录,而 inotify 可以监听所有文件(inode)。此外,dnotify 的机制是发送信号,写起来可能不如 inotify 方便。而且 dnotify 会打开监听的目录,这不仅消耗文件描述符,还会使得包含目录的文件系统在进程运行期间无法卸载。dnotify 还有一些缺点,总之现在已经被 inotify 代替。