QFileSystemWatcher
QFileSystemWatcher提供了对文件系统监控的接口,一般使用方法:
添加监视
QFileSystemWatcher watcher;
watcher.addPath(“文件路径“)
watcher.addPath(“文件夹路径”)
connect(&watcher,SIGNAL(fileChanged()),this,SLOT(处理函数))
connect(&watcher,SIGNAL(directoryChanged()),this,SLOT(处理函数))
取消监控
watcher.removePath(“文件路径”)
watcher.removePath(“文件夹路径”)
使用中遇到了一个问题:我的sd卡挂载在/mnt/mmc,需要监控/mnt/mmc/photo文件夹是否有变化。当需要格式化sd卡时,我需要取消监控、卸载,然后格式化。不过当我卸载时系统出现device is busy。使用lsof工具发现/mnt/mmc居然还被打开着。
需要弄清楚这个问题,需要了解QFileSystemWatcher的实现。
实现
先看看corelib/io/qfilesystemwatcher.cpp,注意到下面代码:
QFileSystemWatcherEngine* QFileSystemWatcherPrivate::createNativeEngine()
{
QFileSysetmWatcherEngine *eng=QInotifyFileSystemWatcherEngine::create();
if(!eng)
eng=QDnotifyFileSystemWatcherEngine::create();
return eng;
}
QT喜欢使用Engine类的字样进行逻辑封装,我们在上面看到了两个类:QInotifyFileSystemWatcherEngine和QDnotifyFileSystemWatcherEngine。
先看QDnotifyFileSystemWatcherEngine,再看QInotifyFileSystemWatcherEngine。
QDnotifyFileSystemWatcherEngine
实际上相关类包括:QDnotifyFileSystemWatcherEngine,QDnotifySignalThread。(很奇怪,居然没有QDnotifyFileSystemWatcherEnginePrivate,因为不需要存储自己的数据)
注意到源码中声明了静态变量 static QDnotifySignalThread* dnotifySignal;
整个程序的逻辑是:
1、QDnotifySignalThread建立管道qfswd_fileChanged_pip
2、QDnotifySignalThread监控系统发出的SIGIO信号,并指定处理函数qfswd_sigio_monitor;
3、qfswd_sigio_monitor在检测到系统的SIGIO信号时,向管道qfswd_fileChanged_pip[1]中写入发生变化的IO句柄
4、QDnotifySignalThread读取到管道qfswd_fileChanged_pip[0]的信息后,发出fdChanged信号
5、QDnotifyFileSystemWatcherEngine接收到fdChanged,调用refresh处理发送的参数fd文件句柄
现在问题来了:QDnotifySignalThread监控的是整个系统的IO活动,而我们只想知道我们需要的某个文件或者文件夹是否发生变化。例如:监控/mnt/usb。
::pipe(qfswd_fileChanged_pipi);//建立管道
::fcntl(qfswd_fileChanged_pipi[0],F_SETFL,::fctnl(qfswd_fileChanged_pipe[0],F_GETFL)|O_NONBLOCK);//读管道非阻塞
struct sigaction oldAction;
struct sigaction action;
memset(&action,0,sizeof(action));
action.sa_sigaction=afswd_sigio_monitor;
action.sa_flags=SA_SIGINFO;
::sigaction(SIGIO,&action,&oldAction); //监控SIGIO信号
if(!(oldAction.sa_flags&SA_SIGINFO))
qfswd_old_sigio_handler=oldAction.sa_handler;
else
qfswd_old_sigio_action=oldAction.sa_sigaction;
static void qfswd_sigio_monitor(int signum,siginfo_t* i,void* v)
{
::write(qfswd_fielChanged_pipe[1],&i->si_fd,sizeof(int));
//别忘了默认的处理函数的调用
if(qfswd_old_sigio_handler&&qfswd_old_sigio_handler!=SIG_IGN)
qfswd_old_sigio_handler(signum);
if(qfswd_old_sigio_action)
qfswd_old_sigio_action(signum,i,v);
}
QDnotifyFileSystemWatcherEngine的接口addPaths中,addPaths接受的参数是文件名这样的字符串数据,而QDnotifySignalThread传递过来的是fd这样的文件句柄。因此,在addPaths的时候,希望能够从fd能够很快得到对于的文件夹路径,而从文件夹路径信息能够很快得到fd信息。这些逻辑阅读过程中,发现了最开始遇到的问题。
addPaths
对于SD卡挂载的路径/mnt/mmc下的photo文件夹,addPaths(“/mnt/mmc/photo”)中,建立了pathToFD、fdToDirectory、parentToFD三个哈希查询表。其中fdToDirectory中使用fd文件句柄作为关键字,可以查询到Directory这个自定义结构。
Directory包含三个变量:path(路径字符串)、fd(文件句柄)、parentFd(父文件夹文件句柄)。fd和parentFd是例子中/mnt/mmc/photo和/mnt/mmc文件夹的文件打开句柄,也就是说,这里打开了检测的文件夹句柄和检测的文件夹的父文件夹句柄。
removePaths
对于removePaths(“/mnt/mmc/photo”)的调用,按道理需要从fdToDirectory中找到对应的/mnt/mmc/photo和/mnt/mmc文件夹的文件打开句柄,然后分别关闭它们。不过可惜的是,代码中居然忘记关闭/mnt/mmc这样的父文件夹句柄,从而导致最开始的错误。
问题解决
不修改代码的前提下,最简单的解决办法是析构掉QFileSystemWatcher,这样会调用QDnotifyFileSystemWatcherEngine的析构函数,在析构函数中会将所有的fdToDirectory中的文件夹和父文件夹句柄都关闭。
QInotifyFileSystemWatcherEngine
如果Linux支持inotify的话,QT会使用QInotifyFileSystemWatcherEngine进行文件系统监控。
inotify是一种高效的文件系统变化通知框架
一般来说,文件系统变化监控最简单的方式是使用轮训(QT里面的对应实现是QPollingFileSystemWatcherEngine)。不过轮训的粒度会影响监控的效果,例如:1秒钟文件变化3次,那么可能只有第3次能够被检测到。
inotify是为了替代dnotify(对应QDnotifyFileSystemWatcherEngine,它的缺点是需要打开每个监控文件,若监控文件很多?那就不行了)
inotify实际上是一种内核实现的框架,特点是:
1. Inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
2. Inotify 既可以监视文件,也可以监视目录。
3. Inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
4. Inotify 使用文件描述符作为接口,因而可以使用通常的文件 I/O 操作select 和 poll 来监视文件系统的变化。
Inotify 可以监视的文件系统事件包括:
IN_ACCESS,即文件被访问
IN_MODIFY,文件被 write
IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等
IN_CLOSE_WRITE,可写文件被 close
IN_CLOSE_NOWRITE,不可写文件被 close
IN_OPEN,文件被 open
IN_MOVED_FROM,文件被移走,如 mv
IN_MOVED_TO,文件被移来,如 mv、cp
IN_CREATE,创建新文件
IN_DELETE,文件被删除,如 rm
IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己
IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己
IN_UNMOUNT,宿主文件系统被 umount
IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)
注:上面所说的文件也包括目录。
inotify接口
#include <sys/inotify.h>
int fd=inotify_init(); //每个inotify对应一个排序队列
int wd = inotify_add_watch (fd, path, mask);//添加watcher
int ret = inotify_rm_watch (fd, wd);//删除watcher
//循环读取事件
while(1)
{
size_t len = read (fd, buf, BUF_LEN);
//读取fd的内容是一个inotify_event 结构
/*
struct inotify_event {
__s32 wd; /* watch descriptor */
__u32 mask; /* watch mask */
__u32 cookie; /* cookie to synchronize two events */
__u32 len; /* length (including nulls) of name */
char name[0]; /* stub for possible name */
};
*/
}
查看是否支持inotify,只需要查看QT_NO_INOTIFY宏是否定义。