QT之QFileSystemWatcher使用

来源:互联网 发布:淘宝投诉假冒伪劣 编辑:程序博客网 时间:2024/05/21 23:32

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);//添加watcherint 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宏是否定义。

1 0
原创粉丝点击