Inotify简介
Inotify是Linux中用于监控文件系统变化的API,自Linux 2.6.13开始集成到Linux内核中,并取代了以往的dnotify。该API可以实现用非轮询的方式,近似实时地监控目录或文件发生的变化。
利用Inotify可以十分方便地实现诸如文件实时同步、目录索引自动更新、配置文件自动重加载等功能。在Linux中可以通过man 7 inotify
查阅Inotify的手册。
API使用简介
Inotify API由头文件<sys/inotify.h>提供,其中包括函数
inotify_init() inotify_init1() inotify_add_watch() inotify_rm_watch()。
inotify_init()和inotify_init1()功能相似,若成功调用可以初始化一个inotify的实例,并返回一个用于读取文件系统事件的文件描述符,若失败则会返回-1。
不同之处在于,后者提供了一个参数。当传入0时,二者没有区别,但传入IN_NONBLOCK IN_CLOEXEC掩码后会对返回的文件描述符提供额外的控制功能,这两个掩码分别提供与fcntl()中的O_NONBLOCK和open()中的FD_CLOEXEC相似的功能,在此不对这两个掩码的功能作详细介绍。
在成功调用inotify_init()或inotify_init1()后,其返回值作为文件描述符,可在后续使用read()系统调用来读取文件系统监控事件的信息,并且该读取是阻塞式的,当没有事件发生时,read()将不会返回。
inotify_add_watch()和inotify_rm_watch()分别用于从监控列表中添加和删除目录/文件项。inotify_add_watch()接受参数为inotify实例的文件描述符、需要监控的目标文件/目录路径,以及需要监控的事件掩码,成功调用后返回一个监控描述符(Watch descriptor),对应于目标路径在文件系统中的inode对象,若失败则会返回-1并设置errno。inotify_rm_watch()接受参数为inotify实例的文件描述符和监控文件或目录的监控描述符,可以停止监控对应的文件或目录。
对于一个文件或目录,可以使用位或运算符合并掩码来同时监控多个事件。可监控事件列表如下:
事件类型
常规事件类型
mask 标志 | 描述 |
---|---|
IN_ACCESS | 文件被访问(执行了 read 操作) |
IN_ATTRIB | 文件元数据发生变更 |
IN_CLOSE_WRITE | 关闭为了写入而打开的文件 |
IN_CLOSE_NOWRITE | 关闭以只读方式打开的文件 |
IN_CREATE | 在受控目录内创建了文件或者目录 |
IN_DELETE | 在受控目录内删除了文件或者目录 |
IN_DELETE_SELF | 删除受控文件或者目录本身 |
IN_MOVED_FROM | 文件移出受控目录之外 |
IN_MOVED_TO | 文件移入受控目录 |
IN_OPEN | 文件被打开 |
IN_MOVE | IN_MOVED_FROM|IN_MOVED_TO 事件的统称 |
IN_CLOSE | IN_CLOSE_WRITE|IN_CLOSE_NOWRITE 统称 |
- IN_ATTRIB监控的元数据变更包括,权限,所有权,链接数,用户 ID 等
重命名受控对象时会发出IN_DELETE_SELF事件,而如果受控目标是一个目录,那么受控目标下的文件发生重命名时会触发两个事件IN_MOVED_FROM和IN_MOVED_TO
在我们的日常开发工作中,上述事件已经基本涵盖了文件变更的所有情况。我们可以按照各自的场景,针对上述不同的事件类型做出相应的处理流程。
其他事件
除了上述文件的常规事件外,inotify
还提供了以下几个 mask 来控制事件监听的过程
mask 标志 | 描述 |
---|---|
IN_DONT_FOLLOW | 不对符号链接引用 |
IN_MASK_ADD | 将事件追加到 pathname 的当前监控掩码 |
IN_ONESHOT | 只监控 pathname 的一个事件 |
IN_ONLYDIR | pathname 不为目录时会失败 |
将上述 mask 标志添加到 inotify_add_watch中时可以控制监听过程,这么说有点笼统,举个例子来说。
inotify_add_watch(fd, pathname, IN_OPEN | IN_CLOSE | IN_ONESHOT);
上面这段代码,除了监听文件的 IN_OPEN
和IN_CLOSE
事件外,还添加了 IN_ONESHOT
mask,那么这就意味着,当监听到 pathname 所指代的文件一次事件后 inotify
就不会在监听 pathname 所指代的文件发出的事件了。
上述 mask 是在添加某个文件监控项的时候作为inotify_add_watch
系统调用的参数传入的。除此之外还有以下几个事件,这些事件不需要用户显示调用inotify_add_watch
添加,仅当出现一些其他异常情况时发出。
mask 标志 | 描述 |
---|---|
IN_IGNORED | 监控项为内核或者应用程序移除 |
IN_ISDIR | 被监听的是一个目录的路径 |
IN_Q_OVERFLOW | 事件队列溢出 |
IN_UNMOUNT | 包含对象的文件系统遭到卸载 |
IN_ISDIR
事件表明被监听的 pathname 指代的是一个目录,举例来说 mkdir /tmp/xxx 这个系统命令会产生IN_CREATE|IS_DIR
事件。
在设置监控成功后,通过read()
系统调用从inotify实例提供的文件描述符中读取文件系统监控事件的信息,该信息使用inotify_event
结构体来记录,该结构体的声明如下:
|
示例代码
#include <stdio.h> #include <sys/inotify.h> #include <unistd.h> int main(void) { int eventFd = inotify_init(); if (eventFd < 0) { printf("Failed to create inotify instance\n"); return 1; } int wd = inotify_add_watch( eventFd, "/home/kayo", IN_CREATE | IN_DELETE | IN_MODIFY | IN_CLOSE_WRITE); if (wd < 0) { printf("Failed to add watch\n"); return 1; } printf("Start to watch directory /home/kayo\n"); char buffer[4096]; while (1) { ssize_t len = read(eventFd, buffer, sizeof(buffer)); if (len == -1) { printf("End watch\n"); break; } ssize_t i = 0; while (i < len) { struct inotify_event *event = (struct inotify_event *)(buffer + i); if (event->mask & IN_CREATE) { printf("IN_CREATE: %s\n", event->name); } if (event->mask & IN_MODIFY) { printf("IN_MODIFY: %s\n", event->name); } if (event->mask & IN_DELETE) { printf("IN_DELETE: %s\n", event->name); } if (event->mask & IN_CLOSE_WRITE) { printf("IN_CLOSE_WRITE: %s\n", event->name); } i += event->len + sizeof(struct inotify_event); } } return 0; } |