Qt的事件驱动机制与eventfd

版权声明:转载请注明来源: https://blog.csdn.net/imred/article/details/82318988

简介

Qt是一个事件驱动的GUI框架,那么,这个“事件驱动”说的是什么呢?以我的理解就是:对于UI线程,除了初始化代码和主循环本身之外,跑在CPU上的每条指令,要么是为了接收事件,要么就是某个事件触发的,这个事件可以直接来源于用户操作,也可以间接来源于用户操作(处理用户操作事件时触发了需要异步处理的其他事件),或者来源于socket,或者来源于定时器,等等。

使用事件驱动能够避免对CPU时间的浪费,只有事件来到时,才进行相应处理,否则就休眠;而不是进行轮询,处在忙等待的状态,白白占用CPU时间。

那么Qt的事件驱动是如何实现的呢?在Linux系统下,如果编译Qt库源码时,系统中有glib可用,Qt默认会使用glib的GMainContext和GSource这一套东西的事件驱动机制。那么glib又是如何实现事件驱动机制的呢?答案是eventfd。下面我将会结合Qt和glib的源码和eventfd的手册来讲解Qt的事件驱动机制,当然上面所述内容可能会因为软件版本的不同而显得超前或过时,所以我把博主目前的软件环境放在下面(当然都是比较新的):

软件环境

操作系统:Ununtu 18.04

内核版本:4.15.0

Qt版本:5.10.0

glib版本:2.56.1

eventfd

由于Qt的事件驱动机制的底层的核心是eventfd,所以我们首先了解一下eventfd。关于eventfd详细内容可以直接man eventfd查看,此处只做粗略介绍。

概括的说,eventfd能够被用户空间程序用作一种等待/通知机制。eventfd和信号量很像,信号量有3个核心的操作或属性:信号量值、P操作和V操作,对应到eventfd就是:计数器值、read操作和write操作。信号量值/计数器值都代表着某种资源的可用性状态,P操作/read操作都是为了尝试使用资源,减小信号量值/计数器值,无法使用则被阻塞,V操作/write操作都是为了使资源可用,增大信号量值/计数器值。实际上在创建eventfd时,确实有一个选项EFD_SEMAPHORE,这个选项会改变write操作的语义,使eventfd表现得更像一个信号量。

在使用int eventfd(unsigned int initval, int flags)创建eventfd时,需要指定计数器值的初值initval,计数器值的作用是指示当前eventfd处在何种状态(可读/可写/可读+可写)。eventfd看名字就知道,实际上就是一个文件描述符,因此能够使用对一般文件描述符使用的read/write/poll操作,只是这些操作的语义不同于普通文件的文件描述符。由于flags中的EFD_SEMAPHORE选项会影响read操作的语义,而glib使用eventfd时没有指定这个选项,因此以下对于read操作的描述不考虑这个选项的影响。

read

read操作需要传入一个至少8字节的缓冲区,用于返回计数器值,如果不足8字节会直接出错返回。

当计数器为0时:若创建flags时未指定EFD_NONBLOCK则阻塞,阻塞到计数器被write操作改变为止;若指定了EFD_NONBLOCK,则出错返回。

当计数器非0时:将8字节的计数器值写到传入的缓冲区中,同时将计数器值置0。

write

计数器值最大为0xfffffffffffffffe,write操作会将传入的缓冲区中的8字节数据加到计数器值上,如果这会导致计数器值超过上限或溢出,则write操作被阻塞(未指定EFD_NONBLOCK),或出错返回(指定了EFD_NONBLOCK)。另外,若传入write的缓冲区大小小于8字节,或数据为0xffffffffffffffff,则直接出错返回。

poll

使用poll监听eventfd时,若计数器值大于0,则返回可读(POLLIN被置位);若计数器值小于上限值,则返回可写(POLLOUT被置位)。

Qt和glib的事件驱动机制

了解了eventfd的基本使用以后,我们就可以研究Qt和glib是如何使用它的了。

在阅读别人的源码时,想要快速的理解其核心流程,让源码跑起来是很重要。通过观察在主流程中哪些函数会跑起来,哪些数据会被访问,可以避免迷失在异常处理代码或花哨功能代码的汪洋大海中。

以下实验需要使用两台设备完成,一台运行Qt程序,一台设备远程连上跑Qt的设备使用gdb调试。如果只有一台设备的话,在调试时键盘和鼠标事件可能会被发送给Qt程序造成干扰,导致现象与我描述的不一致。

首先写一个简单的Qt程序,名字叫做simple,只是一个空白窗口:

没有事件时,程序阻塞在何处?

使用gdb跑起来,待界面显示出来后,中断,查看堆栈:

^C
Thread 1 "simple" received signal SIGINT, Interrupt.
[Switching to Thread 0x7ffff7fc8bc0 (LWP 22621)]
0x00007ffff68fcbf9 in __GI___poll (fds=0x5555558fef20, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29  ../sysdeps/unix/sysv/linux/poll.c: No such file or directory.
(gdb) bt
#0  0x00007ffff68fcbf9 in __GI___poll (fds=0x5555558fef20, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
#1  0x00007ffff551f5a9 in g_main_context_poll (priority=<optimized out>, n_fds=1, fds=0x5555558fef20, timeout=<optimized out>, context=0x7fffec004ff0) at gmain.c:4204
#2  g_main_context_iterate (context=context@entry=0x7fffec004ff0, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>) at gmain.c:3898
#3  0x00007ffff551f6bc in g_main_context_iteration (context=0x7fffec004ff0, may_block=may_block@entry=1) at gmain.c:3964
#4  0x00007ffff747d54f in QEventDispatcherGlib::processEvents (this=0x5555557a1570, flags=...) at kernel/qeventdispatcher_glib.cpp:423
#5  0x00007ffff3260411 in QPAEventDispatcherGlib::processEvents (this=0x5555557a1570, flags=...) at qeventdispatcher_glib.cpp:69
#6  0x00007ffff7420fea in QEventLoop::exec (this=this@entry=0x7fffffffdc80, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
#7  0x00007ffff742a224 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1332
#8  0x0000555555556804 in main (argc=1, argv=0x7fffffffde38) at ../simple/main.cpp:10

正在poll一个文件描述符,这个文件描述符是什么呢?我们来看一下:

(gdb) p *fds
$1 = {fd = 5, events = 1, revents = 0}

文件描述符值为5,且对POLLIN(也就是1)进行了置位,监听可读性状态,查看一下对应的是什么文件:

$ readlink /proc/$(pidof simple)/fd/5
anon_inode:[eventfd]

正是一个eventfd。如果此时在gdb中使用finish命令,你会发现程序一直都不会从poll函数中返回,而是阻塞在了这里。那么poll函数何时会返回呢?这个问题一会儿在说,先观察一下堆栈。

查看#6 QEventLoop::exec的代码:

int QEventLoop::exec(ProcessEventsFlags flags)
{
    ......
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);
    ......
}

明显是主循环,当exit变量不为0时,则一直循环下去。每次循环都运行一次QPAEventDispatcherGlib::processEvents:

bool QPAEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    m_flags = flags;
    const bool didSendEvents = QEventDispatcherGlib::processEvents(m_flags);
    return QWindowSystemInterface::sendWindowSystemEvents(m_flags) || didSendEvents;
}

在调用完QEventDispatcherGlib::processEvents后会调用QWindowSystemInterface::sendWindowSystemEvents,但是现在阻塞在QEventDispatcherGlib::processEvents里面了:

bool QEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    ......
    bool result = g_main_context_iteration(d->mainContext, canWait);
    ......
}

最终调用到了glib的g_main_context_iterate,查看一下源码:

