1. 程式人生 > >Qt的事件驅動機制與eventfd

Qt的事件驅動機制與eventfd

簡介

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 ([email protected]=0x555555771d80, [email protected]=0x7fffffffdd00, [email protected]=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 ([email protected]=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 ([email protected]=0x7fffffffdd00, event=event@entry=0x7fffffffd610, alienWidget=0x0, [email protected]=0x7fffffffdd00, 
    nativeWidget=0x7fffffffdd00, [email protected]=0x7ffff7dd3c70 <qt_button_down>, lastMouseReceiver=..., spontaneous=true) at kernel/qapplication.cpp:2711
#7  0x00007ffff792c663 in QWidgetWindow::handleMouseEvent ([email protected]=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 ([email protected]=0x555555771d80, [email protected]=0x555555777cb0, [email protected]=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 ([email protected]=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 ([email protected]=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 ([email protected]=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=[email protected]=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>, n=[email protected]=1) at tools/qlist.cpp:183
183     return d->array + e;
(gdb) bt
#0  QListData::append (this=[email protected]=0x7ffff67e1d20 <QWindowSystemInterfacePrivate::windowSystemEventQueue>, n=[email protected]=1) at tools/qlist.cpp:183
#1  0x00007ffff7284aca in QListData::append (this=[email protected]=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=[email protected]=0x555555780e80, event=[email protected]=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=[email protected]=0x555555771d80, receiver=[email protected]=0x555555780e80, e=[email protected]=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=[email protected]=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=[email protected]=0x0, event_type=[email protected]=0, data=0x555555771f00) at kernel/qcoreapplication.cpp:1719
#13 0x00007ffff7425ec8 in QCoreApplication::sendPostedEvents (receiver=[email protected]=0x0, event_type=[email protected]=0) at kernel/qcoreapplication.cpp:1573
#14 0x00007ffff747df23 in postEventSourceDispatch (s=[email protected]=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=[email protected]=0x7fffec004ff0) at gmain.c:3830
#17 0x00007ffff551f630 in g_main_context_iterate (context=[email protected]=0x7fffec004ff0, block=[email protected]=1, dispatch=[email protected]=1, self=<optimized out>)
    at gmain.c:3903
#18 0x00007ffff551f6bc in g_main_context_iteration (context=0x7fffec004ff0, may_block=[email protected]=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=[email protected]=0x7fffffffdc80, flags=..., [email protected]=...) 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