簡體   English   中英

如何在 epoll 上使用具有級別觸發行為的 eventfd?

[英]How to use an eventfd with level triggered behaviour on epoll?

在不遞減 eventfd 計數器時,在epoll_ctl上注冊一個級別觸發的 eventfd 只會觸發一次。 總結一下這個問題,我觀察到 epoll 標志( EPOLLETEPOLLONESHOTNone用於級別觸發行為)表現相似。 或者換句話說:沒有效果。

你能確認這個錯誤嗎?

我有一個具有多個線程的應用程序。 每個線程使用相同的 epollfd 使用epoll_wait等待新事件。 如果要優雅地終止應用程序,則必須喚醒所有線程。 我的想法是你使用 eventfd 計數器 ( EFD_SEMAPHORE|EFD_NONBLOCK ) 來喚醒所有這些(具有級別觸發的 epoll 行為)。 (不管少數文件描述符的雷鳴般的羊群問題。)

例如,對於 4 個線程,您將 4 個寫入 eventfd。 我期待epoll_wait立即一次又一次地返回,直到計數器遞減(讀取)4 次。 epoll_wait每次寫入只返回一次。

是的,我仔細閱讀了所有相關手冊;)

#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

static int event_fd = -1;
static int epoll_fd = -1;

void *thread(void *arg)
{
    (void) arg;

    for(;;) {
       struct epoll_event event;
       epoll_wait(epoll_fd, &event, 1, -1);

       /* handle events */
       if(event.data.fd == event_fd && event.events & EPOLLIN) {
           uint64_t val = 0;
           eventfd_read(event_fd, &val);
           break;
       }
    }

    return NULL;
}

int main(void)
{
    epoll_fd = epoll_create1(0);
    event_fd = eventfd(0, EFD_SEMAPHORE| EFD_NONBLOCK);

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = event_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &event);

    enum { THREADS = 4 };
    pthread_t thrd[THREADS];

    for (int i = 0; i < THREADS; i++)
        pthread_create(&thrd[i], NULL, &thread, NULL);

    /* let threads park internally (kernel does readiness check before sleeping) */
    usleep(100000);
    eventfd_write(event_fd, THREADS);

    for (int i = 0; i < THREADS; i++)
        pthread_join(thrd[i], NULL);
}

當您寫入eventfd時,將調用 function eventfd_signal 它包含以下用於喚醒的行:

wake_up_locked_poll(&ctx->wqh, EPOLLIN);

wake_up_locked_poll是一個宏:

#define wake_up_locked_poll(x, m)                       \
    __wake_up_locked_key((x), TASK_NORMAL, poll_to_key(m))

__wake_up_locked_key被定義為:

void __wake_up_locked_key(struct wait_queue_head *wq_head, unsigned int mode, void *key)
{
    __wake_up_common(wq_head, mode, 1, 0, key, NULL);
}

最后, __wake_up_common被聲明為:

/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key,
            wait_queue_entry_t *bookmark)

請注意nr_exclusive參數,您會看到寫入eventfd喚醒一個獨占服務員。

獨家是什么意思? 閱讀epoll_ctl手冊頁給了我們一些見解:

EPOLLEXCLUSIVE(自 Linux 4.5 起):

為附加到目標文件描述符 fd 的 epoll 文件描述符設置獨占喚醒模式。 當發生喚醒事件並且使用EPOLLEXCLUSIVE將多個 epoll 文件描述符附加到同一個目標文件時,一個或多個 epoll 文件描述符將接收帶有epoll_wait(2)的事件。

添加事件時不要使用EPOLLEXCLUSIVE ,但要使用epoll_wait等待,每個線程都必須將自己放入等待隊列。 Function do_epoll_wait通過調用ep_poll來執行等待。 通過遵循代碼,您可以看到它將當前線程添加到第 #1903 行的等待隊列中

__add_wait_queue_exclusive(&ep->wq, &wait);

這是對正在發生的事情的解釋 - epoll 服務員是獨占的,所以只有一個線程被喚醒。 此行為已在v2.6.22-rc1中引入,相關更改已在此處討論。

對我來說,這看起來像是eventfd_signal function 中的一個錯誤:在信號量模式下,它應該執行喚醒,並且nr_exclusive等於寫入的值。

所以你的選擇是:

  • 為每個線程創建一個單獨的 epoll 描述符(可能不適用於您的設計 - 縮放問題)
  • 在它周圍放置一個互斥鎖(縮放問題)
  • 使用poll ,可能在eventfd和 epoll 上
  • 通過使用evenfd_write寫入 1 來分別喚醒每個線程 4 次(可能是你能做的最好的)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM