inotify机制监控文件系统事件原理及使用

Posted samdyhc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了inotify机制监控文件系统事件原理及使用相关的知识,希望对你有一定的参考价值。

1.基本描述

inotify提供了一种监控文件系统事件的机制,可以用来监控单个的文件以及目录。当一个目录被监控,inotify会返回该目录以及该目录下面文件的事件。

2.原理以及使用

2.1内核原理

inotify机制借用了内核里面的notify通知链技术,针对文件系统里面的使用主要是在inode结构体里面加入了相关的字段(内核版本3.10.0-327):

struct inode {
       。。。
#ifdef CONFIG_FSNOTIFY
      __u32 i_fsnotify_mask; /* all events this inode cares about */  具体可能监控的事件,事件基本上是一些位段
      struct hlist_head i_fsnotify_marks;  /* 具体的链表,链表上可以挂入多个mask结构(事件) */
#endif
??。。。
}

struct fsnotify_mark {
     __u32 mask; /* mask this mark is for */
     atomic_t refcnt; /* active things looking at this mark */
     struct fsnotify_group *group; /* group this mark is for */
     struct list_head g_list; /* list of marks by group->i_fsnotify_marks */
     spinlock_t lock; /* protect group and inode */
     union {
          struct fsnotify_inode_mark i;
          struct fsnotify_vfsmount_mark m;
     };
     __u32 ignored_mask; /* events types to ignore */
     unsigned int flags; /* vfsmount or inode mark? */
     struct list_head destroy_list;
     void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */
};

具体的关系结构图如图所示:

技术分享图片

                                                                 图1 inotify机制内核下面各结构体关系图

2.2用户态接口原理

用户态接口的基本思路是初始化一个具体的inotify实例,并设置实例以及具体监控哪些事件。当有具体的事件以后可以读取对应的

结构体来解析。事件结构体为:

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 */  具体的文件名
};

具体可以监控的事件主要有:(注释比较清晰了)

IN_ACCESS File was accessed (read) (*).
IN_ATTRIB Metadata changed, e.g., permissions, timestamps, extended attributes, link count (since Linux 2.6.25), UID, GID, etc. (*).
IN_CLOSE_WRITE File opened for writing was closed (*).
IN_CLOSE_NOWRITE File not opened for writing was closed (*).
IN_CREATE File/directory created in watched directory (*).
IN_DELETE File/directory deleted from watched directory (*).
IN_DELETE_SELF Watched file/directory was itself deleted.
IN_MODIFY File was modified (*).
IN_MOVE_SELF Watched file/directory was itself moved.
IN_MOVED_FROM Generated for the directory containing the old filename when a file is renamed (*).
IN_MOVED_TO Generated for the directory containing the new filename when a file is renamed (*).
IN_OPEN File was opened (*).

主要的用户态接口函数:

int inotify_init(void);
int inotify_init1(int flags);

初始化一个inotify实例并且返回一个文件描述符,inotify实例和一个evnet队列挂钩,失败返回-1。

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

添加一个watch对象,fd为具体的inotify实例描述符,pathname为监控的目录或者文件,mask为具体的事件,成功返回非负整数,失败返

回-1.

int inotify_rm_watch(int fd, int wd);

删除一个watch,fd为inotify实例描述符,wd为watch描述符。成功返回0,失败返回-1.

3.测试程序

#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/select.h>

#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

/*
 * 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
 * };
 *
 **/

int giNotifyFd;
int giaWds[20];
int giCount;

int watch_inotify_events(int fd)
{
     char event_buf[512];
     int ret;
     int event_pos = 0;
     int event_size = 0;
     struct inotify_event *event;
     time_t tNow;
     struct tm *pTimeNow;

     /* 读事件是否发生,没有发生就会阻塞 */
     ret = read(fd, event_buf, sizeof(event_buf));

     /* 如果read的返回值,小于inotify_event大小出现错误 */
     if (ret < (int)sizeof(struct inotify_event)){
          printf("counld not get event! ");
          return -1;
     }
     /* 因为read的返回值存在一个或者多个inotify_event对象,需要一个一个取出来处理 */
     while (ret >= (int)sizeof(struct inotify_event)) {
          event = (struct inotify_event*)(event_buf + event_pos);
          if (event->len) {
               // time(&tNow);    /* 这三行可以注释掉,之前出现过加了这三行执行出现core dump的问题。 */
               // pTimeNow = localtime(&tNow);
               // printf("Local time is:%s", asctime(pTimeNow));

               if(event->mask & IN_CREATE){
                    printf("watch is %d, create file: %s ", event->wd, event->name);
               } else {
                    printf("watch is %d, delete file: %s ", event->wd, event->name);
               }
               if (event->mask & IN_ATTRIB) {
                    printf("watch is %d, modify file attribute: %s ", event->wd, event->name);
               }
          }
          /* event_size就是一个事件的真正大小 */
          event_size = sizeof(struct inotify_event) + event->len;
          ret -= event_size;
          event_pos += event_size;
     }
     return 0;
}

