某些应用程序需要对文件或目录进行监控,以感知这些文件或目录发生了特定事件。在 Linux 中提供了 inotify 机制允许应用程序可以监听文件(目录)事件。
本文主要从以下几个方面对 inotify 进行介绍:
inotify 使用场景
inotify 机制关联的相关系统调用
inotify 支持的事件类型
inotify 使用示例
使用场景
监听文件或者目录的变更,最终目的一定是基于不同的变更事件采取相对应的处理措施。比较常见的使用场景如下:
配置文件热加载,当配置文件发生变化时进程可以自动感知并重新 reload 配置文件,如 golang 的明星项目--viper
配置保持功能,当我们需要保持服务器上某些文件不被改动时,可以监听需要保持的文件。当文件出现变更时做相应的恢复处理
当文件移出或者加入到某个目录下的时候,图形化文件管理器需要根据对应的事件作出相对应的调整
系统调用 与 inotify 有关的系统调用主要有三个:inotify_init,inotify_add_watch,inotify_rm_watch
inotify_init
#include
int inotify_init(void);
inotify_init创建一个inotify实例,该函数会返回文件描述符用来指代inotify实例,同时之后需要通过对该文件描述符进行read 操作获取文件变更事件
inotify_add_watch
#include
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
fd 指代 inotify_init 系统调用返回的 notify 实例
pathname 指代需要被监听事件的文件或者目录路径
mask 事件掩码,表明需要监听的事件类型。具体的事件类型下文会进行描述
该系统调用返回值(wd)是监控描述符,指代一条监控项。
上图展示了一个notify实例,以及该实例维护的一组监控项
监控描述符就是 inotify_add_watch系统调用的返回,唯一指代一个监控描述项
掩码即是 mask 用来定义具体的监听事项
pathname 即是完整的待监听文件或目录的合法路径
inotify_rm_watch
#include
int inotify_rm_watch(int fd, uint32_t wd);
fd 指代 inotify_init 系统调用返回的 notify 实例
wd 指代监控项描述符
事件类型
常规事件类型
mask 标志描述
IN_ACCESS
文件被访问(执行了 read 操作)
IN_ATTRIB
文件元数据发生变更
IN_CLOSE_WRITE
关闭为了写入而打开的文件
IN_CLOSE_NOWRITE
关闭以只读方式打开的文件
IN_CREATE
在受控目录内创建了文件或者目录
IN_DELETE
在受控目录内删除了文件或者目录
IN_DELETE_SELF
删除受控文件或者目录本身
IN_MOVED_FROM
文件移出受控目录之外
IN_MOVED_TO
文
件移入受控目录
IN_OPEN
文件被打开
IN_MOVE
IN_MOVED_FROM|IN_MOVED_TO 事件的统称
IN_CLOSE
IN_CLOSE_WRITE|IN_CLOSE_NOWRITE 统称
IN_ATTRIB监控的元数据变更包括,权限,所有权,链接数,用户 ID 等
重命名受控对象时会发出IN_DELETE_SELF事件,而如果受控目标是一个目录,那么受控目标下的文件发生重命名时会触发两个事件IN_MOVED_FROM和IN_MOVED_TO
在我们的日常开发工作中,上述事件已经基本涵盖了文件变更的所有情况。我们可以按照各自的场景,针对上述不同的事件类型做出相应的处理流程。
其他事件
除了上述文件的常规事件外,inotify还提供了以下几个 mask 来控制事件监听的过程
mask 标志描述
IN_DONT_FOLLOW
不对符号链接引用
IN_MASK_ADD
将事件追加到 pathname 的当前监控掩码
IN_ONESHOT
只监控 pathname 的一个事件
IN_ONLYDIR
pathname 不为目录时会失败
将上述 mask 标志添加到 inotify_add_watch 中时可以控制监听过程,这么说有点笼统,举个例子来说。
inotify_add_watch(fd, pathname, IN_OPEN | IN_CLOSE | IN_ONESHOT);
上面这段代码,除了监听文件的 IN_OPEN和IN_CLOSE事件外,还添加了 IN_ONESHOT mask,那么这就意味着,当监听到 pathname 所指代的文件一次事件后 inotify就不会在监听 pathname 所指代的文件发出的事件了。
上述 mask 是在添加某个文件监控项的时候作为inotify_add_watch系统调用的参数传入的。除此之外还有以下几个事件,这些事件不需要用户显示调用inotify_add_watch添加,仅当出现一些其他异常情况时发出。
IN_ISDIR事件表明被监听的 pathname 指代的是一个目录,举例来说 mkdir /tmp/xxx 这个系统命令会产生 IN_CREATE|IS_DIR 事件。
事件结构
上文描述了inotify支持的事件类型,可以看出来支持的事件类型非常丰富,基本满足了我们对于文件监听的各种诉求。除了上述的事件类型外,在这一小节我们会简单描述一下inotify的event结构,通过事件的数据结构可以看出,从事件中我们可以获取到哪些信息。事件的具体数据结构如下:
mask 标志描述
IN_IGNORED
监控项为内核或者应用程序移除
IN_ISDIR
被监听的是一个目录的路径
IN_Q_OVERFLOW
事件队列溢出
IN_UNMOUNT
包含对象的文件系统遭到卸载
struct inotify_event {
int wd; //监控描述符号,唯一指代一个监控项目
uint32_t mask; //监控的事件类型
uint32_t cookie; //只有重命名才会使用到该字段
uint32_t len; //下面 name 数组的尺寸
char name[]; //当受控目录下的文件有变更时,该字符串会记录发生变更的文件的文件名
};
inotify的事件队列并不是无限大的,因为队列也是需要消耗内核内存的,因此会设置一些上限加以限制,具体的配置可以通过修改对/proc/sys/fs/inotify下的三个文件来达到控制文件事件监听的目的。
max_queued_events: 规定了 inotify事件队列的数量上限,一旦超出这个限制,系统就会生成IN_Q_OVERFLOW事件,该事件上文有详细描述,这里不再赘述。
max_user_instances:对由每个真实用户 ID 创建的inotify实例数的限制值。
max_user_watchers:对由每个真实用户 ID 创建的监控项数量的限制值。
inotify-tools的安装和使用
inotify-tools提供两种工具,一是 inotifywait,它是用来监控文件或目录的变化,二是inotifywatch,它是用来统计文件系统访问的次数。 kernel 2.6.13以上都支持(直接安装使用)运行测试命令 [root@hostname inotify]# grep INOTIFY_USER /boot/config-$(uname -r) CONFIG_INOTIFY_USER=y 如果结果=y(yes),完美支持
wget http://github.com/downloads/rvoicilas/inotify-tools/inotify-tools-3.14.tar.gz #下载最新版
tar zxvf inotify-tools-3.14.tar.gz #解压到当前目录
cd inotify-tools-3.14 #进入目录
./configure --prefix=/opt/ideatech/inotify/ #编译配置,这里是指定位置(你的文件目录)
make #编译
make install #安装
make clean #从源文件夹清除二进制对象等
cd /opt/ideatech/inotify/bin
./inotifywait --help inofify帮助命令
#为方便使用可以选择设置
#设置系统环境变量,添加软连接
echo "PATH=$PATH:/opt/ideatech/inotify/bin" >>/etc/profile.d/inotify.sh
source /etc/profile.d/inotify.sh #使设置立即生效
echo "/opt/ideatech/inotify/lib" >/etc/ld.so.conf.d/inotify.conf && ldconfig
ln -s /opt/ideatech/inotify/include /usr/include/inotify
inotifywait:监控文件或目录变化 语法: inotifywait [-hcmrq] [-e ] [-t ] [--format ] [--timefmt ] [ ... ] 参数: -h,–help 输出帮助信息 @ 排除不需要监视的文件,可以是相对路径,也可以是绝对路径。 –fromfile 从文件读取需要监视的文件或排除的文件,一个文件一行,排除的文件以@开头。 -m, –monitor 接收到一个事情而不退出,无限期地执行。默认的行为是接收到一个事情后立即退出。 -d, –daemon 跟–monitor一样,除了是在后台运行,需要指定–outfile把事情输出到一个文件。也意味着使用了–syslog。 -o, –outfile 输出事情到一个文件而不是标准输出。 -s, –syslog 输出错误信息到系统日志 -r, –recursive 监视一个目录下的所有子目录。 -q, –quiet 指定一次,不会输出详细信息,指定二次,除了致命错误,不会输出任何信息。 –exclude 正则匹配需要排除的文件,大小写敏感。 –excludei 正则匹配需要排除的文件,忽略大小写。 -t , –timeout 设置超时时间,如果为0,则无限期地执行下去。 -e , –event 指定监视的事件。 -c, –csv 输出csv格式。 –timefmt 指定时间格式,用于–format选项中的%T格式。 –format 指定输出格式。 %w 表示发生事件的目录 %f 表示发生事件的文件 %e 表示发生的事件 %Xe 事件以“X”分隔 %T 使用由–timefmt定义的时间格式 inotifywatch:统计文件的系统访问次数 语法: inotifywatch [-hvzrqf] [-e ] [-t ] [-a ] [-d ] [ ... ] 参数: -h, –help 输出帮助信息 -v, –verbose 输出详细信息 @ 排除不需要监视的文件,可以是相对路径,也可以是绝对路径。 –fromfile 从文件读取需要监视的文件或排除的文件,一个文件一行,排除的文件以@开头。 -z, –zero 输出表格的行和列,即使元素为空 –exclude 正则匹配需要排除的文件,大小写敏感。 –excludei 正则匹配需要排除的文件,忽略大小写。 -r, –recursive 监视一个目录下的所有子目录。 -t , –timeout 设置超时时间 -e , –event 只监听指定的事件。 -a , –ascending 以指定事件升序排列。 -d , –descending 以指定事件降序排列。 可监听事件 access 文件读取 modify 文件更改。 attrib 文件属性更改,如权限,时间戳等。 close_write 以可写模式打开的文件被关闭,不代表此文件一定已经写入数据。 close_nowrite 以只读模式打开的文件被关闭。 close 文件被关闭,不管它是如何打开的。 open 文件打开。 moved_to 一个文件或目录移动到监听的目录,即使是在同一目录内移动,此事件也触发。 moved_from 一个文件或目录移出监听的目录,即使是在同一目录内移动,此事件也触发。 move 包括moved_to和 moved_from move_self 文件或目录被移除,之后不再监听此文件或目录。 create 文件或目录创建 delete 文件或目录删除 delete_self 文件或目录移除,之后不再监听此文件或目录 unmount 文件系统取消挂载,之后不再监听此文件系统。