epoll的ET和LT模式比較 - 源碼分析


eventpoll是一種文件,它實現了一種機制利用一條rdllist隊列來避免阻塞地進行poll。eventpoll歸根到底還是在使用poll。而ET比LT高效,並不在於是否使用了poll,更不能說是因為LT使用了poll。通過閱讀源代碼就可以清楚看到對 ET 和 LT 處理的區別僅有一處,其余都相同。其實兩者都在使用poll,只不過 ET 可避免多次在epoll_wait對不確定的rdllist進行重復poll檢測。

 

首先來看sys_poll,f_op->poll 和 eventpoll 文件的關系。

sys_poll 可以通過wait機制,由被poll的文件的具體文件系統在文件發生狀態變化時,通過wait設定的回調函數喚醒阻塞在poll的任務。
而eventpoll 則是不阻塞地使用wait機制,它使用ep_poll_callback作為wait的回調函數,讓被poll的文件的具體文件系統在文件發生狀態變化時,通過這個ep_poll_callback將自身關聯epi鏈入到 eventpoll文件的 rdllist 中去。
對一個文件sys_poll時,會使用__poll_wait回調函數來阻塞等待事件喚醒。而將一個文件sys_epoll_ctl添加進eventpoll時,會使用ep_poll_callback實現異步的poll。
這里要區分sys_poll和file_operations->poll,阻塞是因為sys_poll執行poll_schedule_timeout(),而file_operations->poll只是建立wait機制的使用(,但是我們不可能繞開sys_poll直接去使用具體文件系統的poll)。所以eventpoll才可以有別於sys_poll進行異步的poll(,不去阻塞等待),也就是說sys_poll使用阻塞的政策,epoll_ctl的EPOLL_CTL_ADD使用異步的政策。實際兩者同樣都在使用wait機制的回調。

sys_epoll_wait只是關心eventpoll的rdllist隊列是否為空,並且還必須對rdllist隊列里面每一個關聯的文件使用poll檢測進行最終篩選。

而et 和 lvl-tri模式的唯一不同的處理則只是在sys_epoll_wait過程中。
調用路徑為:
sys_epoll_wait() > ep_send_evnets() > ep_scan_ready_list() > ep_send_events_proc()
et 和 lvl-tri 模式的差異,僅僅是因為 ep_send_events_proc 一個小動作,影響了下一次的ep_scan_ready_list 處理。

下面是ep_scan_ready_list的處理流程,從流程清楚可以找出差異來。

0. ep_scan_ready_list 將rdllist 截出來收集到txlist
1. 當ep_scan_ready_list進行ep_send_events_proc時,ep_poll_callback (使用wait機制對文件進行poll的異步回調) 將epi (一個文件關系到eventpoll的結構)鏈入到 ovflist
2. 否則ep_poll_callback將epi 鏈入到 rdllist
3. ep_send_events_proc 將對txlist的每個epi的進行poll檢測狀態
    如果滿足狀態
    a. 發送到用戶空間,
    b. 並將 非EPOLLET的 epi 重新鏈入 rdllist
    * 差異就在這里,對於LT模式下次還得通過poll進行篩選,即使你已經將文件的讀緩沖讀完了。
4. 在ep_scan_readly_list結束ep_send_events_proc后,會收集 ovflist 到 rdllist
5. 將未能寫到用戶空間的 txlist合並回rdllist

 

試想下面的情景:

當從epoll_wait取得事件后,同樣都將讀事件的文件的緩沖讀完,並且沒有寫入發生時,再次進入epoll_wait。
在 et模式下,這個文件不會出現在eventpoll文件的rdllist中。
而 lvl-tri模式下,盡管這個文件已經不可能是POLLIN狀態了,但下一次epoll_wait時,必須進行一次poll檢測狀態后從而篩選掉。
這個情景中,et模式比lvl-tri模式少了一次file_operations->poll檢測,所以比較起來就有效得多了。
當有N個文件,M次讀事件條件下,lvl-tri就可能會浪費N*M次poll檢測。
但不論et模式還是lvl-tri模式,epoll_wait都必須對rdllist隊列中每一個對應的文件使用poll檢測進行篩選。

static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
                   void *priv)
{
    struct ep_send_events_data *esed = priv;
    int eventcnt;
    unsigned int revents;
    struct epitem *epi;
    struct epoll_event __user *uevent;
    struct wakeup_source *ws;
    poll_table pt;

    init_poll_funcptr(&pt, NULL);

    /*
     * We can loop without lock because we are passed a task private list.
     * Items cannot vanish during the loop because ep_scan_ready_list() is
     * holding "mtx" during this call.
     */
    for (eventcnt = 0, uevent = esed->events;
         !list_empty(head) && eventcnt < esed->maxevents;) {
        epi = list_first_entry(head, struct epitem, rdllink);

        /*
         * Activate ep->ws before deactivating epi->ws to prevent
         * triggering auto-suspend here (in case we reactive epi->ws
         * below).
         *
         * This could be rearranged to delay the deactivation of epi->ws
         * instead, but then epi->ws would temporarily be out of sync
         * with ep_is_linked().
         */
        ws = ep_wakeup_source(epi);
        if (ws) {
            if (ws->active)
                __pm_stay_awake(ep->ws);
            __pm_relax(ws);
        }

        list_del_init(&epi->rdllink);

        revents = ep_item_poll(epi, &pt);    // 調用f_op->poll,但不是sys_poll /*
         * If the event mask intersect the caller-requested one,
         * deliver the event to userspace. Again, ep_scan_ready_list()
         * is holding "mtx", so no operations coming from userspace
         * can change the item.
         */
        if (revents) {
            if (__put_user(revents, &uevent->events) ||
                __put_user(epi->event.data, &uevent->data)) {
                list_add(&epi->rdllink, head);
                ep_pm_stay_awake(epi);
                return eventcnt ? eventcnt : -EFAULT;
            }
            eventcnt++;
            uevent++;
            if (epi->event.events & EPOLLONESHOT)
                epi->event.events &= EP_PRIVATE_BITS;
            else if (!(epi->event.events & EPOLLET)) {
                /*
                 * If this file has been added with Level
                 * Trigger mode, we need to insert back inside
                 * the ready list, so that the next call to
                 * epoll_wait() will check again the events
                 * availability. At this point, no one can insert
                 * into ep->rdllist besides us. The epoll_ctl()
                 * callers are locked out by
                 * ep_scan_ready_list() holding "mtx" and the
                 * poll callback will queue them in ep->ovflist.
                 */
                list_add_tail(&epi->rdllink, &ep->rdllist);
                ep_pm_stay_awake(epi);
            }
        }
    }

    return eventcnt;
}

 


eventpoll是一個文件,同樣可以使用sys_poll對其進行阻塞poll檢測。
ep_scan_ready_list() 配合 ep_read_events_proc() 使用在eventpoll的file_operations->poll中。

(ep_scan_ready_list() 配合 ep_send_events_proc() 使用在epoll_wait)

用於eventpoll文件被poll
1. 檢查rdllist中是否包含至少一個滿足期望輪詢到的狀態。
2. 對rdllist中的每一個epi進行poll檢測:
    a. 滿足就返回;
    b. 不滿足,順便稱出rdllist。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM