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

概要 - 为什么需要监控文件系统?

在日常工作中,人们往往需要知道在某些文件(夹)上都有那些变化,比如:

  • 通知配置文件的改变
  • 跟踪某些关键的系统文件的变化
  • 监控某个分区磁盘的整体使用情况
  • 系统崩溃时进行自动清理
  • 自动触发备份进程
  • 向服务器上传文件结束时发出通知

通常使用文件轮询的通知机制,但是这种机制只适用于经常改变的文件(因为它可以确保每过x秒就可以得到i/o),其他情况下都非常低效,并且有时候会丢失某些类型的变化,例如文件的修改时间没有改变。像Tripwire这样的数据完整性系统,它们基于时间调度来跟踪文件变化,但是如果想实时监控文件的变化的话,那么时间调度就束手无策了。Inotify就这样应运而生了。本文将简要介绍inotify,告诉我们如何监控文件夹,如何一有变化就报告相关消息事件,并介绍了一些相关工具, 我们可以把它们添加到自己的工具箱中。

Inotify到底是什么?

Inotify是一种文件变化通知机制,Linux内核从2.6.13开始引入。在BSD和Mac OS系统中比较有名的是kqueue,它可以高效地实时跟踪Linux文件系统的变化。近些年来,以fsnotify作为后端,几乎所有的主流Linux发行版都支持Inotify机制。如何知道你的Linux内核是否支持Inotify机制呢?很简单,执行下面这条命令:

% grep INOTIFY_USER /boot/config-$(uname -r)
CONFIG_INOTIFY_USER=y

如果输出('CONFIG_INOTIFY_USER=y'),那么你可以马上享受Inotify之旅了。

简单的文件变化通知样例:

好的开始是成功的一半,对于了解Inotify机制来说,让我们从使用inotifywait程序开始,它包含在inotify-tools工具包中。假如我们打算监控/srv/test文件夹上的操作,只需执行:

% inotifywait -rme modify,attrib,move,close_write,create,delete,delete_self /srv/test
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.

上述任务运行的同时,我们在另一个shell里依次执行以下操作:创建文件夹,然后在新文件夹下创建文件,接着删除新创建的文件:

% mkdir /srv/test/infoq
% echo TODO > /srv/test/infoq/article.txt
% rm /srv/test/infoq/article.txt

在运行inotifywait的shell中将会打印以下信息:

/srv/test/ CREATE,ISDIR infoq
/srv/test/infoq/ CREATE article.txt
/srv/test/infoq/ MODIFY article.txt
/srv/test/infoq/ CLOSE_WRITE,CLOSE article.txt
/srv/test/infoq/ DELETE article.txt

显而易见,只要有变化我们就会收到相关的通知。如果想了解关于Inotify提供的事件(如modify, atrrib等)的详细信息,请参考inotifywatch的manpage。实际使用时,如果并不想监控某个大文件夹,那么就可以使用inotifywait的exclude选项。例如:我们要忽略文件夹/srv/test/large,那么就可以这样来建立监控:

% inotifywait --exclude '^/srv/test/(large|ignore)/' -rme modify,attrib,move,close_write,create,delete,delete_self /srv/test
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.

上面的例子中,在exclude选项的匹配串中我们使用了正则表达式,因为我们不想将名称中含有large或ignore的文件也排除掉。我们可以测试一下:

% echo test > /srv/test/action.txt
% echo test > /srv/test/large/no_action.txt
% echo test > /srv/test/ignore/no_action.txt
% echo test > /srv/test/large-name-but-action.txt

这里inotifywait应该只会报告'action.txt'和'large-name-but-action.txt'两个文件的变化,而忽略子文件夹'large'和'ignore'下的文件,结果也确实如此;

/srv/test/ CREATE action.txt
/srv/test/ MODIFY action.txt
/srv/test/ CLOSE_WRITE,CLOSE action.txt
/srv/test/ CREATE large-name-but-action.txt
/srv/test/ MODIFY large-name-but-action.txt
/srv/test/ CLOSE_WRITE,CLOSE large-name-but-action.txt

