Inotify 机制详解

2017-10-11 kernel linux

日常工作中,经常会需要知道某些文件上的变化,一般是通过轮询机制检查文件的变化,不过这样非常低效。

Inotify 是一种文件变化通知机制,Linux 内核从 2.6.13 开始引入,而 BSD 和 Mac OS 系统中比较有名的是 kqueue,可以高效地实时跟踪文件系统的变化。

这里简单介绍其使用方法。

简介

可以通过如下方式查看是否支持 Inotify 机制。

$ grep INOTIFY_USER /boot/config-$(uname -r)
CONFIG_INOTIFY_USER=y

如果输出 CONFIG_INOTIFY_USER=y 那么就表示系统支持 inotify 机制。

源码实现

在用户态主要通过三个系统调用,首先是要创建 inotify 实例。

#include <sys/inotify.h>
int inotify_init(void);
int inotify_init1(int flags);

每个 inotify 实例对应一个独立的排序的队列,文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组:

  1. 目标,可以是文件或目录;
  2. 事件掩码,应用希望关注的 inotify 事件,每位对应一个 inotify 事件。

Watch 对象通过 watch 描述符引用,其中,目录 watches 将返回在该目录下的所有文件上面发生的事件。

下面函数用于添加一个 watch 。

#include <sys/inotify.h>
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

参数:
  fd    如上的 inotify_init() 返回的文件描述符;
  path  被监视的目标的路径名,可以是文件名或目录名;
  mask  事件掩码,在头文件 `linux/inotify.h` 中定义了每一位代表的事件。

可以使用同样的方式来修改事件掩码,即改变希望被通知的 inotify 事件。

下面的函数用于删除一个 watch 。

#include <sys/inotify.h>
int inotify_rm_watch(int fd, int wd);

参数:
  fd   同样是 inotify_init() 返回的文件描述符;
  wd   通过 inotify_add_watch() 返回的 watch 描述符。

获取事件

文件事件通过 struct inotify_event 结构表示,通过由 inotify_init() 返回的文件描述符,并调用 read() 系统调用来获得。

struct inotify_event {
	__s32           wd;             /* watch descriptor */
	__u32           mask;           /* watch mask */
	__u32           cookie;         /* cookie to synchronize two events */
	__u32           len;            /* length (including nulls) of name */
	char            name[0];        /* stub for possible name */
};

该结构的 name 字段为一个桩,它只是为了用户方面引用文件名,文件名是变长的,它实际紧跟在该结构的后面,文件名将被 0 填充以使下一个事件结构能够 4 字节对齐。

通过 read 调用可以一次获得多个事件,只要提供的 buf 足够大。

可以在函数 inotify_init() 返回的文件描述符 fd 上使用 select()poll(),也可以在 fd 上使用 ioctl 命令 FIONREAD 来得到当前队列的长度。

close(fd) 将删除所有添加到 fd 中的 watch 并做必要的清理。

内核实现

在内核中,每一个 inotify 实例对应一个 struct inotify_device 结构。

struct inotify_device {
	wait_queue_head_t       wq;             /* wait queue for i/o */
	struct idr              idr;            /* idr mapping wd -> watch */
	struct semaphore        sem;            /* protects this bad boy */
	struct list_head        events;         /* list of queued events */
	struct list_head        watches;        /* list of watches */
	atomic_t                count;          /* reference count */
	struct user_struct      *user;          /* user who opened this dev */
	unsigned int            queue_size;     /* size of the queue (bytes) */
	unsigned int            event_count;    /* number of pending events */
	unsigned int            max_events;     /* maximum number of events */
	u32                     last_wd;        /* the last wd allocated */
};

struct inotify_device 在调用 inotify_init() 时创建,当关闭对应的文件描述符时将被释放,而结构体 struct inotify_watch 在用户调用 inotify_add_watch() 时创建,在调用 inotify_rm_watch()close(fd) 时被释放。

参考

inotail 通过 inotify 机制实现的 tail 功能,不过实际上 tail 最新版本也已经支持了 inotify 机制。