使用inotifyAPI的几个关键步骤:

1、使用inotify_init()创建一个inotify实例,返回一个文件描述符

2、使用inotify_add_watch()向inotify实例的监控列表添加条目。每个监控项都包含一个路径名以及相关的位掩码。位掩码针对路径名指明了所要监控的事件集合。函数返回一个监控描述符,用于指代该监控项

3、针对inotify文件描述符执行read()操作,每次对read()的成功调用,都会返回一个或多个inotify_event结构,其中各自记录了处于inotify实例监控之下的某一路径名所发生的事件。

4、关闭inotify文件描述符。这会自动清除与inotify实例相关的所有监控项



inotify机制可用于监控文件或目录,当监控目录时,与路径自身及其所含文件相关的事件都会通知给应用程序。但inotify机制是非递归的,若需要监控整个目录子树,则需对该树中中的每个目录发起inotify_add_watch()调用。可使用select()、poll()、epoll()以及由信号驱动的I/O来监控inotify文件描述符。只要有事件可供读取,上述API便会将inotify文件描述符标记为可读。

inotify机制属于可选的Linux内核组件,通过CONFIG_INOTIFY和CONFIG_INOTIFY_USER选项配置。



Inotify API

#include <sys/inotify.h>

int inotify_init(void);


#include <sys/inotify.h>

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

可追加新的监控项,也可修改现有监控项

pathname标识欲创建或修改的监控项所对应的文件,调用程序必须对该文件具有读权限(调用inotify_add_watch()时,会对文件权限做一次性检查。只要监控项继续存在,即便有人更改了文件权限,使调用程序不再对文件具有读权限,调用程序依然会继续收到文件的通知消息)。


#include <sys/inotify.h>

int inotify_rm_watch(int fd, uint32_t wd);

删除监控项会为该监控描述符生成IN_IGNORED事件。




Inotify事件

位值

In

Out

描述

IN_ACCESS

文件被访问(read)

IN_ATTRIB

文件元数据改变

IN_CLOSE_WRITE

关闭为了写入而打开的文件

IN_CLOSE_NOWRITE

关闭以只读方式打开的文件

IN_CREATE

在受监控的目录内创建了文件/目录

IN_DELETE

在受监控目录内删除文件/目录

IN_DELETE_SELF

删除受控目录/文件本身

IN_MODIFY

文件被修改

IN_MOVE_SELF

移动受监控目录/文件本身

IN_MOVED_FROM

文件移出到受控目录之外

IN_MOVED_TO

将文件移入受控目录

IN_OPEN

文件被打开

IN_ALL_EVENTS


以上所有输出事件的统称

IN_MOVE


IN_MOVED_ FROM|TO的统称

IN_CLOSE


IN_CLOSE_ WRITE|NOWRITE的统称

IN_DONT_FOLLOW


不对符号链接解引用

IN_MASK_ADD


将事件追加到pathname的当前监控掩码

IN_ONESHOT


只监控pathname的一个事件

IN_ONLYDIR


pathname不是目录会失败

IN_IGNORED


监控项为内核或应用程序所移除

IN_ISDIR


name中返回的所有文件名为路径

IN_Q_OVERFLOW


事件队列溢出

IN_UNMOUNT


包含对象的文件系统遭卸载


细节:

当文件元数据(比如,权限、所有权、链接计数、扩展属性、用户ID、组ID等)改变时,会发生IN_ATTRIB事件。

IN_DONT_FOLLOW、IN_MASK_ADD、IN_ONESHOT和IN_ORLYDIR位并非对监控事件的定义,而是意在控制inotify_add_watch()系统调用的行为。

IN_DONT_FOLLOW规定,若pathname为符号链接,则不对其解引用,而是监控符号链接

若对已为同一inotify描述符所监控的同一路径名再次执行inotify_add_watch()调用,那么默认情况下会用给定的mask掩码来替换该监控项的当前掩码。如果指定了IN_MASK_ADD,则会用mask与当前掩码相或

IN_ONESHOT允许只监控pathname一个事件,事件发生后,监控项自动消失

只有pathname为目录时,IN_ONLYDIR才允许应用程序对其进行监控,否则报错为ENOTDIR。如要确保监控对象为目录,该标志可以避免竞争条件的发生



读取inotify事件

可用read()从inotify文件描述符中读取事件,以判定发生了哪些事件。若时至读取时尚未发生任何事件,read()会阻塞下去,直至有事件产生(除非对该文件描述符设置了O_NONBLOCK状态标志,这时若无任何事件可读,read()将立即失败,并报错EAGAIN)。

事件发生后,每次调用read()会返回一个缓冲区,内含一个或多个如下类型的结构

struct inotify_event {

int wd; \\Watch descriptor on which event occurred

uint32_t mask; \\Bits descriping event that occurred

uint32_t cookie; \\Cookie for related events

uint32_t len; \\Size of 'name' field

char name[]; \\Optional null-terminated filename

};


mask字段返回该事件的位掩码,注意下列更多的细节:

移出监控项时,会产生IN_IGNORED事件,起因可能由两个:其一,应用程序使用了inotify_rm_watch()系统调用显式移除监控项;其二,因受监控对象被删除或其所驻留的文件系统遭卸载,致使内核隐式删除监控项,以IN_ONESHOT而建立的监控项因事件触发而遭自动移除时,不会产生IN_INGORED事件。

如果事件主体为路径,那么除去其它位以外,在mask中还会设置IN_ISDIR位。

IN_UNMOUT事件会通知应用程序包含受监控对象的文件系统已遭卸载。该事件发生后还会产生包含IN_IGNORED置位的附加事件。

cookie字段可将相关事件联系在一起。目前,只有在对文件重命名时才会用到该字段。这种情况下,系统会对重命名文件所在目录产生IN_MOVED_FORM事件,然后,会针对重命名后文件的所在目录产生IN_MOVED_TO事件。两个事件cookie值相等


name,当受监控目录中有文件发生事件时,name字段返回一个以空字符结尾的字符串,以标识该文件。若受监控对象自身有事件发生,则不使用name字段,将len字段置为0。

len字段标识name的字节数,name的字符串结尾和下一个inotify_event结构的开始之间,可能会由额外的填充字节,单个inotify事件的长度是sizeof(struct inotify_event)+len



如果传递给read()缓冲区过小,将失败并返回EINVAL,只要确保缓冲区足以容纳下至少一个事件即可,传给read()的缓冲区应至少为sizeof(struct inotify_event)+NAME_MAX+1。对inotify描述符所执行的read(),将在已发生事件数量与缓冲区可容纳事件数量间去最小值并返回之。

针对文件描述符fd调用ioctl(fd,FIONREAD,&namebytes),会返回其所指代的inotify实例中的当前可读字节数。


从inotify文件描述符中读取的事件形成了一个有序队列,在事件队列末尾追加一个新事件时,如果此新事件与队列当前尾部事件有相同的wd,mask,cookie值,那么内核会将两者合并。




队列限制和/proc文件

对inotify事件做排队处理,需要消耗内核内存,所以内核会对inotify机制的操作施以各种限制。超级用户可配置/proc/sys/fs/inotify路径中的3个文件来调整这些限制:

max_queued_events 默认值16384

调用inotify_init()时,使用该值来为新的inotify实例队列中的事件数量设置上限,一旦超过这一上限,系统将生成IN_Q_OVERFLOW事件,并丢弃多余事件,溢出事件的wd字段值为-1。

max_user_instances 默认值128

对由每个真实用户ID创建inotify实例数的限制值

max_user_watches 默认值8192

对由每个真实用户ID创建的监控项数量的限制值