I/O複用----epoll

一、內核事件表

    epoll是linux特有的I/0複用函數,它在實現和使用上與select、poll有很大差異。

    首先,epoll使用一組函數來完成任務,而不是單個函數;

    其次,epoll吧用戶關心的文件描述符上的事件放在內核裏的一個事件表中,從而無須像select和poll那樣每次調用都要重複傳入文件描述符集或事件集。但epoll需要一個額外的文件描述符,來唯一標示內核中的這個事件表。這個文件描述符使用epoll_create函數來創建:

#include <sys/epoll.h>
int epoll_create(int size);

    size參數現在並不起作用,只是給內核一個提示,告訴它事件表需要多大。

    該函數返回的文件描述符將用作其他所有epoll系統調用的第一個參數,以指定要訪問的內核事件表。

    下面函數來操作內核事件表:

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

    fd參數是要操作的文件描述符,op參數則指定操作類型。操作類型有如下3種:

        EPOLL_CTL_ADD,往事件表中註冊fd上的事件。

        EPOLL_CTL_MOD,修改fd上的註冊事件。

        EPOLL_CTL_DEL,刪除fd上的註冊事件。

    event參數指定事件,它是epoll_event結構指針類型。定義如下:

struct epoll_event
{
    _uint32_t events;    //epoll事件
    epoll_data_t data;    //用戶數據
}

    其中events成員描述事件類型。epoll支持的事件類型和poll基本相同。標示epoll事件類型的宏是在poll對應的宏前加上“E”,比如epoll的數據可讀事件是EPOLLIN。但epoll有兩個額外的事件類型---EPOLLET和EPOLLONESHOT。data成員用於儲存用戶數據,定義如下:

typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;

    epoll_data_t是一個聯合體,其4個成員中使用最多的是fd,它指定事件所從屬的目標文件描述符。ptr成員可用來指定與fd相關的用戶數據。但由於epoll_data_t是一個聯合體,不能同時使用其ptr和fd。

    返回值:

    成功返回0,失敗返回-1並設置error。


二、epoll_wait函數

    epoll系列系統調用的主要接口是epoll_wait函數。它在一段時間內等待一組文件描述符上的事件,原型如下:

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    返回值:

    成功時返回就緒的文件描述符的個數,失敗時返回-1並設置error。

    epfd標示內核事件表,maxevents參數指定最多監聽多少個事件,它必須大於0。

    epoll_wait函數如果檢測到事件,就將所有就緒的事件從內核事件表(由epfd參數指定)中複製到它的第二個參數events指向的數組中。這個數組只用於輸出epoll_wait檢測到的就緒事件,而不像select和poll的數組參數那樣既用於傳入用戶註冊的事件,又用於輸出內核檢測到的就緒事件。這就極大提高了應用程序索引就緒文件描述符的效率。


三、LT 和 ET模式

    epoll對文件描述符的操作有兩種模式:LT(Level Trigger,電平觸發)模式和ET(Edge Trigger,邊沿觸發)模式。LT是默認的工作模式,在此模式下epoll相當於一個效率較高的poll。當往epoll內核事件表種註冊一個文件描述符上的EPOLLET事件時,epoll將以ET模式來操作該文件描述符。ET是epoll的高效的工作模式。

    1),LT工作模式的文件描述符

    當epoll_wait檢測到其上有事件發生並將此事件通知應用程序後,應用程序可以不立即處理該事件。這樣,當應用程序下一次調用epoll_wait時,epoll_wait還會再次嚮應用程序通告此事件,直到該事件被處理

    2),ET工作模式的文件描述符

    當epoll_wait檢測到其上有事件發生並將此事件通知應用程序後,應用程序必須立即處理該事件。因爲後續的epoll_wait調用將不再向應用程序通知這一事件。

    可見,ET模式在很大程度上降低了同一個epoll事件被反覆觸發的次數,因此效率要比LT模式高。


四、EPOLLONESHOT事件

    即使我們使用ET模式,一個socket上的某個事件還是可能被觸發多次。這在併發程序中就會引起一個問題。比如一個線程(或進程,下同)在讀取完某個socket上的數據後開始處理這些數據,而在數據的處理過程中該socket上又有新數據可讀(EPOLLIN再次被觸發),此時另外一個線程被喚醒來讀取這些新的數據。於是就出現了兩個線程同時操作一個socket的局面。這當然不是我們期望的。我們期望的是一個socket連接在任一時刻只被一個線程處理。這一點可以使用EPOLLONESHOT事件來實現。

    對於註冊了EPOLLONESHOT事件的文件描述符,操作系統最多觸發其上註冊的一個可讀、可寫或異常事件,且只觸發一次,除非我們使用epoll_ctl函數重置該文件描述符上註冊的EPOLLONESHOT事件。這樣,當一個線程在處理某個socket時,其他線程不可能有機會操作該socket的。但反過來思考,註冊了EPOLLONESHOT事件的socket一旦被某個線程處理完畢,該線程就應該立即重置這個socket上的EPOLLONESHOT事件,以確保這個socket下一次可讀時,其EPOLLIN事件能被觸發,進而讓其他工作線程有機會繼續處理這個socket。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章