epoll詳解

  1. ET模式實現分析
    1.1 ET和LT的實現區別
    這裏寫圖片描述
    注:上圖的poll不要理解成和select相似那個poll,這是通過epoll_ctl調用的。
    下面簡要分析一下epoll的工作過程:
    (1) epoll_wait調用ep_poll,當rdlist爲空(無就緒fd)時掛起當前進程,知道rdlist不空時進程才被喚醒。
    (2) 文件fd狀態改變(buffer由不可讀變爲可讀或由不可寫變爲可寫),導致相應fd上的回調函數ep_poll_callback()被調用。
    (3) ep_poll_callback將相應fd對應epitem加入rdlist,導致rdlist不空,進程被喚醒,epoll_wait得以繼續執行。
    (4) ep_events_transfer函數將rdlist中的epitem拷貝到txlist中,並將rdlist清空。
    (5) ep_send_events函數(很關鍵),它掃描txlist中的每個epitem,調用其關聯fd對用的poll方法(圖中藍線)。此時對poll的調用僅僅是取得fd上較新的events(防止之前events被更新),之後將取得的events和相應的fd發送到用戶空間(封裝在struct epoll_event,從epoll_wait返回)。之後如果這個epitem對應的fd是LT模式監聽且取得的events是用戶所關心的,則將其重新加入回rdlist(圖中藍線),否則(ET模式)不在加入rdlist。
    具體代碼:
    /* 掃描整個txlist鏈表… */
    for (eventcnt = 0, uevent = esed->events;
         !list_empty(head) && eventcnt < esed->maxevents;) {
    /* 取出第一個成員 */
    epi = list_first_entry(head, struct epitem, rdllink);
    /* 然後從鏈表裏面移除 */
    list_del_init(&epi->rdllink);
    /* 讀取events, 
     * 注意events我們ep_poll_callback()裏面已經取過一次了, 爲啥還要再取?
     * 1. 我們當然希望能拿到此刻的最新數據, events是會變的~
     * 2. 不是所有的poll實現, 都通過等待隊列傳遞了events, 有可能某些驅動壓根沒傳
     * 必須主動去讀取. */
    revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
    epi->event.events;
    if (revents) {
    /* 將當前的事件和用戶傳入的數據都copy給用戶空間,
     * 就是epoll_wait()後應用程序能讀到的那一堆數據. */
    if (__put_user(revents, &uevent->events) ||
        __put_user(epi->event.data, &uevent->data)) {
    /* 如果copy過程中發生錯誤, 會中斷鏈表的掃描,
     * 並把當前發生錯誤的epitem重新插入到ready list.
     * 剩下的沒處理的epitem也不會丟棄, 在ep_scan_ready_list()
     * 中它們也會被重新插入到ready list */
    list_add(&epi->rdllink, head);
    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, noone 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.
     */
    /* 嘿嘿, EPOLLET和非ET的區別就在這一步之差呀~
     * 如果是ET, epitem是不會再進入到readly list,
     * 除非fd再次發生了狀態改變, ep_poll_callback被調用.
     * 如果是非ET, 不管你還有沒有有效的事件或者數據,
     * 都會被重新插入到ready list, 再下一次epoll_wait
     * 時, 會立即返回, 並通知給用戶空間. 當然如果這個
     * 被監聽的fds確實沒事件也沒數據了, epoll_wait會返回一個0,
     * 空轉一次.
     */
    list_add_tail(&epi->rdllink, &ep->rdllist);
    }
    }
    }
    說明:
    l epoll_wait返回的條件是rdlist不空,而使rdlist不空的途徑有兩個,分別對應圖中的紅線和藍線。
    l ET和LT模式下的epitem都可以通過紅線方式加入rdlist從而喚醒epoll_wait,但LT模式下的epitem還可以通過藍線方式重新加入rdlist喚醒epoll_wait。所以ET模式下,fd就緒(通過紅線加入rdlist)只會被通知一次,而LT模式下只要滿足相應讀寫條件就返回就緒(通過藍線加入rdlist)。
    l ET事件發生僅通知一次的原因是隻被添加到rdlist中一次,而LT可以有多次添加的機會。
    1.2 兩種加入rdlist途徑的不同
    下面我們來分析一下圖中兩種將epitem加入rdlist方式(也就是紅線和藍線)的區別。
    l 紅線:fd狀態改變是纔會觸發。那麼什麼情況會導致fd狀態的改變呢?
    對於讀取操作:
    (1) 當buffer由不可讀狀態變爲可讀的時候,即由空變爲不空的時候。
    (2) 當有新數據到達時,即buffer中的待讀內容變多的時候。
    對於寫操作:
    (1) 當buffer由不可寫變爲可寫的時候,即由滿狀態變爲不滿狀態的時候。
    (2) 當有舊數據被髮送走時,即buffer中待寫的內容變少得時候。
    l 藍線:fd的events中有相應的時間(位置1)即會觸發。那麼什麼情況下會改變events的相應位呢?
    對於讀操作:
    (1) buffer中有數據可讀的時候,即buffer不空的時候fd的events的可讀爲就置1。
    對於寫操作:
    (1) buffer中有空間可寫的時候,即buffer不滿的時候fd的events的可寫位就置1。
    說明:紅線是時間驅動被動觸發,藍線是函數查詢主動觸發。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章