热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浅析Linuxinotify机制

2019独角兽企业重金招聘Python工程师标准简介在我们工作中,经常会遇到一些场景:在系统状况发生变化时,我们能够及时地被告知.而不是等到我们想起来,才去查看是否变化.特别

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

简介

在我们工作中,经常会遇到一些场景: 在系统状况发生变化时, 我们能够及时地被告知.而不是等到我们想起来,才去查看是否变化.特别是一些重要的事情, 如系统故障, 磁盘空间等等.这些硬件级别的"异步通知", 监控系统已经为我们实现了. 那么文件系统级别的呢? 或者举个更加具体的例子: 想要实时同步新文件, 做镜像备份, 那么怎样才能实现实时? 计划任务? 这固然可以, 但是在计划任务调度之间, 总会存在这么小间隔的"非实时", 其实大神们早已为我们想出更好的解决方案: inotify机制

顾名思义, inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知

事实上, 在inotify之前已经存在一种类似的机制: dnotify, 但据文档介绍, 它存在很多缺陷:

  • 对于想监视的每一个目录,用户都需要打开一个文件描述符,因此如果需要监视的目录较多,将导致打开许多文件描述符,特别是,如果被监视目录在移动介质上(如光盘和 USB 盘),将导致无法 umount 这些文件系统,因为使用 dnotify 的应用打开的文件描述符在使用该文件系统。
  • dnotify 是基于目录的,它只能得到目录变化事件,当然在目录内的文件的变化会影响到其所在目录从而引发目录变化事件,但是要想通过目录事件来得知哪个文件变化,需要缓存许多 stat 结构的数据。
  • Dnotify 的接口非常不友好,它使用 signal。

 而作为替代品的inotify却有更为突出的表现,它克服了dnotify的缺点之外,还有更多的优点:

  • inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
  • inotify 既可以监视文件,也可以监视目录。
  • inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
  • inotify 使用文件描述符作为接口,因而可以使用通常的文件 I/O 操作select 和 poll 来监视文件系统的变化。

 它可以监控的文件系统事件包括:

事件掩码文件系统事件
IN_ACCESS文件被访问
IN_MODIFY文件内容被修改
IN_ATTRIB文件属性被修改( 如 chmod )
IN_CLOSE_WRITE可写文件被关闭
IN_CLOSE_NOWRITE不可写文件被关闭
IN_MOVED_FROM文件被移走( 如 mv )
IN_MOVED_TO文件被移来( 如 mv, cp )
IN_OPEN文件被打开
IN_CREATE文件被创建
IN_DELETE文件被删除
IN_DELETE_SELF自删除( 一个可执行文件在执行时删除自己)
IN_MOVE_SELF自移动( 一个可执行文件在执行时移动自己 )
IN_UNMOUNT宿主文件系统被卸载
IN_CLOSE文件被关闭等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE文件被移动 等同于 (IN_MOVED_FROM | IN_MOVED_TO)

如何使用

在用户态, inotify通过三个系统调用和返回的文件描述符上的文件I/O操作来使用, 那么第一步就是创建inotify实例:

int fd = inotify_init();

每个inotify实例对应着一个独立的排序的队列.

文件系统的变化事件, 被称作watches的一个对象管理, 每一个watch是一个二元组(目标, 事件掩码), 目标可以是文件或者目录, 而事件掩码则是上述表格的内容.每一个对应inotify事件.watch对象通过watch描述符的引用,watches通过文件或者目录的路径名来添加.如果是目录, watch将返回该目录下所有关注的发生的事件.

添加一个watch:

int wd = inotify_add_watch(fd, path, mask);

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

如果想要去掉一个watch

int ret = inotify_rm_watch(fd, wd);

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

文件事件用inotify_event结构表示:

取自sys/inotify.h/* Structure describing an inotify event. */
struct inotify_event
{int wd; /* Watch descriptor. */uint32_t mask; /* Watch mask. */uint32_t COOKIE; /* COOKIE to synchronize two events. */uint32_t len; /* Length (including NULs) of name. */char name __flexarr; /* Name. */
};

结构体中的wd为被监视目标的watch描述符, mask为事件掩码, name为路径名, len为name字符的长度

通过read调用来获取事件:

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

buff 是一个inotify_event结构的结构体指针, BUF_LEN是指定要读取的总长度, buff长度要大于等于BUF_LEN. read调用返回的时间数取决于BUF_LEN以及时间中文件名的长度, len为实际读取的字节数, 即获取的事件中总长度.

