INOTIFY - Linux手册页
Linux程序员手册 第7部分
更新日期: 2020-06-09
名称
inotify-监视文件系统事件
说明
inotify API提供了一种监视文件系统事件的机制。 Inotify可用于监视单个文件或目录。监视目录时,inotify将返回目录本身以及目录中文件的事件。
此API使用以下系统调用:
- *
- inotify_init(2)创建一个inotify实例,并返回引用该inotify实例的文件描述符。最新的inotify_init1(2)与inotify_init(2)类似,但是具有一个flags参数,该参数提供对某些额外功能的访问。
- *
- inotify_add_watch(2)操作与inotify实例关联的"监视列表"。监视列表中的每个项目("监视")都指定文件或目录的路径名,以及内核应监视该路径名所引用的文件的一些事件集。 inotify_add_watch(2)创建新的监视项,或修改现有的监视。每个手表都有一个唯一的"手表描述符",即创建手表时inotify_add_watch(2)返回的整数。
- *
- 当受监视的文件和目录发生事件时,这些事件将作为结构化数据提供给应用程序,可以使用read(2)从inotify文件描述符中读取这些结构化数据(请参见下文)。
- *
- inotify_rm_watch(2)从inotify监视列表中删除一个项目。
- *
- 当所有引用inotify实例的文件描述符都已关闭时(使用close(2)),底层对象及其资源将被释放以供内核重用;所有关联的手表都会自动释放。
通过仔细的编程,应用程序可以使用inotify来有效地监视和缓存一组文件系统对象的状态。但是,健壮的应用程序应考虑到以下事实:监视逻辑中的错误或以下所述的竞争可能会使高速缓存与文件系统状态不一致。进行一些一致性检查并在检测到不一致时重建缓存可能是明智的。
Reading events from an inotify file descriptor
为了确定发生了什么事件,应用程序从inotify文件描述符中读取(2)。如果到目前为止尚未发生任何事件,则假定使用阻塞文件描述符,则read(2)将阻塞直到至少一个事件发生(除非被信号中断,在这种情况下,调用将失败,并显示错误EINTR;请参见signal(7) ))。
每个成功的read(2)返回一个包含以下一个或多个结构的缓冲区:
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 */ };
wd标识发生此事件的手表。它是先前对inotify_add_watch(2)的调用返回的监视描述符之一。
掩码包含描述发生的事件的位(请参见下文)。
cookie是连接相关事件的唯一整数。当前,这仅用于重命名事件,并允许应用程序连接结果对IN_MOVED_FROM和IN_MOVED_TO事件。对于所有其他事件类型,cookie设置为0。
仅当返回监视目录中文件的事件时,名称字段才存在;它标识受监视目录中的文件名。该文件名以空值结尾,并且可以包括其他空字节(aq \ 0aq),以将后续的读取与合适的地址边界对齐。
len字段计算名称中的所有字节,包括空字节。每个inotify_event结构的长度因此为sizeof(struct inotify_event)+ len。
当给read(2)的缓冲区太小而无法返回有关下一个事件的信息时,其行为取决于内核版本:在2.6.21之前的内核中,read(2)返回0;在2.6.21之前的内核中,read(2)返回0。从内核2.6.21开始,read(2)失败,错误为EINVAL。指定大小的缓冲区
sizeof(structinotify_event)+ NAME_MAX + 1
足以阅读至少一个事件。
inotify events
在读取(2)一个inotify文件描述符时返回的inotify_add_watch(2)掩码参数和inotify_event结构的mask字段都是标识inotify事件的位掩码。可以在调用inotify_add_watch(2)时在mask中指定以下位,并且可以在read(2)返回的mask字段中返回以下位:
- IN_ACCESS(+)
- 文件已被访问(例如,read(2),execve(2))。
- IN_ATTRIB(*)
- 元数据已更改-例如,权限(例如chmod(2)),时间戳记(例如utimensat(2)),扩展属性(setxattr(2)),链接计数(自Linux 2.6.25起;例如,对于link(2)的目标和unlink(2)的目标)以及用户/组ID(例如chown(2))。
- IN_CLOSE_WRITE(+)
- 打开用于写入的文件已关闭。
- IN_CLOSE_NOWRITE(*)
- 未打开要写入的文件或目录已关闭。
- IN_CREATE(+)
- 在监视目录中创建的文件/目录(例如UNIX域套接字上的open(2)O_CREAT,mkdir(2),link(2),symlink(2),bind(2))。
- IN_DELETE(+)
- 文件/目录已从监视目录中删除。
- IN_DELETE_SELF
- 观看的文件/目录本身已被删除。 (如果将对象移动到另一个文件系统,也会发生此事件,因为mv(1)实际上会将文件复制到另一个文件系统,然后将其从原始文件系统中删除。)此外,随后将为该文件生成IN_IGNORED事件。监视描述符。
- IN_MODIFY(+)
- 文件已修改(例如,write(2),truncate(2))。
- IN_MOVE_SELF
- 观看的文件/目录本身已移动。
- IN_MOVED_FROM(+)
- 重命名文件时,为包含旧文件名的目录生成。
- IN_MOVED_TO(+)
- 重命名文件时,为包含新文件名的目录生成。
- IN_OPEN(*)
- 文件或目录已打开。
Inotify监视是基于inode的:监视文件时(而不是监视包含文件的目录时),可以为文件的任何链接(在相同或不同目录中)的活动生成事件。
监视目录时:
- *
- 上面标有星号(*)的事件可能同时发生在目录本身和目录内部的对象上;和
- *
- 标有加号(+)的事件仅发生在目录内的对象上(而不发生在目录本身上)。
注意:在监视目录时,通过位于受监视目录之外的路径名(即链接)执行事件时,不会为目录内的文件生成事件。
当为受监视目录内的对象生成事件时,返回的inotify_event结构中的名称字段将标识目录内文件的名称。
IN_ALL_EVENTS宏定义为上述所有事件的位掩码。调用inotify_add_watch(2)时,可以将此宏用作mask参数。
定义了两个附加的便利宏:
- IN_MOVE
- 等同于IN_MOVED_FROM | IN_MOVED_TO。
- IN_CLOSE
- 等同于IN_CLOSE_WRITE | IN_CLOSE_NOWRITE。
调用inotify_add_watch(2)时,可以在掩码中指定以下其他位:
- IN_DONT_FOLLOW(since Linux 2.6.15)
- 如果它是符号链接,请不要取消引用路径名。
- IN_EXCL_UNLINK(since Linux 2.6.36)
- 默认情况下,当监视目录子级上的事件时,即使已将子级从目录取消链接,也会为子级生成事件。对于某些应用程序,这可能导致大量不感兴趣的事件(例如,如果观看/ tmp,则其中许多应用程序会创建其名称立即取消链接的临时文件)。指定IN_EXCL_UNLINK会更改默认行为,以便在从监视目录取消链接后,不会为子级生成事件。
- IN_MASK_ADD
- 如果已经存在与路径名对应的文件系统对象的监视实例,则将掩码中的事件添加(或)到监视掩码中(而不是替换掩码);如果还指定了IN_MASK_CREATE,则会导致错误EINVAL。
- IN_ONESHOT
- 监视与一个事件的路径名相对应的文件系统对象,然后从监视列表中删除。
- IN_ONLYDIR(since Linux 2.6.15)
- 仅当路径名是目录时才监视它;如果路径名不是目录,则会导致错误ENOTDIR。使用此标志可为应用程序提供无竞争的方式,以确保受监视的对象是目录。
- IN_MASK_CREATE(since Linux 4.18)
- 仅当监视路径名尚未关联监视时,才监视它;如果已经在监视路径名,则产生错误EEXIST。
- 使用此标志为应用程序提供了一种确保新手表不修改现有手表的方式。这很有用,因为多个路径可能引用同一inode,并且多次调用inotify_add_watch(2)而没有该标志可能会破坏现有的监视掩码。
可以在read(2)返回的mask字段中设置以下位:
- IN_IGNORED
- 监视已明确删除(inotify_rm_watch(2))或自动删除了(文件已删除或文件系统已卸载)。另请参阅错误。
- IN_ISDIR
- 此事件的主题是目录。
- IN_Q_OVERFLOW
- 事件队列溢出(此事件的wd为-1)。
- IN_UNMOUNT
- 包含监视对象的文件系统已卸载。此外,随后将为监视描述符生成IN_IGNORED事件。
Examples
假设应用程序正在监视目录dir和文件dir / myfile中的所有事件。下面的示例显示了将针对这两个对象生成的一些事件。
- fd = open("dir/myfile", O_RDWR);
- 为dir和dir / myfile生成IN_OPEN事件。
- read(fd, buf, count);
- 为dir和dir / myfile生成IN_ACCESS事件。
- write(fd, buf, count);
- 为dir和dir / myfile生成IN_MODIFY事件。
- fchmod(fd, mode);
- 为dir和dir / myfile生成IN_ATTRIB事件。
- close(fd);
- 为dir和dir / myfile生成IN_CLOSE_WRITE事件。
假设一个应用程序正在监视目录dir1和dir2,以及文件dir1 / myfile。以下示例显示了可能生成的一些事件。
- link("dir1/myfile", "dir2/new");
- 为myfile生成一个IN_ATTRIB事件,为dir2生成一个IN_CREATE事件。
- rename("dir1/myfile", "dir2/myfile");
- 为dir1生成一个IN_MOVED_FROM事件,为dir2生成一个IN_MOVED_TO事件,为myfile生成一个IN_MOVE_SELF事件。 IN_MOVED_FROM和IN_MOVED_TO事件将具有相同的cookie值。
假设dir1 / xx和dir2 / yy是(唯一)链接到同一文件,并且一个应用程序正在监视dir1,dir2,dir1 / xx和dir2 / yy。按照下面给出的顺序执行以下调用将生成以下事件:
- unlink("dir2/yy");
- 为xx生成IN_ATTRIB事件(因为其链接计数更改),为dir2生成IN_DELETE事件。
- unlink("dir1/xx");
- 为xx生成IN_ATTRIB,IN_DELETE_SELF和IN_IGNORED事件,为dir1生成IN_DELETE事件。
假设应用程序正在监视目录dir和(空的)目录dir / subdir。以下示例显示了可能生成的一些事件。
- mkdir("dir/new", mode);
- 生成一个IN_CREATE | dir的IN_ISDIR事件。
- rmdir("dir/subdir");
- 为subdir生成IN_DELETE_SELF和IN_IGNORED事件,以及IN_DELETE | IN_DELETE。 dir的IN_ISDIR事件。
/proc interfaces
以下接口可用于限制inotify消耗的内核内存量:
- /proc/sys/fs/inotify/max_queued_events
- 当应用程序调用inotify_init(2)设置可以排队到相应inotify实例的事件数的上限时,将使用此文件中的值。超过此限制的事件将被丢弃,但始终会生成IN_Q_OVERFLOW事件。
- /proc/sys/fs/inotify/max_user_instances
- 这指定了每个真实用户ID可以创建的inotify实例数量的上限。
- /proc/sys/fs/inotify/max_user_watches
- 这指定了每个真实用户ID可以创建的手表数量的上限。
版本
Inotify已合并到2.6.13 Linux内核中。所需的库接口已在版本2.4中添加到glibc。 (在glibc 2.5版中添加了IN_DONT_FOLLOW,IN_MASK_ADD和IN_ONLYDIR。)
遵循规范
inotify API是特定于Linux的。
备注
可以使用select(2),poll(2)和epoll(7)来监视inotify文件描述符。当事件可用时,文件描述符指示为可读。
从Linux 2.6.25开始,信号驱动的I / O通知可用于inotify文件描述符。请参阅fcntl(2)中有关F_SETFL(用于设置O_ASYNC标志),F_SETOWN和F_SETSIG的讨论。传递给信号处理程序的siginfo_t结构(在sigaction(2)中进行了描述)具有以下字段集:si_fd设置为inotify文件描述符号; si_signo设置为信号编号; si_code设置为POLL_IN;并将POLLIN设置为si_band。
如果在inotify文件描述符上产生的连续输出inotify事件是相同的(相同的wd,mask,cookie和名称),则如果尚未读取较旧的事件,则将它们合并为一个事件(但请参阅BUGS)。这减少了事件队列所需的内核内存量,但也意味着应用程序无法使用inotify可靠地计数文件事件。
从inotify文件描述符读取返回的事件形成一个有序队列。因此,例如,可以保证在从一个目录重命名到另一个目录时,将在inotify文件描述符上以正确的顺序生成事件。
可以通过进程/ proc / [pid] / fdinfo目录中inotify文件描述符的条目查看通过inotify文件描述符监视的监视描述符集。有关更多详细信息,请参见proc(5)。 FIONREAD ioctl(2)返回可从inotify文件描述符读取的字节数。
Limitations and caveats
inotify API不提供有关触发inotify事件的用户或进程的信息。尤其是,对于通过inotify监视事件的流程,没有任何简便的方法可以将其自身触发的事件与其他流程触发的事件区分开。
Inotify仅报告用户空间程序通过文件系统API触发的事件。结果,它不会捕获网络文件系统上发生的远程事件。 (应用程序必须回退到轮询文件系统以捕获此类事件。)此外,无法使用inotify监视各种伪文件系统,例如/ proc,/ sys和/ dev / pts。
inotify API不报告由于mmap(2),msync(2)和munmap(2)可能发生的文件访问和修改。
inotify API通过文件名识别受影响的文件。但是,当应用程序处理inotify事件时,文件名可能已经被删除或重命名。
inotify API通过监视描述符标识事件。缓存监视描述符和路径名之间的映射(如果需要)是应用程序的责任。请注意,目录重命名可能会影响多个缓存的路径名。
Inotify监视目录不是递归的:要监视目录下的子目录,必须创建其他监视。对于大型目录树,这可能会花费大量时间。
如果监视整个目录子树,并且在该树中创建了一个新子目录或将现有目录重命名到该树中,请注意,在创建监视新子目录的时间时,可能已经存在新文件(和子目录)在子目录中。因此,您可能希望在添加监视之后立即扫描子目录的内容(并且,如果需要,可以为它包含的任何子目录递归添加监视)。
请注意,事件队列可能会溢出。在这种情况下,事件将丢失。健壮的应用程序应妥善处理丢失事件的可能性。例如,可能有必要重建部分或全部应用程序缓存。 (一种简单但可能昂贵的方法是关闭inotify文件描述符,清空缓存,创建新的inotify文件描述符,然后为要监视的对象重新创建监视和缓存条目。)
如果将文件系统安装在受监视目录的顶部,则不会生成任何事件,也不会为在新安装点下的对象生成任何事件。如果随后卸载了文件系统,则将随后为目录及其包含的对象生成事件。
Dealing with rename() events
如上所述,可以通过重命名(2)生成的IN_MOVED_FROM和IN_MOVED_TO事件对通过它们的共享cookie值进行匹配。但是,匹配任务有一些挑战。
从inotify文件描述符读取时,这两个事件通常在可用的事件流中是连续的。但是,这不能保证。如果多个进程正在触发受监视对象的事件,则(在极少数情况下)IN_MOVED_FROM和IN_MOVED_TO事件之间可能会出现任意数量的其他事件。此外,不能保证将事件对自动插入到队列中:可能会出现一个短暂的间隔,其中出现了IN_MOVED_FROM,而没有出现IN_MOVED_TO。
因此,将由rename(2)生成的IN_MOVED_FROM和IN_MOVED_TO事件对进行匹配具有固有的灵活性。 (不要忘记,如果将对象重命名为受监视的目录之外,甚至可能不会出现IN_MOVED_TO事件。)在大多数情况下,可以使用启发式方法(例如,假设事件始终是连续的)来确保匹配,但不可避免地会丢失某些情况,从而导致应用程序将IN_MOVED_FROM和IN_MOVED_TO事件视为无关。如果结果销毁了监视描述符并重新创建了监视描述符,则这些监视描述符将与任何未决事件中的监视描述符不一致。 (重新创建inotify文件描述符并重建缓存可能对解决这种情况很有用。)
应用程序还应考虑到IN_MOVED_FROM事件是当前调用read(2)返回的缓冲区中可能适合的最后一个事件,并且附带的IN_MOVED_TO事件可能仅在下一个read(2)时才获取。应该以(较小)超时完成操作,以允许插入IN_MOVED_FROM-IN_MOVED_TO事件对不是原子的,并且还可能没有任何IN_MOVED_TO事件。
BUGS
在Linux 3.19之前,fallocate(2)不会创建任何inotify事件。从Linux 3.19开始,对fallocate(2)的调用会生成IN_MODIFY事件。
在2.6.16之前的内核中,IN_ONESHOT掩码标志不起作用。
按照最初的设计和实现,当手表在一个事件后掉落时,IN_ONESHOT标志不会导致生成IN_IGNORED事件。但是,由于其他更改的意外影响,从Linux 2.6.36开始,在这种情况下会生成IN_IGNORED事件。
在内核2.6.25之前,旨在合并连续相同事件的内核代码(即,如果尚未读取较旧的事件,则可以合并两个最近的事件),而不是检查是否可以将最新事件与最早的未读事件。
当通过调用inotify_rm_watch(2)删除监视描述符时(或者因为监视文件被删除或包含该监视文件的文件系统被卸载),该监视描述符的任何未决未读事件仍然可以读取。由于随后使用inotify_add_watch(2)分配了监视描述符,因此内核会逐步循环遍历所有可能的监视描述符(0至INT_MAX)。分配空闲监视描述符时,不会检查该监视描述符号在inotify队列中是否有任何未决的未读事件。因此,即使对于该监视描述符编号的先前版本存在未决的未读事件,也可能重新分配监视描述符,结果应用程序随后可能会读取这些事件并将其解释为属于与新关联的文件回收的手表描述符。实际上,碰到该错误的可能性可能非常低,因为它要求应用程序通过INT_MAX监视描述符循环,释放监视描述符,同时在队列中保留该监视描述符的未读事件,然后回收该监视描述符。由于这个原因,并且由于没有报告在实际应用程序中发生错误,因此从Linux 3.15开始,尚未进行内核更改以消除此可能的错误。
示例
以下程序演示了inotify API的用法。它将目录标记为命令行参数,并等待IN_OPEN,IN_CLOSE_NOWRITE和IN_CLOSE_WRITE类型的事件。
编辑文件/ home / user / temp / foo并列出目录/ tmp时记录了以下输出。在打开文件和目录之前,发生了IN_OPEN事件。关闭文件后,发生IN_CLOSE_WRITE事件。关闭目录后,发生IN_CLOSE_NOWRITE事件。当用户按下ENTER键时,程序的执行结束。
Example output
$ ./a.out /tmp /home/user/temp Press enter key to terminate. Listening for events. IN_OPEN: /home/user/temp/foo [file] IN_CLOSE_WRITE: /home/user/temp/foo [file] IN_OPEN: /tmp/ [directory] IN_CLOSE_NOWRITE: /tmp/ [directory] Listening for events stopped.
Program source
#include <errno.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys/inotify.h> #include <unistd.h> #include <string.h> /* Read all available inotify events from the file descriptor 'fd'. wd is the table of watch descriptors for the directories in argv. argc is the length of wd and argv. argv is the list of watched directories. Entry 0 of wd and argv is unused. */ static void handle_events(int fd, int *wd, int argc, char* argv[]) { /* Some systems cannot read integer variables if they are not properly aligned. On other systems, incorrect alignment may decrease performance. Hence, the buffer used for reading from the inotify file descriptor should have the same alignment as struct inotify_event. */ char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; int i; ssize_t len; char *ptr; /* Loop while events can be read from inotify file descriptor. */ for (;;) { /* Read some events. */ len = read(fd, buf, sizeof buf); if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* If the nonblocking read() found no events to read, then it returns -1 with errno set to EAGAIN. In that case, we exit the loop. */ if (len <= 0) break; /* Loop over all events in the buffer */ for (ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; /* Print event type */ if (event->mask & IN_OPEN) printf("IN_OPEN: "); if (event->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE: "); if (event->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE: "); /* Print the name of the watched directory */ for (i = 1; i < argc; ++i) { if (wd[i] == event->wd) { printf("%s/", argv[i]); break; } } /* Print the name of the file */ if (event->len) printf("%s", event->name); /* Print type of filesystem object */ if (event->mask & IN_ISDIR) printf(" [directory]\n"); else printf(" [file]\n"); } } } int main(int argc, char* argv[]) { char buf; int fd, i, poll_num; int *wd; nfds_t nfds; struct pollfd fds[2]; if (argc < 2) { printf("Usage: %s PATH [PATH ...]\n", argv[0]); exit(EXIT_FAILURE); } printf("Press ENTER key to terminate.\n"); /* Create the file descriptor for accessing the inotify API */ fd = inotify_init1(IN_NONBLOCK); if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); } /* Allocate memory for watch descriptors */ wd = calloc(argc, sizeof(int)); if (wd == NULL) { perror("calloc"); exit(EXIT_FAILURE); } /* Mark directories for events - file was opened - file was closed */ for (i = 1; i < argc; i++) { wd[i] = inotify_add_watch(fd, argv[i], IN_OPEN | IN_CLOSE); if (wd[i] == -1) { fprintf(stderr, "Cannot watch '%s': %s\n", argv[i], strerror(errno)); exit(EXIT_FAILURE); } } /* Prepare for polling */ nfds = 2; /* Console input */ fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; /* Inotify input */ fds[1].fd = fd; fds[1].events = POLLIN; /* Wait for events and/or terminal input */ printf("Listening for events.\n"); while (1) { poll_num = poll(fds, nfds, -1); if (poll_num == -1) { if (errno == EINTR) continue; perror("poll"); exit(EXIT_FAILURE); } if (poll_num > 0) { if (fds[0].revents & POLLIN) { /* Console input is available. Empty stdin and quit */ while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n') continue; break; } if (fds[1].revents & POLLIN) { /* Inotify events are available */ handle_events(fd, wd, argc, argv); } } } printf("Listening for events stopped.\n"); /* Close inotify file descriptor */ close(fd); free(wd); exit(EXIT_SUCCESS); }
另外参见
inotifywait(1),inotifywatch(1),inotify_add_watch(2),inotify_init(2),inotify_init1(2),inotify_rm_watch(2),read(2),stat(2),fanotify(7)
Linux内核源代码树中的Documentation / filesystems / inotify.txt
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。