inotify简介

inotify是一个内核用于通知用户空间程序文件系统变化的系统,使用简单但功能是十分强大的!
在 inotify 之前有 dnotify。不幸的是,dnotify 有局限性,用户需要更好的产品。和 dnotify 相比 inotify 的优势如下:

  1. notify 使用一个独立的文件描述符,而 dnotify 需要为每个受监控的目录打开一个文件描述符。当您同时监控多个目录时成本会非常高,而且还会遇到每进程文件描述符限制。
  2. Inotify 所使用的文件描述符可以通过系统调用获得,并且没有相关设备或者文件。而使用 dnotify,文件描述符就固定了目录,妨碍备用设备卸载,这是可移动媒体的一个典型问题。对于 inotify,卸载的文件系统上的监视文件或目录会产生一个事件,而且监视也会自动移除。
  3. Inotify 能够监视文件或者目录。Dnotify 则只监视目录,因此程序员还必须保持 stat 结构或者一个等效的数据结构,来反映该被监视目录中的文件,然后在一个事件发生时,将其与当前状态进行对比,以此了解当前目录中的条目发生了什么情况。
  4. inotify 使用文件描述符,允许程序员使用标准 select 或者 poll 函数来监视事件。这允许高效的多路复用 I/O 或者与 Glib 的 mainloop 的集成。相比之下,dnotify 使用信号,这使得程序员觉得比较困难或者不够流畅。在 2.6.25 内核中 inotify 还添加了 Signal-drive I.O 通知功能。

inotify的使用步骤:

inotify 通过三个系统调用和在返回的文件描述符上的文件 I/ 操作来使用。

第一步创建 inotify 实例:

int fd = inotify_init (void);

每一个 inotify 实例对应一个独立的排序的队列。
文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组(目标,事件掩码),目标可以是文件或目录,事件掩码表示应用希望关注的 inotify 事件,每一个位对应一个 inotify 事件。Watch 对象通过 watch描述符引用,watches 通过文件或目录的路径名来添加。目录 watches 将返回在该目录下的所有文件上面发生的事件。

第二步骤添加一个 watch:

int wd= inotify_add_watch (int fd, const char *path, __u32 mask);

fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),mask 是事件掩码, 在头文件 linux/inotify.h 中定义了每一位代表的事件。可以使用同样的方式来修改事件掩码,即改变希望被通知的inotify 事件。Wd 是 watch 描述符。

可以被监控的事件:
有几种事件能够被监控。一些事件,比如 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 标志。

与添加对应的是删除一个watch:

int ret = inotify_rm_watch (int fd, __u32 mask);

fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函数的返回值。

第三步等待事件:

使用标准 select 或者 poll 函数来监控inotify ,当被监视的目标有指定的事件发生时select 或者 poll 会返回。

第四步获取事件:

文件事件用一个 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 也把填充字节数统计在内。

size_t len = read (fd, buf, BUF_LEN);

通过 read 调用可以一次获得多个事件,只要提供的 buf 足够大。
buf 是一个 inotify_event 结构的数组指针,BUF_LEN 指定要读取的总长度,buf 大小至少要不小于 BUF_LEN,该调用返回的事件数取决于 BUF_LEN 以及事件中文件名的长度。Len 为实际读去的字节数,即获得的事件的总长度。

第五步处理事件:

根据获取的事件类型做相对应的处理。

第六步停止监控:

close(fd)将删除所有添加到 fd 中的 watch 并做必要的清理。

使用示例

event_queue.h 声明简单队列结构及其操作接口.

/*
*   file  event_queue.h
*/

#ifndef __EVENT_QUEUE_H
#define __EVENT_QUEUE_H

#include <stdint.h>
#include <sys/inotify.h>

struct queue_entry;

struct queue_entry
{
  struct queue_entry * next_ptr;   /* Pointer to next entry */
  struct inotify_event inot_ev;
};

typedef struct queue_entry * queue_entry_t;

/*struct queue_struct; */
struct queue_struct
{
  struct queue_entry * head;
  struct queue_entry * tail;
};
typedef struct queue_struct *queue_t;

int queue_empty (queue_t q);
queue_t queue_create ();
void queue_destroy (queue_t q);
void queue_enqueue (queue_entry_t d, queue_t q);
queue_entry_t queue_dequeue (queue_t q);

#endif

event_queue.c简单队列操作接口的实现。

/*
*   file event_queue.c
*/

#include "event_queue.h"
#include <stdlib.h>
#include <stdint.h>

/* Simple queue implemented as a singly linked list with head 
   and tail pointers maintained
*/
int queue_empty (queue_t q)
{
  return q->head == NULL;
}

queue_t queue_create ()
{
  queue_t q;
  q = malloc (sizeof (struct queue_struct));
  if (q == NULL)
    exit (-1);

  q->head = q->tail = NULL;
  return q;
}

void queue_destroy (queue_t q)
{
  if (q != NULL)
    {
      while (q->head != NULL)
    {
      queue_entry_t next = q->head;
      q->head = next->next_ptr;
      next->next_ptr = NULL;
      free (next);
    }
      q->head = q->tail = NULL;
      free (q);
    }
}

void queue_enqueue (queue_entry_t d, queue_t q)
{
  d->next_ptr = NULL;
  if (q->tail)
    {
       q->tail->next_ptr = d;
       q->tail = d;
    }
  else
    {
      q->head = q->tail = d;
    }
}

queue_entry_t  queue_dequeue (queue_t q)
{
  queue_entry_t first = q->head;
  if (first)
    {
      q->head = first->next_ptr;
      if (q->head == NULL) 
    {
      q->tail = NULL;
    }
      first->next_ptr = NULL;
    }
  return first;
}

inotify_utils.h inotify工具方法的申明。

/*
*   file inotify_utils.h 
*/

#ifndef __INOTIFY_UTILS_H
#define __INOTIFY_UTILS_H

#include "event_queue.h"

void handle_event (queue_entry_t event);
int read_event (int fd, struct inotify_event *event);
int event_check (int fd);
int process_inotify_events (queue_t q, int fd);
int watch_dir (int fd, const char *dirname, unsigned long mask);
int ignore_wd (int fd, int wd);
int close_inotify_fd (int fd);
int open_inotify_fd ();

#endif

inotify_utils.c inotify工具的实现:

/*
*   file inotify_utils.c 
*/

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>

#include <sys/inotify.h>

#include "event_queue.h"
#include "inotify_utils.h"

extern int keep_running;
static int watched_items;

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


/* Close the open file descriptor that was opened with inotify_init() */
int close_inotify_fd (int fd)
{
  int r;

  if ((r = close (fd)) < 0)
    {
      perror ("close (fd) = ");
    }

  watched_items = 0;
  return r;
}

/* This method does the work of determining what happened,
   then allows us to act appropriately
*/
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;

      /* File was moved to X */
    case IN_MOVED_TO:
      printf ("MOVED_TO: %s \"%s\" on WD #%i. Cookie=%d\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd, 
              cur_event_cookie);
      break;

      /* Subdir or file was deleted */
    case IN_DELETE:
      printf ("DELETE: %s \"%s\" on WD #%i\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Subdir or file was created */
    case IN_CREATE:
      printf ("CREATE: %s \"%s\" on WD #%i\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Watched entry was deleted */
    case IN_DELETE_SELF:
      printf ("DELETE_SELF: %s \"%s\" on WD #%i\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Watched entry was moved */
    case IN_MOVE_SELF:
      printf ("MOVE_SELF: %s \"%s\" on WD #%i\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Backing FS was unmounted */
    case IN_UNMOUNT:
      printf ("UNMOUNT: %s \"%s\" on WD #%i\n",
          cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* Too many FS events were received without reading them
         some event notifications were potentially lost.  */
    case IN_Q_OVERFLOW:
      printf ("Warning: AN OVERFLOW EVENT OCCURRED: \n");
      break;

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

void handle_events (queue_t q)
{
  queue_entry_t event;
  while (!queue_empty (q))
    {
      event = queue_dequeue (q);
      handle_event (event);
      free (event);
    }
}

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

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);
}

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

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

int ignore_wd (int fd, int wd)
{
  int r;
  r = inotify_rm_watch (fd, wd);
  if (r < 0)
    {
      perror ("inotify_rm_watch(fd, wd) = ");
    }
  else 
    {
      watched_items--;
    }
  return r;
}

测试代码的实现inotify_test.c

/*
*   file inotify_test.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/inotify.h>
#include <signal.h>

#include "event_queue.h"
#include "inotify_utils.h"
int keep_running;

/* This program will take as arguments one or more directory 
   or file names, and monitor them, printing event notifications 
   to the console. It will automatically terminate if all watched
   items are deleted or unmounted. Use ctrl-C or kill to 
   terminate otherwise.
*/

/* 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);
      /*wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS & ~(IN_CLOSE | IN_OPEN) ); */
    }

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

编译的makefile:

CC=gcc
CFLAGS=-Wall -g
SRCS=inotify_test.c inotify_utils.c event_queue.c
OBJS=inotify_test.o inotify_utils.o event_queue.o

.c.o:
    $(CC) $(CFLAGS) -c $<

all: inotify_test

inotify_test: $(OBJS)
    $(CC) $(CFLAGS) inotify_utils.o inotify_test.o event_queue.o -o inotify_test

clean:
    rm -f $(OBJS) *.bak inotify_test

将event_queue.h event_queue.c inotify_utils.h inotify_utils.c inotify_test.c Makefile保存在同一目录下,执行make后会生成测试程序inotify_test。运行./inotify_test /home/xiaojingling 在监控的目录内操作文件或者目录就可以看到测试效果!

参考资料:
用 inotify 监控 Linux 文件系统事件
Linux inotify功能及实现原理

Logo

更多推荐