从文件管理器到安全工具,文件系统监控对于的许多程序来说都是必不可少的。从 Linux 2.6.13 内核开始,Linux 就推出了 inotify,允许监控程序打开一个独立文件描述符,并针对事件集监控一个或者多个文件,例如打开、关闭、移动/重命名、删除、创建或者改变属性。在后期的内核中有了很多增强,因此在依赖这些特性之前,请先检查您的内核版本。
在 inotify 之前有 dnotify。不幸的是,dnotify 有局限性,用户需要更好的产品。和 dnotify 相比 inotify 的优势如下:
Inotify 使用一个独立的文件描述符,而 dnotify 需要为每个受监控的目录打开一个文件描述符。当您同时监控多个目录时成本会非常高,而且还会遇到每进程文件描述符限制。
Inotify 所使用的文件描述符可以通过系统调用获得,并且没有相关设备或者文件。而使用 dnotify,文件描述符就固定了目录,妨碍备用设备卸载,这是可移动媒体的一个典型问题。对于 inotify,卸载的文件系统上的监视文件或目录会产生一个事件,而且监视也会自动移除。
Inotify 能够监视文件或者目录。Dnotify 则只监视目录,因此程序员还必须保持 stat 结构或者一个等效的数据结构,来反映该被监视目录中的文件,然后在一个事件发生时,将其与当前状态进行对比,以此了解当前目录中的条目发生了什么情况。
如上所述,inotify 使用文件描述符,允许程序员使用标准 select 或者 poll 函数来监视事件。这允许高效的多路复用 I/O 或者与 Glib 的 mainloop 的集成。相比之下,dnotify 使用信号,这使得程序员觉得比较困难或者不够流畅。在 2.6.25 内核中 inotify 还添加了 Signal-drive I.O 通知功能。
用于 inotify 的 API
Inotify 提供一个简单的 API,使用最小的文件描述符,并且允许细粒度监控。与 inotify 的通信是通过系统调用实现。可用的函数如下所示:
inotify_init
是用于创建一个 inotify 实例的系统调用,并返回一个指向该实例的文件描述符。
inotify_init1
与 inotify_init 相似,并带有附加标志。如果这些附加标志没有指定,将采用与 inotify_init 相同的值。
inotify_add_watch
增加对文件或者目录的监控,并指定需要监控哪些事件。标志用于控制是否将事件添加到已有的监控中,是否只有路径代表一个目录才进行监控,是否要追踪符号链接,是否进行一次性监控,当首次事件出现后就停止监控。
inotify_rm_watch
从监控列表中移出监控项目。
read
读取包含一个或者多个事件信息的缓存。
close
关闭文件描述符,并且移除所有在该描述符上的所有监控。当关于某实例的所有文件描述符都关闭时,资源和下层对象都将释放,以供内核再次使用。
因此,典型的监控程序需要进行如下操作:
使用 inotify_init 打开一个文件描述符
添加一个或者多个监控
等待事件
处理事件,然后返回并等待更多事件
当监控不再活动时,或者接到某个信号之后,关闭文件描述符,清空,然后退出。
在下一部分中,您将看到可以监控的事件,它们如何在简单的程序中运行。最后,您将看到事件监控如何进行。
通告
当您的应用程序读取到一个通告时,事件的顺序也被读取到您提供的缓存中。事件在一个变长结构中被返回,如清单 1 所示。如果数据占满了您的缓存,您可能需要对最后一个条目进行局部事件信息或者局部名处理。
清单 1. 用于 inotify 的事件结构体
1
2
3
4
5
6
7
8
9
struct inotify_event
{
int wd; /* Watch descriptor. */
uint32_t mask; /* Watch mask. */
uint32_t cookie; /* Cookie to synchronize two events. */
uint32_t len; /* Length (including NULs) of name. */
char name __flexarr; /* Name. */
};
请注意,只有当监控对象是一个目录并且事件与目录内部相关项目有关,而与目录本身无关时,才提供 name 字段。如果 IN_MOVED_FROM 事件与相应的 IN_MOVED_TO 事件都与被监控的项目有关,cookie 就可用于将两者关联起来。事件类型在掩码字段中返回,并伴随着能够被内核设置的标志。例如,如果事件与目录有关,则标志 IN_ISDIR 将由内核设置。
可监控的事件
有几种事件能够被监控。一些事件,比如 IN_DELETE_SELF 只适用于正在被监控的项目,而另一些,比如 IN_ATTRIB 或者 IN_OPEN 则只适用于监控过的项目,或者如果该项目是目录,则可以应用到其所包含的目录或文件。
IN_ACCESS
被监控项目或者被监控目录中的条目被访问过。例如,一个打开的文件被读取。
IN_MODIFY
被监控项目或者被监控目录中的条目被修改过。例如,一个打开的文件被修改。
IN_ATTRIB
被监控项目或者被监控目录中条目的元数据被修改过。例如,时间戳或者许可被修改。
IN_CLOSE_WRITE
一个打开的,等待写入的文件或目录被关闭。
IN_CLOSE_NOWRITE
一个以只读方式打开的文件或目录被关闭。
IN_CLOSE
一个掩码,可以很便捷地对前面提到的两个关闭事件(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)进行逻辑操作。
IN_OPEN
文件或目录被打开。
IN_MOVED_FROM
被监控项目或者被监控目录中的条目被移出监控区域。该事件还包含一个 cookie 来实现 IN_MOVED_FROM 与 IN_MOVED_TO 的关联。
IN_MOVED_TO
文件或目录被移入监控区域。该事件包含一个针对 IN_MOVED_FROM 的 cookie。如果文件或目录只是被重命名,将能看到这两个事件,如果它只是被移入或移出非监控区域,将只能看到一个事件。如果移动或重命名一个被监控项目,监控将继续进行。参见下面的 IN_MOVE-SELF。
IN_MOVE
可以很便捷地对前面提到的两个移动事件(IN_MOVED_FROM | IN_MOVED_TO)进行逻辑操作的掩码。
IN_CREATE
在被监控目录中创建了子目录或文件。
IN_DELETE
被监控目录中有子目录或文件被删除。
IN_DELETE_SELF
被监控项目本身被删除。监控终止,并且将收到一个 IN_IGNORED 事件。
IN_MOVE_SELF
监控项目本身被移动。
除了事件标志以外,还可以在 inotify 头文件(/usr/include/sys/inotify.h)中找到其他几个标志。例如,如果只想监控第一个事件,可以在增加监控时设置 IN_ONESHOT 标志。
一个简单 inotify 应用程序
这里的简单应用程序(参见 下载 部分)遵循以上的通用逻辑。我们使用一个信号处理程序来监控 ctrl-c(SIGINT)并且重置一个标志(keep_running)使应用程序了解终止操作。真实的 inotify 调用在 utility 例程当中完成。注意,我们还创建了一个队列,这样能够将事件从 inotify 底层对象中清除,留着稍后处理。在真实的应用程序中,您可能想用一个不同于您处理事件所用的线程(具有更高优先级)来完成这一操作。对于该应用程序,只是为了对一般原理进行举例说明。我们采用了一个简单的事件链表,在其中我们队列中的每个条目都包含原始事件以及指向队列中下一事件指针的空间。
主程序
清单 2 中展示了信号处理例程和主例程。在这个简单示例中,在命令行对每个传递进来的文件会目录建立监控,并利用事件掩码 IN_ALL_EVENTS 来监控每个对象的所有事件。在真实的应用程序中,您可能只希望跟踪文件与目录的创建或删除事件,因此您可以屏蔽打开、关闭以及属性改变事件。如果您对文件或目录的重命名和移动不感兴趣,您也可以屏蔽各种移动事件。关于更多细节,参见 inotify 帮助信息。
清单 2. inotify-test.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
67
68
69
70
71
72
73
/* Signal handler that simply resets a flag to cause termination */
void signal_handler (int signum)
{
keep_running = 0;
}
int main (int argc, char **argv)
{
/* This is the file descriptor for the inotify watch */
int inotify_fd;
keep_running = 1;
/* Set a ctrl-c signal handler */
if (signal (SIGINT, signal_handler) == SIG_IGN)
{
/* Reset to SIG_IGN (ignore) if that was the prior state */
signal (SIGINT, SIG_IGN);
}
/* First we open the inotify dev entry */
inotify_fd = open_inotify_fd ();
if (inotify_fd > 0)
{
/* We will need a place to enqueue inotify events,
this is needed because if you do not read events
fast enough, you will miss them. This queue is
probably too small if you are monitoring something
like a directory with a lot of files and the directory
is deleted.
*/
queue_t q;
q = queue_create (128);
/* This is the watch descriptor returned for each item we are
watching. A real application might keep these for some use
in the application. This sample only makes sure that none of
the watch descriptors is less than 0.
*/
int wd;
/* Watch all events (IN_ALL_EVENTS) for the directories and
files passed in as arguments.
Read the article for why you might want to alter this for
more efficient inotify use in your app.
*/
int index;
wd = 0;
printf("\n");
for (index = 1; (index < argc) && (wd >= 0); index++)
{
wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS);
}
if (wd > 0)
{
/* Wait for events and process them until a
termination condition is detected
*/
process_inotify_events (q, inotify_fd);
}
printf ("\nTerminating\n");
/* Finish up by closing the fd, destroying the queue,
and returning a proper code
*/
close_inotify_fd (inotify_fd);
queue_destroy (q);
}
return 0;
}
使用 inotify_init 打开文件描述符
清单 3 展示了用于创建 inotify 实例以及获取其文件描述符的简单实用工具函数。文件描述符返回给了调用者。如果出现错误,返回值将为负。
清单 3. 使用 inotify_init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Create an inotify instance and open a file descriptor
to access it */
int open_inotify_fd ()
{
int fd;
watched_items = 0;
fd = inotify_init ();
if (fd < 0)
{
perror ("inotify_init () = ");
}
return fd;
}
使用 inotify_add_watch 添加一个监控
一旦我们有了用于 inotify 实例的文件描述符之后,就需要增加一个或多个监控。您可以使用掩码来设置想要监控的事件。在本例中,采用掩码 IN_ALL_EVENTS,来监控所有可用事件。
清单 4. 使用 inotify_add_watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int watch_dir (int fd, const char *dirname, unsigned long mask)
{
int wd;
wd = inotify_add_watch (fd, dirname, mask);
if (wd < 0)
{
printf ("Cannot add watch for \"%s\" with event mask %lX", dirname,
mask);
fflush (stdout);
perror (" ");
}
else
{
watched_items++;
printf ("Watching %s WD=%d\n", dirname, wd);
printf ("Watching = %d items\n", watched_items);
}
return wd;
}
事件处理循环
现在我们已经设置了一些监控,接下来就要等待事件。如果还存在监控,并且 keep_running 标志没有被信号处理程序重置,则循环会一直进行。循环进程等待事件的发生,对有效事件进行排队,并在返回等待状态之前处理队列。在真实应用程序当中,您可能会将事件放入线程队列中,而在另一个线程中处理它们,清单 5 展示了该循环。
清单 5. 事件处理循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int process_inotify_events (queue_t q, int fd)
{
while (keep_running && (watched_items > 0))
{
if (event_check (fd) > 0)
{
int r;
r = read_events (q, fd);
if (r < 0)
{
break;
}
else
{
handle_events (q);
}
}
}
return 0;
}
等待事件
在我们的示样例应用程序中,循环会不停地进行下去,直至监控事件出现或者收到了中断信号。清单 6 展示了相关代码。
清单 6. 等待事件或者中断
1
2
3
4
5
6
7
8
9
10
int event_check (int fd)
{
fd_set rfds;
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
/* Wait until an event happens or we get interrupted
by a signal that we catch */
return select (FD_SETSIZE, &rfds, NULL, NULL, NULL);
}
读取事件
当事件发生时,程序会依照缓存区的大小来读取尽量多的事件,然后把这些事件放入队列等待事件处理程序来处理。样例代码不能处理这种情况 — 可用事件超出 16.384 byte 缓存中可存储的事件。要处理这类情况,需要在缓存末端处理部分事件。目前,对名字长度进行限制不成问题,但是优秀的防御式编程会检查名字,来确保不会溢出缓存。
清单 7. 读取事件并排队
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
int read_events (queue_t q, int fd)
{
char buffer[16384];
size_t buffer_i;
struct inotify_event *pevent;
queue_entry_t event;
ssize_t r;
size_t event_size, q_event_size;
int count = 0;
r = read (fd, buffer, 16384);
if (r <= 0)
return r;
buffer_i = 0;
while (buffer_i < r)
{
/* Parse events and queue them. */
pevent = (struct inotify_event *) &buffer[buffer_i];
event_size = offsetof (struct inotify_event, name) + pevent->len;
q_event_size = offsetof (struct queue_entry, inot_ev.name) +
pevent->len;
event = malloc (q_event_size);
memmove (&(event->inot_ev), pevent, event_size);
queue_enqueue (event, q);
buffer_i += event_size;
count++;
}
printf ("\n%d events queued\n", count);
return count;
}
处理事件
最后,我们需要对事件做处理了。对于该应用程序,我们只简单地报告所发生的事件。如果一个名字出现在事件结构中,我们就报告它是一个文件或目录。发生移动时,还会报告与移动或重命名事件相关的 cookie 信息。清单 8 展示了部分代码,包括对一些事件的处理。参见 下载 部分可获取完整代码。
清单 8. 处理事件
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
void handle_event (queue_entry_t event)
{
/* If the event was associated with a filename, we will store it here */
char *cur_event_filename = NULL;
char *cur_event_file_or_dir = NULL;
/* This is the watch descriptor the event occurred on */
int cur_event_wd = event->inot_ev.wd;
int cur_event_cookie = event->inot_ev.cookie;
unsigned long flags;
if (event->inot_ev.len)
{
cur_event_filename = event->inot_ev.name;
}
if ( event->inot_ev.mask & IN_ISDIR )
{
cur_event_file_or_dir = "Dir";
}
else
{
cur_event_file_or_dir = "File";
}
flags = event->inot_ev.mask &
~(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED );
/* Perform event dependent handler routines */
/* The mask is the magic that tells us what file operation occurred */
switch (event->inot_ev.mask &
(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED))
{
/* File was accessed */
case IN_ACCESS:
printf ("ACCESS: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File was modified */
case IN_MODIFY:
printf ("MODIFY: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File changed attributes */
case IN_ATTRIB:
printf ("ATTRIB: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File open for writing was closed */
case IN_CLOSE_WRITE:
printf ("CLOSE_WRITE: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File open read-only was closed */
case IN_CLOSE_NOWRITE:
printf ("CLOSE_NOWRITE: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File was opened */
case IN_OPEN:
printf ("OPEN: %s \"%s\" on WD #%i\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd);
break;
/* File was moved from X */
case IN_MOVED_FROM:
printf ("MOVED_FROM: %s \"%s\" on WD #%i. Cookie=%d\n",
cur_event_file_or_dir, cur_event_filename, cur_event_wd,
cur_event_cookie);
break;
.
. (other cases)
.
/* Watch was removed explicitly by inotify_rm_watch or automatically
because file was deleted, or file system was unmounted. */
case IN_IGNORED:
watched_items--;
printf ("IGNORED: WD #%d\n", cur_event_wd);
printf("Watching = %d items\n",watched_items);
break;
/* Some unknown message received */
default:
printf ("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n",
event->inot_ev.mask, cur_event_filename, cur_event_wd);
break;
}
/* If any flags were set other than IN_ISDIR, report the flags */
if (flags & (~IN_ISDIR))
{
flags = event->inot_ev.mask;
printf ("Flags=%lX\n", flags);
}
}
这个简单示例用于说明 inotify 如何工作,以及您可以监控什么事件。您的实际需求将决定对哪些事件进行监控以及如何处理这些事件。
用法举例
在本部分中,我们创建一个带有文件的简单双级目录结构,然后运行简单程序来举例说明 inotify 所能监控的一些事件。我们将在终端会话中启动 inotify 示例程序,但是在后台运行(使用 &)因此程序的输出与我们输入的命令会交替出现。您可以在一个终端窗口运行该程序,而在其他一个或多个窗口运行指令。清单 9 展示了简单文件结构和空文件的创建,以及最初启动该示例程序时的输出。
清单 9. 创建一个样例环境
ian@attic4:~/inotify-sample$ mkdir -p dir1/dir2
ian@attic4:~/inotify-sample$ touch dir1/dir2/file1
ian@attic4:~/inotify-sample$ ./inotify_test dir1/ dir1/dir2/ dir1/dir2/file1&
[2] 8733
ian@attic4:~/inotify-sample$
Watching dir1/ WD=1
Watching = 1 items
Watching dir1/dir2/ WD=2
Watching = 2 items
Watching dir1/dir2/file1 WD=3
Watching = 3 items
ian@attic4:~/inotify-sample$
在清单 10 中,展示了来自 dir2 内容清单的输出。第一个事件报告是关于 dir1 的,展示了一些内容,即 dir2 ,在被监控描述符 1 监控的目录当中被打开了。第二个条目是关于监控描述符 2 的,显示出被监控项目(在本例中为 dir2 )被打开了。如果正在监控目录树中的多个项目,可能会经常遇到这种双重输出。
清单 10. 列举 dir2 的内容
ian@attic4:~/inotify-sample$ ls dir1/dir2
file1
4 events queued
OPEN: Dir “dir2” on WD #1
OPEN: Dir “(null)” on WD #2
CLOSE_NOWRITE: Dir “dir2” on WD #1
CLOSE_NOWRITE: Dir “(null)” on WD #2
在清单 11 中,我们在 file1 添加了一些文本。请再次注意对文件以及该文件所在目录的双重打开、关闭和修改事件。还请注意所有的事件并非立刻读取。排队例程被调用了3次,每次有两个事件。如果再次运行该程序,并且每次操作相同,您未必会再次遇到这一特别情况。
清单 11. 在 file1 中添加文本
ian@attic4:~/inotify-sample$ echo “Some text” » dir1/dir2/file1
2 events queued
OPEN: File “file1” on WD #2
OPEN: File “(null)” on WD #3
2 events queued
MODIFY: File “file1” on WD #2
MODIFY: File “(null)” on WD #3
2 events queued
CLOSE_WRITE: File “file1” on WD #2
CLOSE_WRITE: File “(null)” on WD #3
在清单 12 中,我们改变了 file1 的属性。我们再次得到有关被监控项目以及其所在目录的双重输出。
清单 12. 改变文件属性
ian@attic4:~/inotify-sample$ chmod a+w dir1/dir2/file1
2 events queued
ATTRIB: File “file1” on WD #2
ATTRIB: File “(null)” on WD #3
现在将文件 file1 移动到上一级目录 dir1 当中。在清单 13 中显示了输出结果。这次没有双重条目。我们实际上得到了 3 个条目,每个目录一个,文件本身一个。请注意 cookie (569) 允许将 MOVED-FROM 事件与 MOVED_TO 事件关联起来。
清单 13. 将 file1 移动到 dir1
ian@attic4:~/inotify-sample$ mv dir1/dir2/file1 dir1
3 events queued
MOVED_FROM: File “file1” on WD #2. Cookie=569
MOVED_TO: File “file1” on WD #1. Cookie=569
MOVE_SELF: File “(null)” on WD #3
现在创建一个 file1 到 file2 的硬链接。当到 inode 的链接数量该变时,我们会有一个关于 file1 的 ATTRIB 事件,还会有一个关于 file2 的 CREATE 事件。
清单 14. 创建硬链接
ian@attic4:~/inotify-sample$ ln dir1/file1 dir1/file2
2 events queued
ATTRIB: File “(null)” on WD #3
CREATE: File “file2” on WD #1
现在将文件 file1 移入当前目录,将其重命名为 file3 。当前目录没有被监控,因此不存在与 MOVED_FROM 事件相关联的 MOVED_TO 事件。
清单 15. 将 file1 移入不受监控的目录当中
ian@attic4:~/inotify-sample$ mv dir1/file1 ./file3
2 events queued
MOVED_FROM: File “file1” on WD #1. Cookie=572
MOVE_SELF: File “(null)” on WD #3
此时,dir2 是空的,因此可以移动它。注意我们得到一个关于监控描述符 2 的 IGNORED 事件,可见现在我们只监控两个项目。
清单 16. 移除 dir2
ian@attic4:~/inotify-sample$ rmdir dir1/dir2
3 events queued
DELETE: Dir “dir2” on WD #1
DELETE_SELF: File “(null)” on WD #2
IGNORED: WD #2
Watching = 2 items
移除文件 file3。注意这次我们没有得到 IGNORED 事件。为什么呢?为什么得到了关于 file 3 的 ATTRIB 事件(就是原来的 dir1/dir2/file1)?
清单 16. 删除 file3
ian@attic4:~/inotify-sample$ rm file3
1 events queued
ATTRIB: File “(null)” on WD #3
记住我们创建了 file1 到 file2 的硬链接。清单 17 显示我们还在通过监控描述符 3 来监控 file2,尽管最开始不存在文件 2!
清单 17. 我们仍在监视 file2!
ian@attic4:~/inotify-sample$ touch dir1/file2
6 events queued
OPEN: File “file2” on WD #1
OPEN: File “(null)” on WD #3
ATTRIB: File “file2” on WD #1
ATTRIB: File “(null)” on WD #3
CLOSE_WRITE: File “file2” on WD #1
CLOSE_WRITE: File “(null)” on WD #3
因此,现在让我们来删除 dir1,并监控事件级联,因为它不再监控任何事情,不得不结束了自己。
清单 18. 删除 dir1
ian@attic4:~/inotify-sample$ rm -rf dir1
8 events queued
OPEN: Dir “(null)” on WD #1
ATTRIB: File “(null)” on WD #3
DELETE_SELF: File “(null)” on WD #3
IGNORED: WD #3
Watching = 1 items
DELETE: File “file2” on WD #1
CLOSE_NOWRITE: Dir “(null)” on WD #1
DELETE_SELF: File “(null)” on WD #1
IGNORED: WD #1
Watching = 0 items
Terminating
inotify 的可能使用
您可以将 inotify 用于多种目标。下面列举一些可能的情况:
性能监控
您可能想确定应用程序打开最频繁的文件是哪个。如果发现一个小文件被频繁打开与关闭,您可能会考虑采用内存版,或者改变应用程序来采取其他方式共享该数据。
元信息
您可能想记录文件的附加信息,例如起始创建时间或者最后改变该文件的用户 id。
安全
您可能会因为安全原因,需要对特定文件或目录的所有访问进行监控。
我们的示例代码监控所有事件并进行报告。实际上,您可能想依据您的需要,来查看这些事件的特定子集。您可能想监控不同被监控项目的不同事件。例如,您可能想监控文件的打开与关闭事件,但对于目录只想监控创建与删除事件。在任何可能的时候,您可以监控您所感兴趣的最小事件集。
何时需要自己调用inotify_rm_watch
合适需要自己调用inotify_add_watch
read调用注意事项
对于1,出现moved_from而没有出现moved_to时,这就需要自 己调用inotify_rm_watch把移走的文件目录(移到非监控目录下)监控删除,这个目录下面的子目录会自动删除的 。像其它操作:删除监控目录,监控目录在大的监控目录下移进移出是不需要自己调用inotify_rm_watch的,因为系统自动处理,产生的事件分别 为delete_self和move_self。
对于2, 这要看你是否需要递归监控每个目录,如果是,那么当你在监控目录下建立一个新目录时,就需要调用inotify_add_watch;放心系统不会出现单 独的moved_to, 如果你从非监控目录下copy一个目录到监控目录下,那么inotify产生的事件是create (目录或文件),而不会是moved_to的,但可以单独产生moved_from事件,如情况1所说。
对与3,要知道下面红色的限制,
/proc interfaces
The following interfaces can be used to limit the amount of kernel memory consumed by inotify:
/proc/sys/fs/inotify/max_queued_events
The value in this file is used when an application calls inotify_init(2) to set an upper limit on the number of events that can be queued to the corresponding inotify instance. Events in excess of this limit are dropped, but an N_Q_OVERFLOW event is always generated.
/proc/sys/fs/inotify/max_user_instances
This specifies an upper limit on the number of inotify instances that can be created per real user ID.
/proc/sys/fs/inotify/max_user_watches
This specifies an upper limit on the number of watches that can be created per real user ID.
如果你监控的目录很大,那么其它限制你也必须考虑,调用read时,要注意返回的是一个完整的
struct inotify_event {
int wd; /* Watch descriptor /
uint32_t mask; / Mask of events /
uint32_t cookie; / Unique cookie associating related
events (for rename(2)) /
uint32_t len; / Size of name field /
char name[]; / Optional null-terminated name */
};
结构。
众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能 够更好地管理设备,给用户提供更好的服务,如 hotplug、udev 和 inotify 就是这种需求催生的。Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理,udev 动态地维护 /dev 下的设备文件,inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知,该机制是著名的桌面搜索引擎项目 beagle 引入的,并在 Gamin 等项目中被应用。
用户接口
在用户态,inotify 通过三个系统调用和在返回的文件描述符上的文件 I/ 操作来使用,使用 inotify 的第一步是创建 inotify 实例:
int fd = inotify_init ();
每一个 inotify 实例对应一个独立的排序的队列。
文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组(目标,事件掩码),目标可以是文件或目录,事件掩码表示应用希望关注的 inotify 事件,每一个位对应一个 inotify 事件。Watch 对象通过 watch描述符引用,watches 通过文件或目录的路径名来添加。目录 watches 将返回在该目录下的所有文件上面发生的事件。
下面函数用于添加一个 watch:
int wd = inotify_add_watch (fd, path, mask);
fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),mask 是事件掩码, 在头文件 linux/inotify.h 中定义了每一位代表的事件。可以使用同样的方式来修改事件掩码,即改变希望被通知的inotify 事件。Wd 是 watch 描述符。
下面的函数用于删除一个 watch:
int ret = inotify_rm_watch (fd, wd);
fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函数的返回值。
文件事件用一个 inotify_event 结构表示,它通过由 inotify_init() 返回的文件描述符使用通常文件读取函数 read 来获得
:
struct inotify_event {
__s32 wd; /* watch descriptor /
__u32 mask; / watch mask /
__u32 cookie; / cookie to synchronize two events /
__u32 len; / length (including nulls) of name /
char name[0]; / stub for possible name */
};
结构中的 wd 为被监视目标的 watch 描述符,mask 为事件掩码,len 为 name字符串的长度,name 为被监视目标的路径名,该结构的 name 字段为一个桩,它只是为了用户方面引用文件名,文件名是变长的,它实际紧跟在该结构的后面,文件名将被 0 填充以使下一个事件结构能够 4 字节对齐。注意,len 也把填充字节数统计在内。
通过 read 调用可以一次获得多个事件,只要提供的 buf 足够大。
size_t len = read (fd, buf, BUF_LEN);
buf 是一个 inotify_event 结构的数组指针,BUF_LEN 指定要读取的总长度,buf 大小至少要不小于 BUF_LEN,该调用返回的事件数取决于 BUF_LEN 以及事件中文件名的长度。Len 为实际读去的字节数,即获得的事件的总长度。
可以在函数 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 来得到当前队列的长度。close(fd)将删除所有添加到 fd 中的 watch 并做必要的清理。
int inotify_init (void);
int inotify_add_watch (int fd, const char *path, __u32 mask);
int inotify_rm_watch (int fd, __u32 mask);
struct inotify_device {
wait_queue_head_t wq; /* wait queue for i/o /
struct idr idr; / idr mapping wd -> watch /
struct semaphore sem; / protects this bad boy /
struct list_head events; / list of queued events /
struct list_head watches; / list of watches /
atomic_t count; / reference count /
struct user_struct *user; / user who opened this dev /
unsigned int queue_size; / size of the queue (bytes) /
unsigned int event_count; / number of pending events /
unsigned int max_events; / maximum number of events /
u32 last_wd; / the last wd allocated */
};
d_list 指向所有 inotify_device 组成的列表的,i_list 指向所有被监视 inode 组成的列表,count 是引用计数,dev 指向该 watch 所在的 inotify 实例对应的 inotify_device 结构,inode 指向该 watch 要监视的 inode,wd 是分配给该 watch 的描述符,mask 是该 watch 的事件掩码,表示它对哪些文件系统事件感兴趣。
结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭 inotify_init()返回的文件描述符时将被释放。结构 inotify_watch 在用户态调用 inotify_add_watch()时创建,在用户态调用 inotify_rm_watch() 或 close(fd) 时被释放。
无论是目录还是文件,在内核中都对应一个 inode 结构,inotify 系统在 inode 结构中增加了两个字段:
struct inotify_watch {
struct list_head d_list; /* entry in inotify_device’s list /
struct list_head i_list; / entry in inode’s list /
atomic_t count; / reference count /
struct inotify_device *dev; / associated device /
struct inode *inode; / associated inode /
s32 wd; / watch descriptor /
u32 mask; / event mask for this watch */
};
d_list 指向所有 inotify_device 组成的列表的,i_list 指向所有被监视 inode 组成的列表,count 是引用计数,dev 指向该 watch 所在的 inotify 实例对应的 inotify_device 结构,inode 指向该 watch 要监视的 inode,wd 是分配给该 watch 的描述符,mask 是该 watch 的事件掩码,表示它对哪些文件系统事件感兴趣。
结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭 inotify_init()返回的文件描述符时将被释放。结构 inotify_watch 在用户态调用 inotify_add_watch()时创建,在用户态调用 inotify_rm_watch() 或 close(fd) 时被释放。
无论是目录还是文件,在内核中都对应一个 inode 结构,inotify 系统在 inode 结构中增加了两个字段:
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode /
struct semaphore inotify_sem; / protects the watches list */
#endif
inotify_watches 是在被监视目标上的 watch 列表,每当用户调用 inotify_add_watch()时,内核就为添加的 watch 创建一个 inotify_watch 结构,并把它插入到被监视目标对应的 inode 的 inotify_watches 列表。inotify_sem 用于同步对 inotify_watches 列表的访问。当文件系统发生第一部分提到的事件之一时,相应的文件系统代码将显示调用fsnotify_* 来把相应的事件报告给 inotify 系统,其中*号就是相应的事件名,目前实现包括:
fsnotify_move,文件从一个目录移动到另一个目录fsnotify_nameremove,文件从目录中删除 fsnotify_inoderemove,自删除fsnotify_create,创建新文件fsnotify_mkdir,创建新目录 fsnotify_access,文件被读fsnotify_modify,文件被写fsnotify_open,文件被打开 fsnotify_close,文件被关闭fsnotify_xattr,文件的扩展属性被修改fsnotify_change,文件被修改或原数据被修 改有一个例外情况,就是 inotify_unmount_inodes,它会在文件系统被 umount 时调用来通知 umount 事件给 inotify 系统。
以上提到的通知函数最后都调用 inotify_inode_queue_event(inotify_unmount_inodes直接调用 inotify_dev_queue_event ),该函数首先判断对应的inode是否被监视,这通过查看 inotify_watches 列表是否为空来实现,如果发现 inode 没有被监视,什么也不做,立刻返回,反之,遍历 inotify_watches 列表,看是否当前的文件操作事件被某个 watch 监视,如果是,调用 inotify_dev_queue_event,否则,返回。函数inotify_dev_queue_event 首先判断该事件是否是上一个事件的重复,如果是就丢弃该事件并返回,否则,它判断是否 inotify 实例即 inotify_device 的事件队列是否溢出,如果溢出,产生一个溢出事件,否则产生一个当前的文件操作事件,这些事件通过kernel_event 构建,kernel_event 将创建一个 inotify_kernel_event 结构,然后把该结构插入到对应的 inotify_device 的 events 事件列表,然后唤醒等待在inotify_device 结构中的 wq 指向的等待队列。想监视文件系统事件的用户态进程在inotify 实例(即 inotify_init() 返回的文件描述符)上调用 read 时但没有事件时就挂在等待队列 wq 上。