使用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创建的监控项数量的限制值