static gboolean
g_main_context_iterate (GMainContext *context,
            gboolean      block,
            gboolean      dispatch,
            GThread      *self)
{
  ......
  g_main_context_poll (context, timeout, max_priority, fds, nfds);

  some_ready = g_main_context_check (context, max_priority, fds, nfds);

  if (dispatch)
    g_main_context_dispatch (context);
  ......
}

有3个重要的调用,g_main_context_poll、g_main_context_check和g_main_context_dispatch,其中g_main_context_poll我们已经在堆栈中见到了,源码如下,其唯一的作用是监听eventfd,剩下两个函数作用后面会说:

static void
g_main_context_poll (GMainContext *context,
             gint          timeout,
             gint          priority,
             GPollFD      *fds,
             gint          n_fds)
{
  ......
  GPollFunc poll_func;

  if (n_fds || timeout != 0)
    {
  ......
      poll_func = context->poll_func;

  ......
      ret = (*poll_func) (fds, n_fds, timeout);
  ......
    } /* if (n_fds || timeout != 0) */
}

接下来干什么呢?我们来看一下Qt程序的事件响应函数是如何在事件到达后是如何被调用的。

用户操作事件产生后,事件响应函数如何被调用?

给QWidget的鼠标按下事件的响应函数打个断点:

(gdb) b QWidget::mousePressEvent(QMouseEvent*) 
Breakpoint 5 at 0x7ffff790d420: file kernel/qwidget.cpp, line 9391.

点击Qt界面,命中断点后查看堆栈:

#0  QWidget::mousePressEvent (this=0x7fffffffdd00, event=0x7fffffffd610) at kernel/qwidget.cpp:9391
#1  0x00007ffff79121af in QWidget::event (this=0x7fffffffdd00, event=0x7fffffffd610) at kernel/qwidget.cpp:8813
#2  0x00007ffff78d2c5c in QApplicationPrivate::notify_helper (this=this@entry=0x555555771d80, receiver=receiver@entry=0x7fffffffdd00, e=e@entry=0x7fffffffd610)
    at kernel/qapplication.cpp:3732
#3  0x00007ffff78da96f in QApplication::notify (this=<optimized out>, receiver=0x7fffffffdd00, e=0x7fffffffd610) at kernel/qapplication.cpp:3208
#4  0x00007ffff7422da8 in QCoreApplication::notifyInternal2 (receiver=receiver@entry=0x7fffffffdd00, event=event@entry=0x7fffffffd610)
    at kernel/qcoreapplication.cpp:1044
#5  0x00007ffff78d9942 in QCoreApplication::sendEvent (event=<optimized out>, receiver=<optimized out>)
    at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:234
#6  QApplicationPrivate::sendMouseEvent (receiver=receiver@entry=0x7fffffffdd00, event=event@entry=0x7fffffffd610, alienWidget=0x0, alienWidget@entry=0x7fffffffdd00, 
    nativeWidget=0x7fffffffdd00, buttonDown=buttonDown@entry=0x7ffff7dd3c70 <qt_button_down>, lastMouseReceiver=..., spontaneous=true) at kernel/qapplication.cpp:2711
#7  0x00007ffff792c663 in QWidgetWindow::handleMouseEvent (this=this@entry=0x555555777cb0, event=event@entry=0x7fffffffda10) at kernel/qwidgetwindow.cpp:654
#8  0x00007ffff792ec99 in QWidgetWindow::event (this=0x555555777cb0, event=0x7fffffffda10) at kernel/qwidgetwindow.cpp:273
#9  0x00007ffff78d2c5c in QApplicationPrivate::notify_helper (this=this@entry=0x555555771d80, receiver=receiver@entry=0x555555777cb0, e=e@entry=0x7fffffffda10)
    at kernel/qapplication.cpp:3732
#10 0x00007ffff78da414 in QApplication::notify (this=0x7fffffffdcf0, receiver=0x555555777cb0, e=0x7fffffffda10) at kernel/qapplication.cpp:3491
#11 0x00007ffff7422da8 in QCoreApplication::notifyInternal2 (receiver=receiver@entry=0x555555777cb0, event=event@entry=0x7fffffffda10)
    at kernel/qcoreapplication.cpp:1044
