linux 文件监控之 inotify、及inotify-tools的安装和使用
某些应用程序需要对文件或目录进行监控,以感知这些文件或目录发生了特定事件。在 Linux 中提供了 inotify 机制允许应用程序可以监听文件(目录)事件。
本文主要从以下几个方面对 inotify 进行介绍:
- inotify 使用场景
- inotify 机制关联的相关系统调用
- inotify 支持的事件类型
- inotify 使用示例
使用场景
监听文件或者目录的变更,最终目的一定是基于不同的变更事件采取相对应的处理措施。比较常见的使用场景如下:
- 配置文件热加载,当配置文件发生变化时进程可以自动感知并重新 reload 配置文件,如 golang 的明星项目--viper
- 配置保持功能,当我们需要保持服务器上某些文件不被改动时,可以监听需要保持的文件。当文件出现变更时做相应的恢复处理
- 当文件移出或者加入到某个目录下的时候,图形化文件管理器需要根据对应的事件作出相对应的调整
系统调用 与 inotify 有关的系统调用主要有三个:inotify_init
,inotify_add_watch
,inotify_rm_watch
inotify_init
#include <sys inotify.h=""> int inotify_init(void);
inotify_init
创建一个inotify
实例,该函数会返回文件描述符用来指代inotify
实例,同时之后需要通过对该文件描述符进行read 操作
获取文件变更事件
inotify_add_watch
#include <sys inotify.h=""> 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 <sys inotify.h=""> 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 [-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 文件系统取消挂载,之后不再监听此文件系统。