/* 递归处理目录 */

void init_all_iwds(char *pcName)
{
     int iWd;
     struct stat tStat;
     DIR *pDir;
     struct dirent *ptDirent;
     char caNametmp[100]; // 存储目录名字

     iWd = inotify_add_watch(giNotifyFd, pcName, IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MODIFY);
     giaWds[giCount] = iWd;
     giCount++;

     if (-1 == stat(pcName, &tStat)) {
          printf("stat %s error ", pcName);
          return;
     }
     if (!S_ISDIR(tStat.st_mode))
          return;
     /* now the child dir */
     pDir = opendir(pcName);
     if (NULL == pDir) {
          printf("opendir %s error ", pcName);
          return;
     }

     // 循环读目录下面的子项
     while (NULL != (ptDirent = readdir(pDir))) {
          if ((0 == strcmp(ptDirent->d_name, ".")) || (0 == strcmp(ptDirent->d_name, "..")))
               continue; // 跳过当前目录和上一级父目录
          // printf("sub name is %s, d_type is 0x%x ", ptDirent->d_name, ptDirent->d_type);
          sprintf(caNametmp, "%s/%s", pcName, ptDirent->d_name); //获取子目录或文件名字
          if (-1 == stat(caNametmp, &tStat)) {
               printf("stat error:%s ", caNametmp); // 获取统计数据
               return;
          }
          if (!S_ISDIR(tStat.st_mode)) //看是否是子目录,原则只处理目录
               continue;

          printf("sub absoulte dir name is %s ", caNametmp);
          // iWd = inotify_add_watch(giNotifyFd, caNametmp, IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MODIFY);
          init_all_iwds(caNametmp); //处理子目录
     }

     // 关闭
     closedir(pDir);
}

int main(int argc, char** argv)
{
     int iNotifyRet;
     fd_set fds;

     int iaWd[10];

     int icount = 0;

     if (argc != 2) {
          printf("Usage: %s <dir> ", argv[0]);
          return -1;
     }

     /* inotify初始化 */
     iNotifyFd = inotify_init();
     if (iNotifyFd == -1) {
          printf("inotify_init error! ");
          return -1;
     }

     /* 递归处理具体的目录,添加watch对象 */
     init_all_iwds(argv[1]);

     /* 处理事件 */
     while (1) {
          FD_ZERO(&fds);
          FD_SET(iNotifyFd, &fds);

          if (select(iNotifyFd+1, &fds, NULL, NULL, NULL) > 0) {
               iNotifyRet = watch_inotify_events(iNotifyFd);
               if (-1 == iNotifyRet)
               break;
           }
     }

     /* 删除inotify的watch对象 */

     for (icount = 0; icount < giCount; icount++) {

          if (inotify_rm_watch(iNotifyFd, giaWds[icount ]) == -1) {
               printf("notify_rm_watch %d error! ", giaWds[icount]);
               return -1;
          }

     }

     /* 关闭inotify描述符 */
     close(iNotifyFd);

     return 0;
}

文件命名为:inotify.c

编译: gcc -o inotify inotify.c    生成可执行文件

执行: ./inotify输出  Usage: ./inotify <dir>  提示需要输入具体监控的目录或者文件。

执行: ./inotify /home/work/0604_inotify/ &

        创建 aaa 文件,打印出  watch is 1, create file: aaa

        修改aaa文件属性,打印出  watch is 1, modify file attribute: aaa



























































































































































以上是关于inotify机制监控文件系统事件原理及使用的主要内容,如果未能解决你的问题,请参考以下文章

Linux 文件操作监控inotify功能及实现原理

Linux下inotify的基本使用及注意事项

Inotify与Android文件监控FileObserver原理

inotify工具介绍及实时复制实践

inotify用法简介及结合rsync实现主机间的文件实时同步

Inotify: 高效实时的Linux文件系统事件监控框架