2010年5月18日星期二

使用inotify监视文件系统(转)

转:http://hi.baidu.com/hq1305018/blog/item/55c7bbde8c78d9abcc11661b.html
使用inotify监视文件系统
2009-10-19 12:07
使用inotify监视文件系统

inotify是Linux的功能,监视文件系统的读取,写入,文件建立等动作。inotify是事件驱动型的,而且使用方法又非常简单,比起用cron来频繁地查找文件系统的变化相比,非常有效。

关于inotify

inotify是Linux系统的核心功能,监视文件系统,当有事件(删除,读取,写入或者unmount等等)发生时,向指定的程序发出通知。另外,文件的移动等的操作发生时,移动文件和移动位置也可以通过通知来获取。

inotify的使用文件非常简单。首先生成一个文件标识,一个文件标识中记载了一个以上的监视对象(文件系统的路径和所要监视的事件类别)。然后,使用read()函数和读取该文件标识的事件通知。inotify并不会不断的去检索文件系统,如果一个事件没有发生,那么read()会一直等待。

另外因为inotify使用的是以前的文件标识方式,所以可以和select()等系统函数结合起来,对文件监视对象和多种入力源同时进行事件驱动型监视。事件驱动型的实行方式,避免了频繁地检索文件系统,大量地节省了系统资源。

接下来我会通过一些例子程序来详细说明inotify的使用方法。inotify需要Linux2.6.13以上,可以使用下面的命令来检查系统是否可以使用inotify。另外一种确认方法是,查找/usr/include/sys/inotify.h是否存在。

%uname -a

Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux
注意:FreeBSD,或者Mac OS X系统并不支持inotify,但是提供了和inotify相似的kqueue。关于kqueue可以使用man 2 kqueue来获取相关信息。

inotify的C语言API

inotify提供了3个系统调用函数,使用它们可以进行任意的文件系统监视。

● inotify_init() 用来生成inotify的实例,成功时返回一个文件标识,失败时返回-1。和其它的系统调用一样,失败的时候可以使用errno来进行分析。

● inotify_add_watch() 看函数名就知道了,这个是用来追加监视对象的系统调用。追加监视对象的时候需要监视文件路径,监视的事件列表(监视事件的指定要使用象IN_MODIFY等的常量)。多个监视对象之间是逻辑OR的关系(C语言中是(|))。inotify_add_watch()调用成功时,会返回一个监视标识,失败的时候会返回-1。当需要对监视对象进行变更或者删除的时候要使用监视标识。

● inotify_rm_watch() 是用来删除监视对象的系统调用函数。

除了上述的三个系统调用 之外,还要使用系统调用 函数read()和close()。read()是用来读取通知,如果通知没有发生那么read()不会返回。对文件标识执行了close()之后,和那个文件标识相关的内存资源都被删除和释放。

例子程序:事件监视

#include
#include
#include
#include
#include

#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )

int main( int argc, char **argv )
{
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];

fd = inotify_init();

if ( fd <>
perror( "inotify_init" );
}

wd = inotify_add_watch( fd, "/home/kankyou",
IN_MODIFY | IN_CREATE | IN_DELETE );
length = read( fd, buffer, BUF_LEN );

if ( length <>
perror( "read" );
}

while ( i <>
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->len ) {
if ( event->mask & IN_CREATE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was created.\n", event->name );
}
else {
printf( "The file %s was created.\n", event->name );
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was deleted.\n", event->name );
}
else {
printf( "The file %s was deleted.\n", event->name );
}
}
else if ( event->mask & IN_MODIFY ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was modified.\n", event->name );
}
else {
printf( "The file %s was modified.\n", event->name );
}
}
}
i += EVENT_SIZE + event->len;
}

( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );

exit( 0 );
}
这个程序中,用 fd = inotify_init();来生成了一个文件标识。
用wd = inotify_add_watch(...)追加了一个监视标识。对 /home/kankyou的文件系统变化来进行监视。
read()函数在一个通知到来之前会一直等待。通知的详细信息(文件和事件)是通知一个字节流来送信的。利用应用程序中的循环来获取字节流中的信息。
事件通知的构造体被定义在/usr/include/sys/inotify.h文件中。(参照下记)
struct inotify_event
{
int wd; /* The watch descriptor */
uint32_t mask; /* Watch mask */
uint32_t cookie; /* A cookie to tie two events together */
uint32_t len; /* The length of the filename found in the name field */
char name __flexarr; /* The name of the file, padding to the end with NULs */
}


wd是监视标识,表明一个监视对象。mask是事件通知的类别(文件删除,文件更新等等)。

通过使用cookie来关联两个监视事件对象,比如当文件从一个目录被移动到另一个目录的时候,发生的两个事件的cookie是相同的。对文件移动的监视要使用IN_MOVED_FROM和IN_MOVED_TO,或者使用IN_MOVED来对双方进行监视。

最后,name和len是发生事件的文件的文件名(不包括路径)和文件名的长度。


Build例子程序


调用C编译器(大部分的Linux系统是gcc)。之后是实行编译后的程序。

%cc -o watcher watcher.c
%./watcher
watcher实行中的状态下,在/home/kankyou目录中执行touch,cat,rm等命令,来进行实验。

%cd $HOME
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher &
%rm a b c
The file a was deleted.
The file b was deleted.
The file c was deleted.

%./watcher &
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher &
%cat /etc/passwd >> a
The file a was modified.

%./watcher &
%mkdir d
The directory d was created.


inotify的使用技巧

因为read()在通知到来之前一直是处于等待状态,所以对于象画面应该程序是不能使用的。这种场合可以结合select(),pselect(),poll(),epoll()来进行监视。



int return_value;
fd_set descriptors;
struct timeval time_to_wait;

FD_ZERO ( &descriptors );
FD_SET( ..., &descriptors );
FD_SET ( fd, &descriptors );

...

time_to_wait.tv_sec = 3;
time.to_waittv_usec = 0;

return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait);

if ( return_value <>
/* Error */
}

else if ( ! return_value ) {
/* Timeout */
}

else if ( FD_ISSET ( fd, &descriptors ) ) {
/* Process the inotify events */
...
}

else if ...


select()函数被调用之后,程序会等待time_to_wait秒,但是如果在这期间,fd中的任何文件发生变化,那么监视会立刻被再次实行。time_to_wait秒之后,应用程序可以进行其它的处理(象GUI事件处理等等)。


最后介绍几个inotify使用时的技巧。

● 监视对象的文件或者目录被删除之后,该当监视对象自动被删除。

● 如果正在监视的文件或者目录,发生了unmount,那么该当监视对象接收到unmount事件通知,之后受unmount影响的所有的监视对象都被删除。

● 监视事件类别中如果指定了IN_ONESHOT,那么事件通知只被送信一次。

● 事件通知的队列到达最大值时,会发生IN_OVERFLOW。

● close()函数执行之后,inotify的实例和相关的所有监视对象会被删除,队列中等待处理的事件会被删除。

没有评论: