轉載地址:https://www.cnblogs.com/pluser/p/epoll_principles.html
epoll原理
設想一個場景:有100萬用戶同時與一個進程保持着TCP連接,而每一時刻只有幾十個或幾百個TCP連接是活躍的(接收TCP包),也就是說在每一時刻進程只需要處理這100萬連接中的一小部分連接。那麼,如何才能高效的處理這種場景呢?進程是否在每次詢問操作系統收集有事件發生的TCP連接時,把這100萬個連接告訴操作系統,然後由操作系統找出其中有事件發生的幾百個連接呢?實際上,在Linux2.4版本以前,那時的select或者poll事件驅動方式是這樣做的。
這裏有個非常明顯的問題,即在某一時刻,進程收集有事件的連接時,其實這100萬連接中的大部分都是沒有事件發生的。因此如果每次收集事件時,都把100萬連接的套接字傳給操作系統(這首先是用戶態內存到內核態內存的大量複製),而由操作系統內核尋找這些連接上有沒有未處理的事件,將會是巨大的資源浪費,然後select和poll就是這樣做的,因此它們最多隻能處理幾千個併發連接。而epoll不這樣做,它在Linux內核中申請了一個簡易的文件系統,把原先的一個select或poll調用分成了3部分:
1)調用epoll_create建立一個epoll對象(在epoll文件系統中給這個句柄分配資源);
2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字;
3)調用epoll_wait收集發生事件的連接;
這樣只需要在進程啓動時建立1個epoll對象,並在需要的時候向它添加或刪除連接就可以了,因此,在實際收集事件時,epoll_wait的效率就會非常高,因爲調用epoll_wait時並沒有向它傳遞這100萬個連接,內核也不需要去遍歷全部的連接。
那麼Linux內核將如何實現以上的想法呢?下面以Linux內核2.6.35版本爲例,簡單說明一下epoll是如何高效處理事件的。
當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關,如下所示:
struct eventpoll {
...
/*紅黑樹的根節點,這棵樹中存儲着所有添加到epoll中的事件,也就是這個epoll監控的事件*/
struct rb_root rbr;
/*雙向鏈表rdllist保存着將要通過epoll_wait返回給用戶的、滿足條件的事件*/
struct list_head rdllist;
...
};
每一個epoll對象都有一個獨立的eventpoll結構體,這個結構體會在內核空間中創造獨立的內存,用於存儲使用epoll_ctl方法向epoll對象中添加進來的事件。這些事件都會掛到rbr紅黑樹中,這樣重複添加的事件就可以通過紅黑樹而高效的識別出來(epoll_ctl方法很快)。
所有添加到epoll中的事件都會與設備(如網卡)驅動程序建立回調關係,也就是說相應事件的發生時會調用這裏的回調方法。這個回調方法在內核中叫做ep_poll_callback,它會把這樣的事件放到上面的rdllist雙向鏈表中。
在epoll中對於每一個事件都會建立一個epitem結構體,如下所示:
struct epitem {
...
//紅黑樹節點
struct rb_node rbn;
//雙向鏈表節點
struct list_head rdllink;
//事件句柄等信息
struct epoll_filefd ffd;
//指向其所屬的eventepoll對象
struct eventpoll *ep;
//期待的事件類型
struct epoll_event event;
...
};
這裏包含每一個事件對應着的信息。
當調用epoll_wait檢查是否有發生事件的連接時,只是檢查eventpoll對象中的rdllist雙向鏈表是否有epitem元素而已,如果rdllist鏈表不爲空,則這裏的事件複製到用戶態內存中,同時將事件數量返回給用戶。因此epoll_waitx效率非常高。epoll_ctl在向epoll對象中添加、修改、刪除事件時,從rbr紅黑樹中查找事件也非常快,也就是說epoll是非常高效的,它可以輕易地處理百萬級別的併發連接。
總結:
1)epoll_create創建一個epoll對象,該對象有一個eventpoll結構體,結構體中含有關鍵成員:存放epoll中的事件的紅黑樹根節點rbr、保存epoll_wait返回的事件的雙向鏈表rdllist;
2)epoll_ctl方法向epoll對象中註冊事件,將epitem添加到rbr中,事件與設備驅動程序建立回調關係,事件發生調用回調方法,將發生的事件添加到rdllist中;
3)epoll_wait檢查rdllist雙向鏈表;
4)與select和poll的區別:
select和poll每次收集事件時都會把所有的fd傳給操作系統內核,首先是用戶態內存到內核態內存的大量複製,然後系統內核輪詢所有fd有沒有未處理的事件,浪費資源。