文中摘自微信公众平台「Linux内核那些事情」,创作者songsong001。转截文中请联络Linux内核那些事情微信公众号。
关键的算法设计
鲁迅说过:程序流程 = 算法设计 优化算法
想一想假如使我们设计制作 inotify 应当怎么完成呢?下边来剖析一下:
- 我们知道,inotify 是用于监管文档或列表的变化事件,因此应当界定一个目标来储存被窃听的文档或文件目录列表和他们所出现的事件列表(在内核中界定了 inotify_device 目标来储存被窃听的文档列表和事件列表)。
- 此外,当想被窃听的文档或文件目录开展读写操作的时候会开启相对应的事件造成。因此,应当在读写操作有关的系统软件调用中置入造成事件的姿势(在内核中由 inotify_dev_queue_event 函数公式造成事件)。
在详细介绍 inotify 的完成前,大家先来知晓下其原理。inotify 的原理如下所示:
当客户调用 read 或是 write 等系统软件调用对资料开展读写操作时,内核会把事件储存到 inotify_device 目标的事件序列中,随后唤起等候 inotify 事件的过程。正所谓一图胜万言,因此人们根据下面来叙述此全过程:
从图中得知,当应用软件调用 read 函数公式读取文件的信息时,最后会调用 inotify_dev_queue_event 函数公式来开启事件,调用栈如下所示:
inotify_dev_queue_event 函数公式关键进行2个工作中:
- 建立一个表明事件的 inotify_kernel_event 目标,而且把其插进到 inotify_device 目标的 events 列表中。
- 唤起已经等候 inotify 产生事件的过程,等候的过程置放在 inotify_device 目标的 wq 字段名中。
上边关键牵涉到2个目标,inotify_device 和 inotify_kernel_event,大家先来介绍一下这两个目标的功效。
- inotify_device:内核应用此目标来叙述一个 inotify,是 inotify 的关键目标。
- intoify_kernel_event:内核应用此目标来叙述一个事件。
大家一起来看看这两个目标的界定。
1. inotify_device目标
内核应用 inotify_device 来管理方法 inotify 监视的另一半和产生的事件,其界定如下所示:
下边大家介绍一下每个字符的功效:
- wq:已经等候现阶段 inotify 产生事件的过程列表。
- events:储存由 inotify 监视的文档或文件目录所出现的事件。
- ih:内核用于储存 inotify 监视的文档或文件目录,下边会详细介绍。
- event_count:inotify 监视的文档或文件目录所出现的事件总数。
- max_events:inotify 可以储存较大的事件总数。
下面的图叙述了 inotify_device 目标中2个较为主要的序列(等候序列 和 事件队列):
当事件序列中有数据信息时,就可以根据调用 read 函数公式来载入这种事件。
2. inotify_kernel_event目标
内核应用 inotify_kernel_event 目标来储存一个事件,其界定如下所示:
可以看得出,inotify_kernel_event 目标仅仅对 inotify_event 目标开展拓展罢了,而我们在《监听风云 - inotify介绍》一文中己经详细介绍过 inotify_event 目标。
inotify_kernel_event 对象在 inotify_event 目标的根基上提升了 list 字段名和 name 字段:
- list:用以把全部由 inotify 监视的文档或文件目录所出现的事件相互连接,
- name:用以纪录产生事件的文件夹名称或路径名。
3. inotify_handle目标
在 inotify_device 对象中,有一个种类为 inotify_handle 的字段名 ih,这一字段名关键用于储存 inotify 监视的文档或文件目录。大家一起来看看 inotify_handle 目标的界定:
下边来介绍一下 inotify_handle 目标的每个字段名功效:
- idr:ID制作器,用以转化成被监视目标(文档或文件目录)的ID。
- watches:inotify 监视的目标(文档或文件目录)列表。
- in_ops:当事件产生时,被 inotify 调整的函数公式列表。
4. inotify_watch目标
内核应用 inotify_handle 来储存被窃听的目标列表,那麼被监视目标是个什么呢?内核中应用 inotify_watch 目标来表明一个被监视的目标。其界定如下所示:
下边介绍一下 inotify_watch 目标每个字符的功效:
- h_list:用以把归属于同一个 inotify 监视的对象连接起來。
- i_list:因为同一个文档或文件目录可以被好几个 inotify 监视,因此应用此字段名来把全部监视同一个文档的 inotify_handle 对象连接起來。
- ih:偏向其归属的 inotify_handle 目标。
- inode:因为在 Linux 内核中,每一个文档或文件目录都由一个 inode 目标来叙述,这一字段名便是偏向被窃听的文档或列表的 inode 目标。
- wd:被监视目标的ID(或称之为描述符)。
- mask:被窃听的事件种类(在《监听风云 - inotify介绍》一文中早已介绍)。
如今,大家根据下面来叙述一下 inotify_device、inotify_handle 和 inotify_watch 三者的关联:
inotify功能完成
上边大家把 inotify 功能涉及到的全部算法设计都介绍了,有上边的基本,如今我们可以逐渐剖析 inotify 功能的达到了。
1. inotify_init 函数
在《监听风云 - inotify介绍》一文中介绍过,要应用 inotify 功能,最先要启用 inotify_init 函数创建一个 inotify 的句柄,而 inotify_init 函数最后会启用核心函数 sys_inotify_init。大家来剖析一下 sys_inotify_init 的完成:
sys_inotify_init 函数关键进行下面这几个工作中:
- 启用 get_unused_fd 函数从过程中获得一个没被采用的文件描述符(句柄)。
- 启用 get_empty_filp 获得一个文档对象。
- 启用 kmalloc 函数申请办理一个 inotify_device 对象。
- 启用 inotify_init 函数创建并复位一个 inotify_handle 对象。
- 把 inotify_handle 对象与 inotify_device 对象开展关联。
- 设定文档对象的实际操作函数目录为:inotify_fops,关键给予 read 和 poll 等插口的完成。
- 将 inotify_device 对象关联到文档对象的 private_data 字段名中。
- 把文件描述符与文档对象开展投射。
- 回到文件描述符给网络层。
从以上的建立可以看得出,sys_inotify_init 函数主要是创建 inotify_device 对象和 inotify_handle 对象,而且将他们与文档对象关系起來。
此外要留意的是,在 sys_inotify_init 函数中,还把文档对象的实际操作函数集设定为 inotify_fops,关键带来了 read 和 poll 等插口的完成,其界定如下所示:
因此,当启用 read 函数载入 inotify 的句柄时,便会开启启用 inotify_read 函数载入 inotify 事件队列中的事情。
2. inotify_add_watch 函数
当启用 inotify_init 函数创建好 inotify 句柄后,就可以根据启用 inotify_add_watch 函数向 inotify 句柄加上要监管的文档或文件目录。inotify_add_watch 函数的完成如下所示:
sys_inotify_add_watch 函数关键进行下面这几个工作中:
- 启用 fget_light 函数获得 inotify 句柄相匹配的文档对象。
- 启用 find_inode 函数获得 path 途径相对应的 inode 对象,也就是获得要监视的文档或文件目录所相应的 inode 对象。
- 从 inotify 文档对象的 private_data 字段名中,获得相应的 inotify_device 对象。
- 调用 create_watch 函数公式建立一个新的 inotify_watch 对象,而且把这个 inotify_watch 对象加上到 inotify_handle 对象的 watches 列表和 inode 对象的 inotify_watches 列表中。
事件通告
到了 inotify 最重要的一部分,便是 inotify 的事件是怎么发生的。
在文中的第一部分中详细介绍过,当客户调用 read 系统软件调用读取文件內容时,最后会调用 inotify_dev_queue_event 函数公式来造成一个事件,大家先来总结一下 read 系统软件调用的调用栈:
下边大家来剖析一下 inotify_dev_queue_event 函数公式的完成:
大家先来介绍一下 inotify_dev_queue_event 函数公式每个主要参数的实际意义:
- w:被监视对象,用以叙述被窃听的文档或文件目录。
- wd:被监视对象的ID。
- mask:产生的事件种类,可以参照《监听风云 - inotify介绍》一文。
- cookie:较为少应用,忽视。
- name:产生事件的文档或文件目录名字。
- ignored:产生事件的文档或列表的 inode 对象,在本函数公式中并没有应用。
inotify_dev_queue_event 函数公式关键进行下面这几个工作中:
- 根据调用 kernel_event 函数公式申请办理一个 inotify_kernel_event 事件对象。
- 提升 inotify 事件序列的计数。
- 提升 inotify 事件序列所占有的内存空间。
- 把第一步建立的事件对象加上到 inotify 的事件序列中。
- 唤起已经等候载入事件的过程(由于早已有事件发生了)。
从以上的研究可以看得出,inotify_dev_queue_event 函数公式只承担建立一个事件对象,而且加上到 inotify 的事件序列中。但发生什么事事件是由哪个流程特定的呢?
我们可以根据剖析 read 系统软件调用的调用栈,会看到在 fsnotify_access 函数公式中特定了事件的种类,大家一起来看看 fsnotify_access 函数公式的完成:
从以上的研究得知,当产生读事件时,由 fsnotify_access 函数公式特定事件种类为 IN_ACCESS。在 include/linux/fsnotify.h 文档中还建立了别的事件的开启函数公式,有感兴趣的可以自主查看此文档 。
汇总
inotify 的建立全过程汇总为下列二点:
当客户调用读写能力、建立或删除文件夹的系统软件调用时,核心会引入对应的事件开启函数公式来造成一个事件,而且加上到 inotify 的事件序列中。
唤起等候载入事件的过程,当进程被叫醒后,就可以根据调用 read 函数公式来载入 inotify 事件序列中的事件。