Linux中inotify详解及实例

1、inotify简介

    用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,包括了hotplug、udev 和 inotify。Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理,udev 动态地维护 /dev 下的设备文件,

       Inotify 是一个 Linux特性,它监控文件系统操作,比如读取、写入和创建,以及卸载等操作,还可以跟踪活动的源头和目标等细节。即文件系统变化通知机制。inotify摈弃了dnotify的信号方式,采用在文件系统的处理函数中放置hook函数的方式实现。Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多。

 

2、inotify的使用

(1)原理

       使用 inotify 很简单:创建一个文件描述符,附加一个或多个监视器(一个监视器 是一个路径和一组事件),然后使用 read 方法从描述符获取事件。read 并不会用光整个周期,它在事件发生之前是被阻塞的。

更好的是,因为inotify 通过传统的文件描述符工作,您可以利用传统的 select 系统调用来被动地监控监视器和许多其他输入源。两种方法 — 阻塞文件描述符和使用 select— 都避免了繁忙轮询。

(2)具体使用

a、创建inotify实例

int fd = inotify_init ();

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

b、添加一个 watch

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

c、删除一个 watch

int ret = inotify_rm_watch (fd, wd);
  • fd 是 inotify_init() 返回的文件描述符;
  • wd 是 inotify_add_watch() 返回的 watch 描述符;
  • ret 是函数的返回值。

d、文件事件

       文件事件用一个 inotify_event 结构表示,它通过由 inotify_init() 返回的文件描述符使用通常文件读取函数 read 来获得

struct inotify_event {
        __s32           wd;             /*  watch 描述符*/
        __u32           mask;           /* 事件掩码 */
        __u32           cookie;         /* cookie to synchronize two events */
        __u32           len;            /* name字符串的长度 */
        char            name[0];        /* 被监视目标的路径名 */
};

       注,name 为被监视目标的路径名,该结构的 name 字段为一个桩,它只是为了用户方面引用文件名,文件名是变长的,它实际紧跟在该结构的后面,文件名将被 0 填充以使下一个事件结构能够 4 字节对齐。注意,len 也把填充字节数统计在内。

c、获取事件

       通过 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);

 

3、内核实现原理

       在内核中,每一个 inotify 实例对应一个 inotify_device 结构:

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

    结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭 inotify_init()返回的文件描述符时将被释放。结构 inotify_watch 在用户态调用 inotify_add_watch()时创建,在用户态调用 inotify_rm_watch() 或 close(fd) 时被释放。

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 的事件掩码,表示它对哪些文件系统事件感兴趣,mask有:
IN_ACCESS : 文件的读操作
IN_ATTRIB : 文件属性变化
IN_CLOSE_WRITE : 文件被关闭之前被写
IN_CLOSE_NOWRITE : 文件被关闭
IN_CREATE : 新建文件
IN_DELETE : 删除文件
IN_MODIFY : 修改文件
IN_MOVE_SELF : 被监控的文件或者目录被移动
IN_MOVED_FROM : 文件从被监控的目录中移出
IN_MOVED_TO : 文件从被监控的目录中移入
IN_OPEN : 文件被打开

    无论是目录还是文件,在内核中都对应一个 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 系统,其中*号就是相应的事件名,目前实现包括:

Linux中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 上。

 

4、实例

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

using namespace std;

//从buf中取出一个事件
static void   _inotify_event_handler(struct inotify_event *event)       
{
	printf("event->mask: 0x%08x\n", event->mask);
	printf("event->name: %s\n", event->name);
}

int  main(int argc, char **argv)
{
	/*if (argc != 2) {
		printf("Usage: %s <file/dir>\n", argv[0]);
		return -1;
	}*/

	char * path = "/home/wengzh6/projects/Inotify/bin/x64/Debug/testFile.txt";

	unsigned char buf[1024] = { 0 };
	struct inotify_event *event = NULL;

	int fd = inotify_init();										      //初始化
	// int wd = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS);            //监控指定文件的ALL_EVENTS。
	int wd = inotify_add_watch(fd, path, IN_ALL_EVENTS);                  //监控指定文件的ALL_EVENTS。

	for (;;) {
		fd_set fds;
		FD_ZERO(&fds);
		FD_SET(fd, &fds);

		if (select(fd + 1, &fds, NULL, NULL, NULL) > 0){							 //监控fd的事件。当有事件发生时,返回值>0
			int len, index = 0;
			while (((len = read(fd, &buf, sizeof(buf))) < 0) && (errno == EINTR));   //没有读取到事件。
			while (index < len) {
				event = (struct inotify_event *)(buf + index);
				_inotify_event_handler(event);                                       //获取事件。
				index += sizeof(struct inotify_event) + event->len;					 //移动index指向下一个事件。
			}
		}
	}

	inotify_rm_watch(fd, wd);              //删除对指定文件的监控。

	return 0;
}

编辑文件时监控到的结果如下:

Linux中inotify详解及实例

 

参考:

https://blog.csdn.net/breakout_alex/article/details/89028868

https://www.sohu.com/a/244164762_467784

 

上一篇:Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步


下一篇:linux使用inotify+rsync实现监控目录变化