

📢本篇将详细介绍 Linux 中用 inotify 实现文件系统监控在内核中的实现思路。
不管是单个文件,还是某个目录,在 Linux 的文件系统上,都会对应一个索引节点(inode),所以,不管是在某个目录下新建或修改文件,或者修改某个文件对应的内容,都会执行 inode 信息的更新。所以,inode 是理想的文件系统事件的监测点。
在 inode 的数据结构中,有两个字段用来记录当前的文件事件观察者:
__u32 i_fsnotify_mask;//对当前节点感兴趣的所有事件掩码
structhlist_head i_fsnotify_marks;//监控当前节点的所有监控对象,fsnotify_mark 结构的链表
在执行 inotify_add_watch() 时,就是在上面的监控列表对象上追加或更新项目。
在 fsnotify_mark 结构中,比较重要的字段包括:
structfsnotify_mark{
__u32 mask;// 感兴趣的事件掩码
structfsnotify_group*group;//指向包含本监控项的监控对象
union{
structfsnotify_inode_mark i;//索引到相关的 inode
structfsnotify_vfsmount_mark m;
};
......
};
其中的 group 字段,就是在 inotify_init() 创建监控对象时生成的内核数据结构,其中包含了添加到这个监控对象上的所有监控项的列表、阻塞 read() 系统调用的读队列,以及产生的通知事件列表,等等。
structfsnotify_group{
structlist_head notification_list;//要发到用户空间的事件列表
wait_queue_head_t notification_waitq;//阻塞 read() 系统调用的等待队列
unsignedint q_len;//等待队列长度
unsignedint max_events;//队列中允许保存的事件的最大数量
atomic_t num_marks;//监控目标数量
structlist_head marks_list;//监控目标列表
......
};
用一个示例表示上面各个结构间的关系:

在这个例子中,有两个监控对象,分别创建了对应的内核对象结构:
其中 fsnotify_group1 只有一个监控目标 inode1,而 fsnotify_group2 有两个监控目标:inode1 和 inode2。
当 inode1 或 inode2 上发生了文件系统事件时,分别沿着绿色和蓝色的箭头就可以定位到需要唤醒并通知的阻塞 read()。
可以看到,某个 inode 被添加为监控目标后,当在该 inode 上发生任何文件事件时,是可以 O(1) 地定位到相关的阻塞 read(),从而向应用层发出事件通知的。
虽然如此,但是我们同时也能看到,每个监控对象和监控目标都需要分配相应的内核数据结构,这会占用内核空间的内存。
为了保护系统,Linux 对系统中的文件系统监控实例的数量,以及每个实例中可以监控的目标的数量都做了限制,实际的限制值可以在位于目录 /proc/sys/fs/inotify 下的几个文件中查看和设置,每个文件代表的意义见下说明。
max_user_instances:允许用 inotify_init() 创建的监控对象的最大数量,默认为 128。
max_user_watches:每个监控对象中,可以用 inotify_add_watch() 添加的监控目标的最大数量,默认为 8192。
max_queued_events:每个监控对象上,在调用 read() 之前能够排队的事件的最大数量,超出这个数量的事件会被丢弃,默认值为 16384。