可以在函数 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 来得到当前队列的长度。close(fd)将删除所有添加到 fd 中的 watch 并做必要的清理。

简单实现

#include
#include
#include
#define MAX_BUFFER_SIZE 4096
int main()
{int fd, wd;int len, index;char buffer[2048];struct inotify_event * event;char *path &#61; "/tmp/test";if((fd &#61; inotify_init()) <0){printf("Failed to initialize inotify\n");return 1;}// 通过inotify_add_watch向初始化的fd添加事件if((wd &#61; inotify_add_watch(fd, path, IN_CLOSE_WRITE | IN_CREATE)) <0){printf("Can&#39;t add watch for %s\n", path);return 1;}while(len &#61; read(fd, buffer, MAX_BUFFER_SIZE)){index &#61; 0;while(index wd !&#61; wd)continue;if(event->mask & IN_CLOSE_WRITE)printf("file %s is closed for writing.\n", event->name);else if(event->mask & IN_CREATE)printf("file %s is created.\n", event->name);// 移动到下一个事件index &#43;&#61; sizeof(struct inotify_event) &#43; event->len;}}return 0;}

[root&#64;iZ23pynfq19Z ~]# mkdir /tmp/test
[root&#64;iZ23pynfq19Z ~]# gcc 1.c && ./a.out
...处于堵塞状态## 当另一个终端创建了 文件11 之后的输出
file 11 is created.
file 11 is closed for writing.----------------------- 另一个终端 ---------------------
[root&#64;iZ23pynfq19Z ~]# cd /tmp/test/
[root&#64;iZ23pynfq19Z test]# touch 11

例子比较简单, 请大家见谅~ 后期咱们将其完善得更好吧~

----------------------------------- 可爱的分割线 -------------------------------

咱们也不能光会用, 了解下本质原理也是很不错的 ( 谷歌的结果 )

内核实现机理

在内核中, 每一个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 */
};

wq是等待队列, 被read调用堵塞的进程, 将挂在该等待队列上, idr用于把watch描述符映射到对应的inotify_watch, sem用于同步对该结构的访问.events为该inotify实例上发生的事件的列表, 被该inotify实例监听的所有事件在发生后都将插入到这个列表, watches是给inotify实例监视的watch列表, inotify_add_watch将把新添加的watch插入到该列表, count是引用计数, user用于描述创建该inotify实例的用户, queue_size表示该inotify实例的事件队列的字节数, event_count 是 events 列表的事件数, max_events为最大允许的事件数, last_wd是上次分配的watch描述符

每一个watch 对应一个inotify_watch 结构:

struct inotify_watch {struct list_head d_list; /* entry in inotify_device&#39;s list */struct list_head i_list; /* entry in inode&#39;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的事件掩码, 表示它对哪些文件系统事件感兴趣

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

无论是目录还是文件, 在内核中都对应这一个inode结构, inotify 系统在inode结构中增加了两个字段:

#ifdef CONFIG_INOTIFYstruct 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 结构, 并把它插入到被监视目标对应的inotify 的 inotify_watches 列表.inotify_sem用于同步 inotify_watches 列表的访问,. 当文件系统发生关注的事件之一时, 相应的文件系统代码将显示调用fsnotify_* 来把相应的事件报告给inotify系统, 其中 * 号就是相应的事件名, 目前实现包括:

  • fsnotify_move&#xff0c;文件从一个目录移动到另一个目录
  • fsnotify_nameremove&#xff0c;文件从目录中删除
  • fsnotify_inoderemove&#xff0c;自删除
  • fsnotify_create&#xff0c;创建新文件
  • fsnotify_mkdir&#xff0c;创建新目录
  • fsnotify_access&#xff0c;文件被读
  • fsnotify_modify&#xff0c;文件被写
  • fsnotify_open&#xff0c;文件被打开
  • fsnotify_close&#xff0c;文件被关闭
  • fsnotify_xattr&#xff0c;文件的扩展属性被修改
  • fsnotify_change&#xff0c;文件被修改或原数据被修改

 有一个例外情况&#xff0c;就是 inotify_unmount_inodes&#xff0c;它会在文件系统被 umount 时调用来通知 umount 事件给 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上.

欢迎各位大神指点 转载请注明来源:https://my.oschina.net/u/2291453/blog/833919


转:https://my.oschina.net/LinBigR/blog/833919



推荐阅读
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
author-avatar
上午劳动_951
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有