天天看點

[epoll 源碼走讀] LT 與 ET 模式差別

走讀核心源碼,看看 epoll 的 LT 和 ET 模式差別。

詳細資訊可以參考文章《[epoll 源碼走讀] epoll 實作原理》,現在将部分代碼提取出來。

1. 原理

核心邏輯在

ep_send_events_proc

函數裡實作,關鍵在 就緒清單。

  • epoll 監控的 fd 産生事件,fd 資訊被添加進就緒清單。
  • epoll_wait 發現有就緒事件,程序持續執行,或者被喚醒工作。
  • epoll 将 fd 資訊從就緒清單中删除。
  • fd 對應就緒事件資訊從核心空間拷貝到使用者空間。
  • 拷貝完成後,檢查事件模式是 LT 還是 ET,如果不是 ET,重新将 fd 資訊添加回就緒清單,下次重新觸發。
🔥 文章來源:《[epoll 源碼走讀] LT 與 ET 模式差別》

2. 源碼實作流程

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
        int, maxevents, int, timeout) {
    return do_epoll_wait(epfd, events, maxevents, timeout);
}

static int do_epoll_wait(int epfd, struct epoll_event __user *events,
             int maxevents, int timeout) {
    ...
    error = ep_poll(ep, events, maxevents, timeout);
    ...
}

// 檢查就緒隊列,如果就緒隊列有就緒事件,就将事件資訊從核心空間發送到使用者空間。
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout) {
    ...
    // 檢查就緒隊列,如果有就緒事件就進入發送環節。
    ...
send_events:
    // 有就緒事件就發送到使用者空間,否則繼續擷取資料直到逾時。
    if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) &&
        !timed_out)
        goto fetch_events;
    ...
}

static int ep_send_events(struct eventpoll *ep,
              struct epoll_event __user *events, int maxevents) {
    struct ep_send_events_data esed;

    esed.maxevents = maxevents;
    esed.events = events;

    // 周遊事件就緒清單,發送就緒事件到使用者空間。
    ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
    return esed.res;
}

static __poll_t ep_scan_ready_list(struct eventpoll *ep,
                  __poll_t (*sproc)(struct eventpoll *,
                       struct list_head *, void *),
                  void *priv, int depth, bool ep_locked) {
    ...
    // 将就緒隊列分片連結到 txlist 連結清單中。
    list_splice_init(&ep->rdllist, &txlist);
    // 執行 ep_send_events_proc
    res = (*sproc)(ep, &txlist, priv);
    ...
}

static __poll_t ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv) {
    ...
    // 周遊處理 txlist(原 ep->rdllist 資料)就緒隊列結點,擷取事件拷貝到使用者空間。
    list_for_each_entry_safe (epi, tmp, head, rdllink) {
        if (esed->res >= esed->maxevents)
            break;
        ...
        // 先從就緒隊列中删除 epi,如果是 LT 模式,就緒事件還沒處理完,再把它添加回去。
        list_del_init(&epi->rdllink);

        // 擷取 epi 對應 fd 的就緒事件。
        revents = ep_item_poll(epi, &pt, 1);
        if (!revents)
            continue;

        // 核心空間向使用者空間傳遞資料。__put_user 成功拷貝傳回 0。
        if (__put_user(revents, &uevent->events) ||
            __put_user(epi->event.data, &uevent->data)) {
            // 如果拷貝失敗,繼續儲存在就緒清單裡。
            list_add(&epi->rdllink, head);
            ep_pm_stay_awake(epi);
            if (!esed->res)
                esed->res = -EFAULT;
            return 0;
        }

        // 成功處理就緒事件的 fd 個數。
        esed->res++;
        uevent++;
        if (epi->event.events & EPOLLONESHOT)
            // #define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET | EPOLLEXCLUSIVE)
            epi->event.events &= EP_PRIVATE_BITS;
        else if (!(epi->event.events & EPOLLET)) {
            /* lt 模式下,目前事件被處理完後,不會從就緒清單中删除,留待下一次 epoll_wait
             * 調用,再檢視是否還有事件沒處理,如果沒有事件了就從就緒清單中删除。
             * 在周遊事件的過程中,不能寫 ep->rdllist,因為已經上鎖,隻能把新的就緒資訊
             * 添加到 ep->ovflist */
            list_add_tail(&epi->rdllink, &ep->rdllist);
            ep_pm_stay_awake(epi);
        }
    }

    return 0;
}
           

3. 參考

  • [epoll 源碼走讀] epoll 實作原理

你的點贊👍,是對作者最好的支援,謝謝!😸

繼續閱讀