另外,通过使用-t选项我们还可以定义inotifywait的监控时间,既可以让它执行一段时间,也可以让它一直运行。util-linux-ng的logger命令也可以实现此功能,不过得先把相关的消息事件发送到syslog,然后从syslog服务器再分析整合。

Inotifywatch - 使用inotify来统计文件系统访问信息

Inotify-tools中还有一个工具叫inotifywatch,它会先监听文件系统的消息事件,然后统计每个被监听文件或文件夹的消息事件,之后输出统计信息。比如我们想知道某个文件夹上有那些操作:

% inotifywatch -v -e access -e modify -t 120 -r ~/InfoQ
Establishing watches...
Setting up watch(es) on /home/mika/InfoQ
OK, /home/mika/InfoQ is now being watched.
Total of 58 watches.
Finished establishing watches, now collecting statistics.
Will listen for events for 120 seconds.
total  modify  filename
2      2       /home/mika/InfoQ/inotify/

很显然,这里我们监控的是~/InfoQ文件夹,并且可以看到/home/mika/InfoQ/inotify上发生了两个事件。方法虽简单,但却很有效。

Inotify的配置选项

使用Inotify时,要特别注意内核中关于它的两个配置。首先/proc/sys/fs/inotify/max_user_instances 规定了每个用户所能创建的Inotify实例的上限;其次/proc/sys/fs/inotify/max_user_watches规定了每个Inotify实例最多能关联几个监控(watch)。你可以很容易地试验在运行过程中达到上限,如:

% inotifywait -r /
Setting up watches.  Beware: since -r was given, this may take a while!
Failed to watch /; upper limit on inotify watches reached!
Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'.

如果要改变这些配置,只要向相应的文件写入新值即可,如下所示:

# cat /proc/sys/fs/inotify/max_user_watches
8192
# echo 16000 > /proc/sys/fs/inotify/max_user_watches
# cat /proc/sys/fs/inotify/max_user_watches
16000

使用Inotify的一些工具

近一段时间出现了很多基于Inotify的超炫的工具,如incron,它是一个类似于cron的守护进程(daemon),传统的cron守护进程都是在规定的某个时间段内执行,而incron由于使用了Inotify,可以由事件触发执行。同时incron的安装简单而直观,比如在debian上,首先在/etc/incron.allow中添加使用incron的用户(debian默认不允许用户使用incron,因为如果incron使用不慎的话,例如形成死循环,则会导致系统宕机):

# echo username > /etc/incron.allow

然后调用”incrontab -e“, 在弹出的编辑器中插入我们自己的规则,例如下面的这条简单的规则,文件一变化incron就会发邮件通知我们:

/srv/test/ IN_CLOSE_WRITE mail -s "$@/$#\n" root

从现在开始,一旦/src/test文件夹中的文件被修改,就会发送一封邮件。但是注意不要让incron监控整个子目录树,因为Inotify只关注inodes,而不在乎是文件还是文件夹,所以基于Inotify的软件都需要自己来处理/预防递归问题。关于incontab详细使用,请参考incrontab的manpage。

如果你还要处理incoming文件夹,那么你可能需要inoticoming。当有文件进入incoming文件夹时Inoticoming就会执行某些动作,从而可以用inoticoming来管理debian的软件仓库(例如软件仓库中一旦有上传源码包或是新添加的二进制包就立刻自动进行编译),另外,还可以用它来监控系统是否有新上传文件,如果有就发送通知。类似的工具还有(它们都各有专长):inosync(基于消息通知机制的文件夹同步服务),iwatch(基于Inotify的程序,对文件系统进行实时监控),以及lsyncd(一个守护进程(daemon),使用rsync同步本地文件夹)。

Inotify甚至对传统的Unix工具也进行了改进,例如tail。使用inotail,同时加上-f选项,就可以取代每秒轮询文件的做法。此外,GNU 的coreutils从版本7.5开始也支持Inotify了,我们可以运行下面的命令来确认:

# strace -e inotify_init,inotify_add_watch tail -f ~log/syslog
[...]
inotify_init()                          = 4
inotify_add_watch(4, "/var/log/syslog", IN_MODIFY|IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF) = 1

