目前比较通用的监控Linux下文件系统变化的是Epoll+iNotify结合的机制.
Epoll有两种机制,LevelTrigger和EdgeTrigger, 前者相当于fast poll, 后者可以理解为对nonblocking fd的阻塞化, 这个说法严格来讲有点儿业余,只是为了简单的说明问题.
对于从epoll_wait等待事件触发,然后进行read.这里做下说明,在读事件的时候, Linux下的epoll是异步读,而Windows下的IOCP是同步读,从后面的分析可以发现,同步读似乎更有优势.
开始的时候,对于wait事件发生并进行read的线程,并没有提高其优先级,发现在过于频繁的往目录下添加文件和目录的时候,会丢事件.这样在做实时同步时,要想办法弥补丢失的事件.
第一个方案是对新添加的目录,先把目录add_watch,然后把该目录扫描一遍.add_watch只是为了确保新建目录被加入watch,一般不会漏掉的,除非是在事件被漏掉的情况下.漏掉指的是在目录新建并上报到加入watch前的这段时间,在该目录下又发生了新建文件或目录时,会漏事件. iNotify是允许对同一个目录add_watch两遍的,但是由于add时还要访问硬盘,确保目录存在才能添加,所以做了一个缓存,path<--->wd,通过path的查找确定是否已add_watch,若没add再add下.
第一个方案里漏事件的问题通过事后扫描得到解决,但是再扫描一遍是否有意义呢? 分析了Linux的线程有限级和调度策略之后,发现实时优先级有两种SCHED_FIFO和SCHED_RR.从Linux kernel development里chapter4 RealTime看到:
“SCHED_RR is identical to SCHED_FIFO except that each process can run only until it exhausts a predetermined timeslice. That is, SCHED_RR is SCHED_FIFO with timeslicesit is a real-time round-robin scheduling algorithm. When a SCHED_RR task exhausts its timeslice, any other real-time processes at its priority are scheduled round robin. The timeslice is used only to allow rescheduling of same-priority processes.”
这也就是说,若是把读事件的线程设置为FIFO,则没有timeslice的限制,可以直到读完事件,并且不会被抢占;而用RR则在timeslice用完一个之后,就会调度到别的线程,觉得可以用FIFO的设置来替代低效的扫描。
注1:
设置线程策略可以用下面的api:
- pthread_attr_getschedpolicy(const pthread_attr_t *restrict attr, int *restrict policy);
- pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy );
设置线程优先级可以用下面的api:
- int sched_get_priority_max(int policy);
- int sched_get_priority_min(int policy);
- int pthread_attr_getschedparam(const pthread_attr_t *restrict attr,
- struct sched_param *restrict param);
- int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
- const struct sched_param *restrict param);
- struct sched_param sched;
- sched.sched_priority = sched_get_priority_max(SCHED_FIFO);
- pthread_attr_setschedparam( attr, &sched );
第二个方案的产生是因为在实现了第一个方案之后,发现多CPU或多core的情况下,仍然有漏报事件发生,而且漏报无规律可循,文件或目录的个数不定,.再做了多种模拟测试之后,发现在单CPU下,读事件是没有问题的,但是在多CPU下,读事件的线程是分配了core的个数个的.这时会发现读到的事件不是一般的乱,而且无规律可循.在没有任何分析的情况下,我假定inotify会对他内部的RB-Tree的读取做了同步的,所以为了效率就肆无忌惮的用多个线程去读了,结果在多U下,就给读乱了.在把读取线程改为一个后,就没有漏事件了;但是考虑到效率问题,还是要用多个线程去读的.或者可以对每个线程的read进行加锁,这样就可以保证读的时候,在iNotify的buffer里是同步的了.具体效果如何,还有待明天验证;
在这里插一句,Windows的iocp是同步读的,是先read然后再getiocpstatus的,Linux的Epoll是反过来的,是异步读取的.个人觉得可能是ms已经对异步读的方式进行了测试,最后选定了同步读的方式,这样对写应用的人来说,是要省不少心的.而且还有一点,inotify没有加迭代监控子目录的参数,而Windows却有了这种考虑,这一点也算是win在设计上考虑比较全面的地方吧.