3、既然iNotify这么牛x,它是怎么实现起来的?
- 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 列表,notify_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's list */
struct list_head i_list; /* entry in inode'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(fd) 时被释放。
再来看看inode 结构,系统在inode结构中增加了两个字段
#ifdef CONFIG_INOTIFY
struct 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 结构,并把它插入到被监视目标对应的 inode 的 inotify_watches 列表.
- inotify_sem 用于同步对 inotify_watches 列表的访问
- 当文件系统发生第一部分提到的事件之一时,相应的文件系统代码将显示调用fsnotify_* 来把相应的事件报告给 inotify 系统,其中*号就是相应的事件名,目前实现包括:
event | 描述 |
fsnotify_move | 文件从一个目录移动到另一个目录 |
fsnotify_nameremove | 文件从目录中删除 |
fsnotify_inoderemove | 自删除 |
fsnotify_create | 创建新文件 |
fsnotify_mkdir | 创建新目录 |
fsnotify_access | 文件被读 |
fsnotify_modify | 文件被写 |
fsnotify_open | 文件被打开 |
fsnotify_close | 文件被关闭 |
fsnotify_xattr | 文件的扩展属性被修改 |
fsnotify_change | 文件被修改或原数据被修改 |
- 有一个例外情况,就是 inotify_unmount_inodes,它会在文件系统被 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 上。
核心原理其实就是在inode上建立起一系列的监听机制。
简单介绍FileObserver
FileObserver其实就是基于iNotify机制,在java层和c层实现了些巧妙的封装,开放给用户接口调用:
private native int init();
private native void observe(int fd);
private native int startWatching(int fd, String path, int mask);
private native void stopWatching(int fd, int wfd);
怎么把FileObserver用好
1、不能递归监视---不能监视目录的子目录的变动。
2、被监听目录/文件在startwatching方法调用时,必须是已经创建好。
3、接受到事件时,不要对event做任何处理,需要将event post出去使用,推荐使用HandlerThread。
4、被监听的目录/文件在监控期间删除,FileObserver不会接受到任何事件,需要重新部署监控。
5、FileObserver维护一个静态ObserverThread,应用增加监听都是在同一thread处理,不会造成太多性能开销。
6、处理时避免事件循环操作。比如监听到open事件在处理这个事件过程中又触发open事件点。