#12 0x00007ffff62caa03 in QCoreApplication::sendSpontaneousEvent (event=0x7fffffffda10, receiver=0x555555777cb0)
    at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:237
#13 QGuiApplicationPrivate::processMouseEvent (e=0x5555558fec60) at kernel/qguiapplication.cpp:1957
#14 0x00007ffff62cc4d5 in QGuiApplicationPrivate::processWindowSystemEvent (e=e@entry=0x5555558fec60) at kernel/qguiapplication.cpp:1741
#15 0x00007ffff62a4b1b in QWindowSystemInterface::sendWindowSystemEvents (flags=...) at kernel/qwindowsysteminterface.cpp:976
#16 0x00007ffff326041b in QPAEventDispatcherGlib::processEvents (this=0x5555557a0540, flags=...) at qeventdispatcher_glib.cpp:70
#17 0x00007ffff7420fea in QEventLoop::exec (this=this@entry=0x7fffffffdc80, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
#18 0x00007ffff742a224 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1332
#19 0x00005555555566b4 in main (argc=1, argv=0x7fffffffde38) at ../simple/main.cpp:10

和前面的堆栈对比一下,从下往上看,从第5个起就不一样了,前面被阻塞在了QEventDispatcherGlib::processEvents,而现在则已经返回,进入了QWindowSystemInterface::sendWindowSystemEvents:

bool QPAEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    m_flags = flags;
    const bool didSendEvents = QEventDispatcherGlib::processEvents(m_flags);
    return QWindowSystemInterface::sendWindowSystemEvents(m_flags) || didSendEvents;
}

看一下QWindowSystemInterface::sendWindowSystemEvents函数:

bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
    int nevents = 0;

    while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {
        QWindowSystemInterfacePrivate::WindowSystemEvent *event =
            (flags & QEventLoop::ExcludeUserInputEvents) ?
                QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :
                QWindowSystemInterfacePrivate::getWindowSystemEvent();
        if (!event)
            break;

        if (QWindowSystemInterfacePrivate::eventHandler) {
            if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))
                nevents++;
        } else {
            nevents++;
            QGuiApplicationPrivate::processWindowSystemEvent(event);
        }

        // Record the accepted state for the processed event
        // (excluding flush events). This state can then be
        // returned by flushWindowSystemEvents().
        if (event->type != QWindowSystemInterfacePrivate::FlushEvents)
            QWindowSystemInterfacePrivate::eventAccepted.store(event->eventAccepted);

        delete event;
    }

    return (nevents > 0);
}

当窗口系统事件队列不为空时,调用QWindowSystemInterfacePrivate::getWindowSystemEvent()从窗口系统事件队列中取出了一个事件,然后进行处理。这个窗口系统事件队列实际上就是用户操作事件队列,键盘鼠标事件都是由窗口系统发送给Qt的,属于窗口系统事件。

也就是说在结束监听eventfd造成的阻塞状态后,就直接进入了响应用户操作事件的处理流程中。
那现在我们来看一下程序是如何结束监听eventfd造成的阻塞状态的。

何时结束阻塞状态?

想要知道何时结束阻塞状态,监听对eventfd的write操作就可以:

(gdb) b __libc_write if fd == 5
Breakpoint 4 at 0x7ffff5fb9270: __libc_write. (3 locations)

然后点击Qt程序,断点命中后查看堆栈:

[Switching to Thread 0x7ffff1541700 (LWP 3868)]

