本文转载自微信公众号「Linux内核那些事」,作者songsong001。转载本文请联系Linux内核那些事公众号。
创新互联从2013年开始,公司以成都网站设计、网站建设、系统开发、网络推广、文化传媒、企业宣传、平面广告设计等为主要业务,适用行业近百种。服务企业客户1000多家,涉及国内多个省份客户。拥有多年网站建设开发经验。为企业提供专业的网站建设、创意设计、宣传推广等服务。 通过专业的设计、独特的风格,为不同客户提供各种风格的特色服务。
鲁迅先生说过:程序 = 数据结构 + 算法
想想如果让我们来设计 inotify 应该如何实现呢?下面来分析一下:
在介绍 inotify 的实现前,我们先来了解下其原理。inotify 的原理如下:
当用户调用 read 或者 write 等系统调用对文件进行读写操作时,内核会把事件保存到 inotify_device 对象的事件队列中,然后唤醒等待 inotify 事件的进程。正所谓一图胜千言,所以我们通过下图来描述此过程:
从上图可知,当应用程序调用 read 函数读取文件的内容时,最终会调用 inotify_dev_queue_event 函数来触发事件,调用栈如下:
- read()
- └→ sys_read()
- └→ vfs_read()
- └→ fsnotify_access()
- └→ inotify_inode_queue_event()
- └→ inotify_dev_queue_event()
inotify_dev_queue_event 函数主要完成两个工作:
上面主要涉及到两个对象,inotify_device 和 inotify_kernel_event,我们先来介绍一下这两个对象的作用。
我们来看看这两个对象的定义。
内核使用 inotify_device 来管理 inotify 监听的对象和发生的事件,其定义如下:
- 1struct inotify_device {
- 2 wait_queue_head_t wq;
- 3 ...
- 4 struct list_head events;
- 5 ...
- 6 struct inotify_handle *ih;
- 7 unsigned int event_count;
- 8 unsigned int max_events;
- 9};
下面我们介绍一下各个字段的作用:
下图描述了 inotify_device 对象中两个比较重要的队列(等待队列 和 事件队列):
当事件队列中有数据时,就可以通过调用 read 函数来读取这些事件。
内核使用 inotify_kernel_event 对象来存储一个事件,其定义如下:
- struct inotify_kernel_event {
- struct inotify_event event;
- struct list_head list;
- char *name;
- };
可以看出,inotify_kernel_event 对象只是对 inotify_event 对象进行扩展而已,而我们在《监听风云 - inotify介绍》一文中已经介绍过 inotify_event 对象。
inotify_kernel_event 对象在 inotify_event 对象的基础上增加了 list 字段和 name 字段:
在 inotify_device 对象中,有个类型为 inotify_handle 的字段 ih,这个字段主要用来存储 inotify 监听的文件或目录。我们来看看 inotify_handle 对象的定义:
- struct inotify_handle {
- struct idr idr;
- ...
- struct list_head watches;
- ...
- const struct inotify_operations *in_ops;
- };
下面来介绍一下 inotify_handle 对象的各个字段作用:
内核使用 inotify_handle 来存储被监听的对象列表,那么被监听对象是个什么东西呢?内核中使用 inotify_watch 对象来表示一个被监听的对象。其定义如下:
- struct inotify_watch {
- struct list_head h_list;
- struct list_head i_list;
- ...
- struct inotify_handle *ih;
- struct inode *inode;
- __s32 wd;
- __u32 mask;
- };
下面介绍一下 inotify_watch 对象各个字段的作用:
现在,我们通过下图来描述一下 inotify_device、inotify_handle 和 inotify_watch 三者的关系:
上面我们把 inotify 功能涉及的所有数据结构都介绍了,有上面的基础,现在我们可以开始分析 inotify 功能的实现了。
1. inotify_init 函数
在《监听风云 - inotify介绍》一文中介绍过,要使用 inotify 功能,首先要调用 inotify_init 函数创建一个 inotify 的句柄,而 inotify_init 函数最终会调用内核函数 sys_inotify_init。我们来分析一下 sys_inotify_init 的实现:
- long sys_inotify_init(void)
- {
- struct inotify_device *dev;
- struct inotify_handle *ih;
- struct user_struct *user;
- struct file *filp;
- int fd, ret;
- // 1. 获取一个没用被占用的文件描述符
- fd = get_unused_fd();
- ...
- // 2. 获取一个文件对象
- filp = get_empty_filp();
- ...
- // 3. 创建一个 inotify_device 对象
- dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
- ...
- // 4. 创建一个 inotify_handle 对象
- ih = inotify_init(&inotify_user_ops);
- ...
- // 5. 把 inotify_handle 对象与 inotify_device 对象进行绑定
- dev->ih = ih;
- // 6. 设置文件对象的操作函数列表为:inotify_fops
- filp->f_op = &inotify_fops;
- ...
- // 7. 将 inotify_device 对象绑定到文件对象的 private_data 字段中
- filp->private_data = dev;
- ...
- // 8. 把文件句柄与文件对象进行映射
- fd_install(fd, filp);
- return fd;
- }
sys_inotify_init 函数主要完成以下几个工作:
从上面的实现可以看出,sys_inotify_init 函数主要是创建 inotify_device 对象和 inotify_handle 对象,并且将它们与文件对象关联起来。
另外需要注意的是,在 sys_inotify_init 函数中,还把文件对象的操作函数集设置为 inotify_fops,主要提供了 read 和 poll 等接口的实现,其定义如下:
- static const struct file_operations inotify_fops = {
- .poll = inotify_poll,
- .read = inotify_read,
- .release = inotify_release,
- ...
- };
所以,当调用 read 函数读取 inotify 的句柄时,就会触发调用 inotify_read 函数读取 inotify 事件队列中的事件。
2. inotify_add_watch 函数
当调用 inotify_init 函数创建好 inotify 句柄后,就可以通过调用 inotify_add_watch 函数向 inotify 句柄添加要监控的文件或目录。inotify_add_watch 函数的实现如下:
- long sys_inotify_add_watch(int fd, const char __user *path, u32 mask)
- {
- struct inode *inode;
- struct inotify_device *dev;
- struct nameidata nd;
- struct file *filp;
- int ret, fput_needed;
- unsigned flags = 0;
- // 通过文件句柄获取文件对象
- filp = fget_light(fd, &fput_needed);
- ...
- // 获取文件或目录对应的 inode 对象
- ret = find_inode(path, &nd, flags);
- ...
- inode = nd.dentry->d_inode;
- // 从文件对象的 private_data 字段获取对应的 inotify_device 对象
- dev = filp->private_data;
- ...
- // 创建一个新的 inotify_watch 对象
- if (ret == -ENOENT)
- ret = create_watch(dev, inode, mask);
- ...
- return ret;
- }
sys_inotify_add_watch 函数主要完成以下几个工作:
到了 inotify 最关键的部分,就是 inotify 的事件是怎么产生的。
在本文的第一部分中介绍过,当用户调用 read 系统调用读取文件内容时,最终会调用 inotify_dev_queue_event 函数来产生一个事件,我们先来回顾一下 read 系统调用的调用栈:
- read()
- └→ sys_read()
- └→ vfs_read()
- └→ fsnotify_access()
- └→ inotify_inode_queue_event()
- └→ inotify_dev_queue_event()
下面我们来分析一下 inotify_dev_queue_event 函数的实现:
- static void
- inotify_dev_queue_event(struct inotify_watch *w, u32 wd,
- u32 mask, u32 cookie, const char *name, struct inode *ignored)
- {
- struct inotify_user_watch *watch;
- struct inotify_device *dev;
- struct inotify_kernel_event *kevent, *last;
- watch = container_of(w, struct inotify_user_watch, wdata);
- dev = watch->dev;
- ...
- // 1. 申请一个 inotify_kernel_event 事件对象
- if (unlikely(dev->event_count == dev->max_events))
- kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL);
- else
- kevent = kernel_event(wd, mask, cookie, name);
- ...
- // 2. 增加 inotify 事件队列的计数器
- dev->event_count++;
- // 3. 增加 inotify 事件队列所占用的内存大小
- dev->queue_size += sizeof(struct inotify_event) + kevent->event.len;
- // 4. 把事件对象添加到 inotify 的事件队列中
- list_add_tail(&kevent->list, &dev->events);
- // 5. 唤醒正在等待读取事件的进程
- wake_up_interruptible(&dev->wq);
- ...
我们先来介绍一下 inotify_dev_queue_event 函数各个参数的意义:
inotify_dev_queue_event 函数主要完成以下几个工作:
从上面的分析可以看出,inotify_dev_queue_event 函数只负责创建一个事件对象,并且添加到 inotify 的事件队列中。但发生了什么事件是由哪个步骤指定的呢?
我们可以通过分析 read 系统调用的调用栈,会发现在 fsnotify_access 函数中指定了事件的类型,我们来看看 fsnotify_access 函数的实现:
- static inline void fsnotify_access(struct dentry *dentry)
- {
- struct inode *inode = dentry->d_inode;
- u32 mask = IN_ACCESS; // 指定事件类型为 IN_ACCESS
- if (S_ISDIR(inode->i_mode))
- mask |= IN_ISDIR; // 如果是目录, 增加 IN_ISDIR 标志
- ...
- // 创建事件
- inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
- }
从上面的分析可知,当发生读事件时,由 fsnotify_access 函数指定事件类型为 IN_ACCESS。在 include/linux/fsnotify.h 文件中还实现了其他事件的触发函数,有兴趣的可以自行查阅此文件 。
inotify 的实现过程总结为以下两点:
当用户调用读写、创建或删除文件的系统调用时,内核会注入相应的事件触发函数来产生一个事件,并且添加到 inotify 的事件队列中。
唤醒等待读取事件的进程,当进程被唤醒后,就可以通过调用 read 函数来读取 inotify 事件队列中的事件。
分享名称:监听风云|Inotify实现原理
网站地址:http://www.shufengxianlan.com/qtweb/news25/2525.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联