从现在开始,通过轮询来确实文件是否需要重新读取的方法应该作为古董了。

在脚本中使用Inotify

Inotify机制并不局限于工具,在脚本语言中也完全可以享受Inotify的乐趣,如Python中可以使用pyinotify和inotifyx,Perl中有Filesys-Notify-Simple和Linux-Inotify2,Inotify的Ruby版有ruby-inotifyrb-inoty和fssm。

总结

综上所述,Inotify为Linux提供了一套高效监控和跟踪文件变化的机制,它可以实时地处理、调试以及监控文件变化,而轮询是一种延迟机制。对于系统管理员,关于实现事件驱动的服务如系统备份,构建服务以及基于文件操作的程序调试等,Inotify无疑提供了强大的支持。

查看英文原文:Inotify: Efficient, Real-Time Linux File System Event Monitoring


感谢侯伯薇对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

Android文件监控FileObserver介绍

在前面介绍了Linux对文件变更监控过程。Android系统在此基础上封装了一个FileObserver类来方便使用Inotify机制。FileObserver是一个抽象类,需要定义子类实现该类的onEvent抽象方法,当被监控的文件或者目录发生变更事件时,将回调FileObserver的onEvent()函数来处理文件或目录的变更事件。

事件监控过程

在FileObserver类中定义了一个静态内部类ObserverThread,该线程类才是真正实现文件或目录监控过程。各种类型的FileObserver都拥有一个ObserverThread实例:

frameworks\base\core\java\android\os\FileObserver.java

view source print ?
01. public abstract class FileObserver {
02. //可监控的事件类型
03. public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE
04. | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE | DELETE_SELF | MOVE_SELF;
05. //静态创建并启动一个文件监控线程
06. private static ObserverThread s_observerThread;
07. static {
08. s_observerThread = new ObserverThread();
09. s_observerThread.start();
10. }
11. // instance
12. private String m_path;
13. private Integer m_descriptor;
14. private int m_mask;
15. }

FileObserver类通过静态方式构造了一个ObserverThread对象:

view source print ?
1. public ObserverThread() {
2. super("FileObserver");
3. m_fd = init();//初始化一个inotify实例,Observer线程就是对该inotify实例进行监控
4. }

frameworks\base\core\jni\android_util_FileObserver.cpp

view source print ?
1. static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
2. {
3. #ifdef HAVE_INOTIFY
4. return (jint)inotify_init();//初始化一个inotify实例  
5. #else // HAVE_INOTIFY
6. return -1;
7. #endif // HAVE_INOTIFY
8. }

inotify_init()函数实现在Linux文件系统Inotify机制 有详细介绍,然后启动ObserverThread线程,ObserverThread线程运行体:

frameworks\base\core\java\android\os\FileObserve$ObserverThread

view source print ?
1. public void run() {
2. observe(m_fd);//监控inotify实例句柄
3. }

frameworks\base\core\jni\android_util_FileObserver.cpp

view source print ?
01. static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd)
02. {
03. #ifdef HAVE_INOTIFY
04. char event_buf[512];//定义事件数组
05. struct inotify_event* event;
06. while (1)
07. {
08. int event_pos = 0;
09. //从inotify实例句柄中读取事件
10. int num_bytes = read(fd, event_buf, sizeof(event_buf));
11. if (num_bytes < (int)sizeof(*event))
12. {
13. if (errno == EINTR)
14. continue;
15. ALOGE("***** ERROR! android_os_fileobserver_observe() got a short event!");
16. return;
17. }
18. //循环处理读取到的事件
19. while (num_bytes >= (int)sizeof(*event))
20. {
21. int event_size;
22. event = (struct inotify_event *)(event_buf + event_pos);
23. jstring path = NULL;
24. if (event->len > 0)
25. {
26. path = env->NewStringUTF(event->name);
27. }
28. //调用ObserverThread的onEvent函数通知上层响应
29. env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
30. if (env->ExceptionCheck()) {
31. env->ExceptionDescribe();
32. env->ExceptionClear();
33. }
34. if (path != NULL)
35. {
36. env->DeleteLocalRef(path);
37. }
38. event_size = sizeof(*event) + event->len;
39. num_bytes -= event_size;
40. event_pos += event_size;
41. }
42. }
43. #endif // HAVE_INOTIFY
44. }