Thread 2 "QXcbEventReader" hit Breakpoint 4, __GI___libc_write (fd=5, buf=buf@entry=0x7ffff1540c90, nbytes=nbytes@entry=8) at ../sysdeps/unix/sysv/linux/write.c:27
27  in ../sysdeps/unix/sysv/linux/write.c
(gdb) bt
#0  __GI___libc_write (fd=5, buf=buf@entry=0x7ffff1540c90, nbytes=nbytes@entry=8) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff5563d5a in g_wakeup_signal (wakeup=0x7fffec001530) at gwakeup.c:239
#2  0x00007ffff74266b0 in QCoreApplication::postEvent (receiver=0x555555780e80, event=event@entry=0x7fffec005ed0, priority=priority@entry=0)
    at kernel/qcoreapplication.cpp:1506
#3  0x00007ffff74521ac in queued_activate (locker=<synthetic pointer>..., argv=0x7ffff1540df0, c=0x555555780e30, signal=5, sender=0x55555578dd00)
    at kernel/qobject.cpp:3619
#4  QMetaObject::activate (sender=sender@entry=0x55555578dd00, signalOffset=<optimized out>, local_signal_index=local_signal_index@entry=0, argv=argv@entry=0x0)
    at kernel/qobject.cpp:3718
#5  0x00007ffff7452bb7 in QMetaObject::activate (sender=sender@entry=0x55555578dd00, m=m@entry=0x7ffff3392300 <QXcbEventReader::staticMetaObject>, 
    local_signal_index=local_signal_index@entry=0, argv=argv@entry=0x0) at kernel/qobject.cpp:3628
#6  0x00007ffff322eba0 in QXcbEventReader::eventPending (this=this@entry=0x55555578dd00) at .moc/moc_qxcbconnection.cpp:135
#7  0x00007ffff31ec1a5 in QXcbEventReader::run (this=0x55555578dd00) at qxcbconnection.cpp:1376
#8  0x00007ffff72325ef in QThreadPrivate::start (arg=0x55555578dd00) at thread/qthread_unix.cpp:376
#9  0x00007ffff5faf6db in start_thread (arg=0x7ffff1541700) at pthread_create.c:463
#10 0x00007ffff690988f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

断在了与窗口管理系统通信的QXcbEventReader线程中,查看QXcbEventReader::run源码:

void QXcbEventReader::run()
{
    xcb_generic_event_t *event;
    while (m_connection && (event = xcb_wait_for_event(m_connection->xcb_connection()))) {
        m_mutex.lock();
        addEvent(event);
        while (m_connection && (event = local_xcb_poll_for_queued_event(m_connection->xcb_connection())))
            addEvent(event);
        m_mutex.unlock();
        emit eventPending();
    }

    m_mutex.lock();
    for (int i = 0; i < m_events.size(); ++i)
        free(m_events.at(i));
    m_events.clear();
    m_mutex.unlock();
}

QXcbEventReader线程在一个循环中监听与窗口管理系统的连接,收到事件后则将事件加入到一个xcb_generic_event_t类型的事件队列中,并发出eventPending信号。响应该信号时产生了一个post事件,调用了QCoreApplication::postEvent,在该函数的最后,调用了QAbstractEventDispatcher::wakeUp函数:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    ......
    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();
}

查看QAbstractEventDispatcher::wakeUp函数:

void QEventDispatcherGlib::wakeUp()
{
    Q_D(QEventDispatcherGlib);
    d->postEventSource->serialNumber.ref();
    g_main_context_wakeup(d->mainContext);
}

QAbstractEventDispatcher::wakeUp也只是简单地调用了g_main_context_wakeup,又进入了glib的代码,最终调用了g_wakeup_signal,而该函数又调用了write函数,对eventfd执行写操作:

void
g_wakeup_signal (GWakeup *wakeup)
{
  int res;

  if (wakeup->fds[1] == -1)
    {
      guint64 one = 1;

      /* eventfd() case. It requires a 64-bit counter increment value to be
       * written. */
      do
        res = write (wakeup->fds[0], &one, sizeof one);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
    }
  ......
}

可以看出来只是简单地往eventfd里面写了正整数1,但正是这个写操作使得UI线程的poll操作不再阻塞,进而进入了响应用户事件的代码中。

