QT之QFileSystemWatcher使用

  • Post author:
  • Post category:其他


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宏是否定义。



版权声明:本文为king523103原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。