ObserverThread线程循环从inotify实例句柄中读取事件,然后回调ObserverThread的onEvent函数来处理事件。

frameworks\base\core\java\android\os\FileObserve$ObserverThread

view source print ?
01. public void onEvent(int wfd, int mask, String path) {
02. // look up our observer, fixing up the map if necessary...
03. FileObserver observer = null;
04. synchronized (m_observers) {
05. //根据wfd句柄从m_observers表中查找出注册的FileObserver对象
06. WeakReference weak = m_observers.get(wfd);
07. if (weak != null) {  // can happen with lots of events from a dead wfd
08. observer = (FileObserver) weak.get();
09. if (observer == null) {
10. m_observers.remove(wfd);
11. }
12. }
13. }
14. // ...then call out to the observer without the sync lock held
15. if (observer != null) {
16. try {
17. //调用对应的FileObserver对象的onEvent函数来处理事件
18. observer.onEvent(mask, path);
19. catch (Throwable throwable) {
20. Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
21. }
22. }
23. }

注册监控watch

FileObserver类提供了startWatching()函数来启动文件监控

frameworks\base\core\java\android\os\FileObserver.java

view source print ?
1. public void startWatching() {
2. if (m_descriptor < 0) {
3. m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
4. }
5. }

由ObserverThread线程对象启动监控

frameworks\base\core\java\android\os\FileObserver$ObserverThread

view source print ?
01. public int startWatching(String path, int mask, FileObserver observer) {
02. //在Inotify实例中添加一个watch对象,并得到一个watch对象句柄
03. int wfd = startWatching(m_fd, path, mask);
04. Integer i = new Integer(wfd);
05. if (wfd >= 0) {
06. //将watch对象句柄和响应该watch事件的FileObserver以键值对的形式保存在m_observers成员变量中
07. synchronized (m_observers) {
08. m_observers.put(i, new WeakReference(observer));
09. }
10. }
11. return i;
12. }

ObserverThread又调用native方法android_os_fileobserver_startWatching()来添加一个watch

frameworks\base\core\jni\android_util_FileObserver.cpp

view source print ?
01. static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
02. {
03. int res = -1;
04. #ifdef HAVE_INOTIFY
05. if (fd >= 0)
06. {
07. const char* path = env->GetStringUTFChars(pathString, NULL);
08. //在Inotify实例上添加一个watch对象
09. res = inotify_add_watch(fd, path, mask);
10. env->ReleaseStringUTFChars(pathString, path);
11. }
12. #endif // HAVE_INOTIFY
13. return res;
14. }

注销监控watch

FileObserver类提供了使用stopWatching()函数来停止文件监控。
frameworks\base\core\java\android\os\FileObserver$ObserverThread

view source print ?
1. public void stopWatching() {
2. if (m_descriptor >= 0) {
3. s_observerThread.stopWatching(m_descriptor);
4. m_descriptor = -1;
5. }
6. }

frameworks\base\core\java\android\os\FileObserve$ObserverThread

view source print ?
1. public void stopWatching(int descriptor) {
2. stopWatching(m_fd, descriptor);
3. }

frameworks\base\core\jni\android_util_FileObserver.cpp

view source print ?
1. static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd)
2. {
3. #ifdef HAVE_INOTIFY
4. inotify_rm_watch((int)fd, (uint32_t)wfd);
5. #endif // HAVE_INOTIFY
6. }

Android文件监控FileObserver介绍 - 深入剖析Android系统 - 博客频道 - CSDN.NET
http://blog.csdn.net/yangwen123/article/details/36379763