之前我们也看到了,响应用户事件时需要从窗口系统事件队列中取事件,这个事件是何时被加入的呢?还有就是调用QCoreApplication::postEvent产生的事件是如何被响应的?这两个问题实际上是相关的,处理post事件的过程正是将用户操作事件加入窗口系统事件队列的过程。

如何响应post事件?

窗口系统事件队列为QWindowSystemInterfacePrivate::WindowSystemEventList QWindowSystemInterfacePrivate::windowSystemEventQueue,使用gdb查看其何时插入新事件:

(gdb) watch QWindowSystemInterfacePrivate::windowSystemEventQueue.impl.d.end
Hardware watchpoint 7: QWindowSystemInterfacePrivate::windowSystemEventQueue.impl.d.end

点击鼠标,命中断点,查看堆栈:

Thread 1 "simple" hit Hardware watchpoint 7: QWindowSystemInterfacePrivate::windowSystemEventQueue.impl.d.end

Old value = 22
New value = 23
QListData::append (this=this@entry=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>, n=n@entry=1) at tools/qlist.cpp:183
183     return d->array + e;
(gdb) bt
#0  QListData::append (this=this@entry=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>, n=n@entry=1) at tools/qlist.cpp:183
#1  0x00007ffff7284aca in QListData::append (this=this@entry=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>) at tools/qlist.cpp:189
#2  0x00007ffff62a3a38 in QList<QWindowSystemInterfacePrivate::WindowSystemEvent*>::append (t=<synthetic pointer>: <optimized out>, 
    this=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>) at ../../include/QtCore/../../src/corelib/tools/qlist.h:602
#3  QWindowSystemInterfacePrivate::WindowSystemEventList::append (this=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>, e=0x7fffec013150)
    at kernel/qwindowsysteminterface_p.h:469
#4  QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery> (ev=0x7fffec013150) at kernel/qwindowsysteminterface.cpp:79
#5  0x00007ffff31ee09d in QXcbConnection::handleXcbEvent (this=this@entry=0x555555780e80, event=event@entry=0x7fffec0095b0) at qxcbconnection.cpp:1107
#6  0x00007ffff31ee8ac in QXcbConnection::processXcbEvents (this=0x555555780e80) at qxcbconnection.cpp:1767
#7  0x00007ffff7453052 in QObject::event (this=0x555555780e80, e=<optimized out>) at kernel/qobject.cpp:1246
#8  0x00007ffff78d2c5c in QApplicationPrivate::notify_helper (this=this@entry=0x555555771d80, receiver=receiver@entry=0x555555780e80, e=e@entry=0x7fffec02ddc0)
    at kernel/qapplication.cpp:3732
#9  0x00007ffff78da414 in QApplication::notify (this=0x7fffffffdcf0, receiver=0x555555780e80, e=0x7fffec02ddc0) at kernel/qapplication.cpp:3491
#10 0x00007ffff7422da8 in QCoreApplication::notifyInternal2 (receiver=0x555555780e80, event=event@entry=0x7fffec02ddc0) at kernel/qcoreapplication.cpp:1044
#11 0x00007ffff742593d in QCoreApplication::sendEvent (event=0x7fffec02ddc0, receiver=<optimized out>)
    at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:234
#12 QCoreApplicationPrivate::sendPostedEvents (receiver=receiver@entry=0x0, event_type=event_type@entry=0, data=0x555555771f00) at kernel/qcoreapplication.cpp:1719
#13 0x00007ffff7425ec8 in QCoreApplication::sendPostedEvents (receiver=receiver@entry=0x0, event_type=event_type@entry=0) at kernel/qcoreapplication.cpp:1573
#14 0x00007ffff747df23 in postEventSourceDispatch (s=s@entry=0x5555557a8a60) at kernel/qeventdispatcher_glib.cpp:276
#15 0x00007ffff551f3f7 in g_main_dispatch (context=0x7fffec004ff0) at gmain.c:3177
#16 g_main_context_dispatch (context=context@entry=0x7fffec004ff0) at gmain.c:3830
#17 0x00007ffff551f630 in g_main_context_iterate (context=context@entry=0x7fffec004ff0, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>)
    at gmain.c:3903
