epoll的內部實現 看了就會懂

epoll的內部實現 & 百萬級別句柄監聽 & lt和et模式非常好的解釋


epoll是Linux高效網絡的基礎,比如event poll(例如nodejs),是使用libev,而libev的底層就是epoll(只不過不同的平臺可能用epoll,可能用kqueue)。

epoll能夠高效支持百萬級別的句柄監聽

epoll高效,是因爲內部用了一個紅黑樹記錄添加的socket,用了一個雙向鏈表接收內核觸發的事件。是系統級別的支持的:

當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。

eventpoll結構體如下所示:

struct eventpoll{
    ....
    /*紅黑樹的根節點,這顆樹中存儲着所有添加到epoll中的需要監控的事件*/
    struct rb_root  rbr;
    /*雙鏈表中則存放着將要通過epoll_wait返回給用戶的滿足條件的事件*/
    struct list_head rdlist;
    ....
};

下面內容參考文章

每一個epoll對象都有一個獨立的eventpoll結構體,用於存放通過epoll_ctl方法向epoll對象中添加進來的事件。

這些事件都會掛載在紅黑樹中,如此,重複添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n爲樹的高度)。

而所有添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關係,也就是說,當相應的事件發生時會調用這個回調方法。這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。

在epoll中,對於每一個事件,都會建立一個epitem結構體,如下所示:

struct epitem{
    struct rb_node  rbn;//紅黑樹節點
    struct list_head    rdllink;//雙向鏈表節點
    struct epoll_filefd  ffd;  //事件句柄信息
    struct eventpoll *ep;    //指向其所屬的eventpoll對象
    struct epoll_event event; //期待發生的事件類型
}

 

下面圖的左上角文字寫錯了,應該是雙向鏈表的每個節點都是基於epitem結構中的rdllink成員。 

 

上面這一句更具體的解釋是(爲什麼能支持百萬句柄):

1. 不用重複傳遞。我們調用epoll_wait時就相當於以往調用select/poll,但是這時卻不用傳遞socket句柄給內核,因爲內核已經在epoll_ctl中拿到了要監控的句柄列表。

2. 在內核裏,一切皆文件。所以,epoll向內核註冊了一個文件系統,用於存儲上述的被監控socket。當你調用epoll_create時,就會在這個虛擬的epoll文件系統裏創建一個file結點。當然這個file不是普通文件,它只服務於epoll。

epoll在被內核初始化時(操作系統啓動),同時會開闢出epoll自己的內核高速cache區,用於安置每一個我們想監控的socket,這些socket會以紅黑樹的形式保存在內核cache裏,以支持快速的查找、插入、刪除。這個內核高速cache區,就是建立連續的物理內存頁,然後在之上建立slab層,簡單的說,就是物理上分配好你想要的size的內存對象,每次使用時都是使用空閒的已分配好的對象。

3. 極其高效的原因:

這是由於我們在調用epoll_create時,內核除了幫我們在epoll文件系統裏建了個file結點,在內核cache裏建了個紅黑樹用於存儲以後epoll_ctl傳來的socket外,還會再建立一個list鏈表,用於存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表裏有沒有數據即可。有數據就返回,沒有數據就sleep,等到timeout時間到後即使鏈表沒數據也返回。所以,epoll_wait非常高效。

這個準備就緒list鏈表是怎麼維護的呢?當我們執行epoll_ctl時,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上之外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。所以,當一個socket上有數據到了,內核在把網卡上的數據copy到內核中後就來把socket插入到準備就緒鏈表裏了(注:好好理解這句話!)

從上面這句可以看出,epoll的基礎就是回調呀! 

 

如此,一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,就幫我們解決了大併發下的socket處理問題。執行epoll_create時,創建了紅黑樹和就緒鏈表,執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹幹上,然後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據。執行epoll_wait時立刻返回準備就緒鏈表裏的數據即可。

 

 

最後看看epoll獨有的兩種模式LT和ET。無論是LT和ET模式,都適用於以上所說的流程。區別是,LT模式下,只要一個句柄上的事件一次沒有處理完,會在以後調用epoll_wait時次次返回這個句柄,而ET模式僅在第一次返回。

 

關於LT,ET,有一端描述,LT和ET都是電子裏面的術語,ET是邊緣觸發,LT是水平觸發,一個表示只有在變化的邊際觸發,一個表示在某個階段都會觸發。

參考了這篇文章

關於LT和ET的實際代碼實驗,可以看這裏:http://www.cnblogs.com/charlesblc/p/5521086.html

關於ET和EPOLL_ONESHOT,可以看這裏:http://www.cnblogs.com/charlesblc/p/5538363.html

關於epoll統一事件源代碼,可以看這裏:http://www.cnblogs.com/charlesblc/p/5554785.html

 

LT, ET這件事怎麼做到的呢?當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,然後清空準備就緒list鏈表,最後,epoll_wait幹了件事,就是檢查這些socket,如果不是ET模式(就是LT模式的句柄了),並且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表了。所以,非ET的句柄,只要它上面還有事件,epoll_wait每次都會返回這個句柄。(從上面這段,可以看出,LT還有個回放的過程,低效了

 

更詳細的內容,可以看下面兩篇文章:

http://blog.csdn.net/russell_tao/article/details/7160071

http://www.open-open.com/lib/view/open1410403215664.html

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