Inotify与Android文件监控FileObserver原理相关推荐

  1. 基于inotify的文件监控方案

    最近在做一个linux上的文件监控程序,2.6内核提供了inotify机制,这仅仅是个机制,任何策略都必须自己实现,这一点从inotify不提供递归接口就可以看出来,如果我实时监控到目录被创建,那么马 ...

  2. PHP Linux监控文件变化,文件监控与通知机制 audit inotify

    什么是auditThe Linux Audit Subsystem is a system to Collect information regarding events occurring on t ...

  3. python watchdog 同时检测到多个事件_python中watchdog文件监控与检测上传功能

    引言 上一篇介绍完了观察者模式的原理,本篇想就此再介绍一个小应用,虽然我也就玩了一下午,是当时看observer正好找到的,以及还有Django-observer,但Django很久没用了,所以提下这 ...

  4. Android手机一键Root原理分析(作者:非虫,文章来自:《黑客防线》2012年7月)

    之前几天都在做Android漏洞分析的项目,万幸发现了这篇文章.废话不多说,上文章! <Android手机一键Root原理分析> (作者:非虫,文章来自:<黑客防线>2012年 ...

  5. inotify + rsync 打造文件实时同步

    rsync 是Linux 下的一个文件同步利器,以高效的增量传输而闻名,适用于同步,备份等多个场合.几乎所有的Linux 发行版都收录了该软件,安装非常容易,以CentOS 6.4为例: 1 yum  ...

  6. android 红外遥控器实现原理

    一.红外遥控器是什么鬼 现有的红外遥控器有两种:一种是PWM(脉冲宽度调制),另外一种是PPM(脉冲位置调制): 这两种调制方式对应两种编码形式NEC(PWM对应的编码形式)和philips的RC-5 ...

  7. zabbix之日志文件监控

    一.日志item介绍  下面介绍zabbix另一个"重量级"的功能--日志文件监控,它最主要的是监控日志文件中有没有某个字符串的表达式,对应日志轮转与否,zabbix都支持. 在配 ...

  8. 【转载】CentsOS系统inotify实时监控服务器文件(夹)定制事件处理程序

    原始博文和参考博文 1.CentsOS系统inotify实时监控服务器文件 2.Linux中让进程在后台运行的方法 3.linux inotify 监控文件系统事件 非常好 方法一 说明: 服务器系统 ...

  9. android动画的实现原理,Android动画的实现原理 .

    1.动画运行模式 独行模式 中断模式 2.Animation类 每个动画都重载了父类的applyTransformation方法这个方法的主要作用是把一些属性组装成一个Transformation类, ...

最新文章

  1. android查询竞价处理,公平可靠的竞价方式,应对越来越高的流量获取成本,如何解决推广费用过高的问题可能是...
  2. 记-python中socket服务器设置中的setsockopt
  3. 固态硬盘量产工具_机械硬盘Q1出货量大降,电脑硬盘榜单出炉
  4. msp430入门编程11
  5. C语言判断系统是32位还是64位
  6. error_reporting()的用法
  7. python-玉米(小米)商城作业
  8. 原生javascript开发仿微信打飞机小游戏
  9. elasticsearch-head后台运行
  10. C#-Activex插件操作指南
  11. Charles(1) 请求转发
  12. 有关计算机知识的外文翻译,计算机专业外文翻译+原文-DBMS和MIS
  13. 【QT】对话框dialog
  14. 华为防火墙设置安全策略,封禁高危异常ip
  15. 经典车间生产调度问题模型及其算法 目录
  16. 在JS中利用for...in循环遍历对象
  17. java annotation class,Java Class类 isAnnotation()方法及示例
  18. 正面硬刚Beats!这款耳机从美国红回中国,细腻音质千元内无敌手!
  19. 嵌入式开发教程哪家好?linux嵌入式系统开发
  20. Linux网络编程7——epoll反应堆模型

热门文章

  1. windows查看端口号,查看端口号占用情况,杀掉端口
  2. 01-Spring初识
  3. OA系统如何打造集团多业务管理平台
  4. 18.js--三目运算符
  5. 2018世界机器人大会来了
  6. iOS 11 自适应布局教程: 开始
  7. JS中循环遍历数组的四种方式总结
  8. 逐步提升———十个python基础题(1—10,共100道)
  9. 服务器Svn 冲突解决
  10. Linux 日志切割神器 Logrotate 原理和配置详解