#18 0x00007ffff551f6bc in g_main_context_iteration (context=0x7fffec004ff0, may_block=may_block@entry=1) at gmain.c:3964
#19 0x00007ffff747d54f in QEventDispatcherGlib::processEvents (this=0x5555557a0540, flags=...) at kernel/qeventdispatcher_glib.cpp:423
#20 0x00007ffff3260411 in QPAEventDispatcherGlib::processEvents (this=0x5555557a0540, flags=...) at qeventdispatcher_glib.cpp:69
#21 0x00007ffff7420fea in QEventLoop::exec (this=this@entry=0x7fffffffdc80, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
#22 0x00007ffff742a224 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1332
#23 0x00005555555566b4 in main (argc=1, argv=0x7fffffffde38) at ../simple/main.cpp:10

从下往上看,在#18进入了glib代码,然后又在#14进入了Qt的代码,而#14的postEventSourceDispatch,从名字就可以看出来,是为了响应post事件,而响应post事件时将用户操作事件加入了窗口系统事件队列中。

查看g_main_dispatch代码:

static void
g_main_dispatch (GMainContext *context)
{
......
  for (i = 0; i < context->pending_dispatches->len; i++)
    {
      GSource *source = context->pending_dispatches->pdata[i];
......
      if (!SOURCE_DESTROYED (source))
    {
......
      gboolean (*dispatch) (GSource *,
                GSourceFunc,
                gpointer);
          GSource *prev_source;

      dispatch = source->source_funcs->dispatch;
......
          need_destroy = !(* dispatch) (source, callback, user_data);
......
    }

  g_ptr_array_set_size (context->pending_dispatches, 0);
}

这个函数遍历了context->pending_dispatches->pdata,取其回调函数source->source_funcs->dispatch进行调用,这回调函数正是用来处理post事件的。查看Qt源码,我们还能看到这些函数:

static gboolean socketNotifierSourceDispatch(GSource *source, GSourceFunc, gpointer)
static gboolean timerSourceDispatch(GSource *source, GSourceFunc, gpointer)
static gboolean idleTimerSourceDispatch(GSource *source, GSourceFunc, gpointer)

这些函数类似于postEventSourceDispatch,用于处理不同类型的事件。

那现在又有两个问题这个回调函数所在的GSource是何时加入context->pending_dispatches->pdata的,还有就是如何做到没有相应事件时避免这个GSource加入context->pending_dispatches->pdata。

GSource何时加入context->pending_dispatches->pdata?

再次请出我们的gdb:

(gdb) frame 15
#15 0x00007ffff551f3f7 in g_main_dispatch (context=0x7fffec004ff0) at gmain.c:3177
3177              need_destroy = !(* dispatch) (source, callback, user_data);
(gdb) p context
$1 = (GMainContext *) 0x7fffec004ff0
(gdb) watch ((GMainContext *) 0x7fffec004ff0)->pending_dispatches->len
Hardware watchpoint 8: ((GMainContext *) 0x7fffec004ff0)->pending_dispatches->len

点击Qt程序界面,命中断点后查看堆栈:

Thread 1 "simple" hit Hardware watchpoint 8: ((GMainContext *) 0x7fffec004ff0)->pending_dispatches->len

Old value = 0
New value = 1
0x00007ffff54f1ed7 in g_ptr_array_add (array=0x7fffec003040, data=0x5555557a8a60) at garray.c:1403
1403      rarray->pdata[rarray->len++] = data;
(gdb) bt
#0  0x00007ffff54f1ed7 in g_ptr_array_add (array=0x7fffec003040, data=0x5555557a8a60) at garray.c:1403
#1  0x00007ffff551ef0c in g_main_context_check (context=context@entry=0x7fffec004ff0, max_priority=0, fds=fds@entry=0x7fffec0078d0, n_fds=n_fds@entry=1)
    at gmain.c:3793
#2  0x00007ffff551f550 in g_main_context_iterate (context=context@entry=0x7fffec004ff0, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>)
    at gmain.c:3900
#3  0x00007ffff551f6bc in g_main_context_iteration (context=0x7fffec004ff0, may_block=may_block@entry=1) at gmain.c:3964
#4  0x00007ffff747d54f in QEventDispatcherGlib::processEvents (this=0x5555557a0540, flags=...) at kernel/qeventdispatcher_glib.cpp:423
#5  0x00007ffff3260411 in QPAEventDispatcherGlib::processEvents (this=0x5555557a0540, flags=...) at qeventdispatcher_glib.cpp:69
#6  0x00007ffff7420fea in QEventLoop::exec (this=this@entry=0x7fffffffdc80, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
#7  0x00007ffff742a224 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1332
#8  0x00005555555566b4 in main (argc=1, argv=0x7fffffffde38) at ../simple/main.cpp:10

可以看出是在g_main_context_check函数中将GSource加入遍历列表中,看一下这个函数:

gboolean
g_main_context_check (GMainContext *context,
              gint          max_priority,
              GPollFD      *fds,
              gint          n_fds)
{
  GSource *source;
  GSourceIter iter;
......

  for (i = 0; i < n_fds; i++)
    {
      if (fds[i].fd == context->wake_up_rec.fd)
        {
          if (fds[i].revents)
            {
              TRACE (GLIB_MAIN_CONTEXT_WAKEUP_ACKNOWLEDGE (context));
              g_wakeup_acknowledge (context->wakeup);
            }
          break;
        }
    }
......
  g_source_iter_init (&iter, context, TRUE);
  while (g_source_iter_next (&iter, &source))
    {
......
      if (!(source->flags & G_SOURCE_READY))
    {
          gboolean result;
          gboolean (* check) (GSource *source);

          check = source->source_funcs->check;

          if (check)
            {
......
              result = (* check) (source);
......
            }
          else
            result = FALSE;

......
      if (result)
        {
          GSource *ready_source = source;

          while (ready_source)
        {
          ready_source->flags |= G_SOURCE_READY;
          ready_source = ready_source->priv->parent_source;
        }
        }
    }

      if (source->flags & G_SOURCE_READY)
    {
......
      g_ptr_array_add (context->pending_dispatches, source);
......
    }
    }
......
}

g_main_context_check在g_main_context_poll结束后执行,因此首先调用了g_wakeup_acknowledge将eventfd的counter清空:

void
g_wakeup_acknowledge (GWakeup *wakeup)
{
  char buffer[16];

  /* read until it is empty */
  while (read (wakeup->fds[0], buffer, sizeof buffer) == sizeof buffer);
}

然后遍历GSource,得到回调函数check = source->source_funcs->check,根据该函数返回的结果来判断该事件源是否有新事件待处理,如果有,则将这个事件源加入待处理事件源,然后在g_main_dispatch函数中进行处理。对于post事件源,这个check是 postEventSourceCheck,对于不同的事件源有不同的check,具体可以自行查看。

总结

在Qt中,实现事件驱动机制时,以GSource方式响应事件的事件类型共有以下几种,我们只分析了postEvent,其他的类型也是大同小异:

postEvent
socketNotifier
timer
idleTimer

而用户事件的响应方式则不同,每次主循环都会进行轮询,即使当前没有用户事件,也会调用QWindowSystemInterface::sendWindowSystemEvents。这可能会有一点点性能损失,在老版本Qt中用户事件和其他事件也是同样使用GSource方式进行处理的,新版本不知道为什么改成这样了。

猜你喜欢

转载自blog.csdn.net/imred/article/details/82318988