实现Linux文件系统的监控——Inotify学习笔记

本文最后更新于:2021-04-08

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(2) inotify_init1(2) inotify_add_watch(2) inotify_rm_watch(2)

inotify_init(2)inotify_init1(2)功能相似,若成功调用可以初始化一个inotify的实例,并返回一个用于读取文件系统事件的文件描述符,若失败则会返回-1。
不同之处在于,后者提供了一个参数。当传入0时,二者没有区别,但传入IN_NONBLOCK IN_CLOEXEC掩码后会对返回的文件描述符提供额外的控制功能,这两个掩码分别提供与fcntl(2)中的O_NONBLOCKopen(2)中的FD_CLOEXEC相似的功能,在此不对这两个掩码的功能作详细介绍。

在成功调用inotify_init(2)inotify_init1(2)后,其返回值作为文件描述符,可在后续使用read(2)系统调用来读取文件系统监控事件的信息,并且该读取是阻塞式的,当没有事件发生时,read(2)将不会返回。

inotify_add_watch(2)inotify_rm_watch(2)分别用于从监控列表中添加和删除目录/文件项。inotify_add_watch(2)接受参数为inotify实例的文件描述符、需要监控的目标文件/目录路径,以及需要监控的事件掩码,成功调用后返回一个监控描述符(Watch descriptor),对应于目标路径在文件系统中的inode对象,若失败则会返回-1并设置errno。inotify_rm_watch(2)接受参数为inotify实例的文件描述符和监控文件或目录的监控描述符,可以停止监控对应的文件或目录。

对于一个文件或目录,可以使用位或运算符合并掩码来同时监控多个事件。可监控事件列表如下:

事件掩码 事件描述 事件掩码 事件描述 事件掩码 事件描述
IN_ACCESS 访问文件 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 打开文件或目录

在设置监控成功后,通过read(2)系统调用从inotify实例提供的文件描述符中读取文件系统监控事件的信息,该信息使用inotify_event结构体来记录,该结构体的声明如下:

1
2
3
4
5
6
7
struct inotify_event {
int wd; /* 监控描述符 (Watch descriptor) */
uint32_t mask; /* 事件掩码类型 */
uint32_t cookie; /* 关联相关事件 (待进一步研究) */
uint32_t len; /* name字段的长度(包括C字符串末尾的空字符) */
char name[]; /* 引发该事件的文件或目录名的C字符串 */
};

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#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;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议