epoll詳解

什麼是epoll?
epoll是爲處理大批量句柄而作了改進的poll, 是性能最好的多路I/O就緒通知方法;
只有三個系統調用: epoll_create, epoll_ctl, epoll_wait;
epoll_ctl - epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型;
epoll的工作原理
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值;
你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時複製的開銷;
另一個本質的改進在於epoll採用基於事件的就緒通知方式;
在select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描;
而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知;
epoll的兩種工作方式
水平觸發(LT)
相當於速度比較快的poll;
LT(level triggered)是epoll缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作;
如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點;
傳統的select/poll都是這種模型的代表.
邊緣觸發(ET)
用了EPOLLET標誌;
相當於非阻塞的讀;
ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高;
ET與LT的區別在於,當一個新的事件到來時,ET模式下當然可以從epoll_wait調用中獲取到這個事件,可是如果這次沒有把這個事件對應的套接字緩衝區處理完,在這個套接字中沒有新的事件再次到來時,在ET模式下是無法再次從epoll_wait調用中獲取這個事件的;
而LT模式正好相反,只要一個事件對應的套接字緩衝區還有數據,就總能從epoll_wait中獲取這個事件;
LT模式下開發基於epoll的應用要簡單些,不太容易出錯。而在ET模式下事件發生時,如果沒有徹底地將緩衝區數據處理完,則會導致緩衝區中的用戶請求得不到響應;
epoll的優點
支持一個進程打開大數目的socket描述符(FD);
select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是2048;
重新編譯內核解決這個問題, 或者利用多進程解決這個問題(Apache方案);
epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數目, 這個數字一般遠大於2048, 1G的內存上是10W左右;
IO效率不隨FD數目增加而線性下降;
傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,性能會線性下降;
不過由於網絡延時,任一時間只有部分的socket是”活躍”的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降;
epoll不存在這個問題,它只會對”活躍”的socket進行操作(內核實現的原因);
這是因爲在內核實現中epoll是根據每個fd上面的callback函數實現的;
只有”活躍”的socket纔會主動的去調用 callback函數,其他idle狀態socket則不會;
epoll實現了一個”僞AIO,因爲這時候推動力在os內核;
在一些 benchmark中,如果所有的socket基本上都是活躍的;
epoll並不比select/poll有什麼效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降;
使用mmap加速內核與用戶空間的消息傳遞;
這點實際上涉及到epoll的具體實現了;
無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核於用戶空間mmap同一塊內存實現的;
epoll是一種IO多路複用技術,可以非常高效的處理數以百萬計的socket句柄;
因爲select/poll每次調用時都要傳遞你所要監控的所有socket給select/poll系統調用,這意味着需要將用戶態的socket列表copy到內核態,如果以萬計的句柄會導致每次都要copy幾十幾百KB的內存到內核態,非常低效;
epoll_wait卻不用傳遞socket句柄給內核,因爲內核已經在epoll_ctl中拿到了要監控的句柄列表;
epoll還維護了一個雙鏈表,用戶存儲發生的事件;
當epoll_wait調用時,僅僅觀察這個list鏈表裏有沒有數據即eptime項即可;
有數據就返回,沒有數據就sleep,等到timeout時間到後即使鏈表沒數據也返回;
這個準備就緒list鏈表是怎麼維護的呢?
當我們執行epoll_ctl時,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上之外;
還會給內核中斷處理程序註冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表裏;
一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,就幫我們解決了大併發下的socket處理問題;
執行epoll_create時,創建了紅黑樹和就緒鏈表;
執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹幹上,然後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據;
執行epoll_wait時立刻返回準備就緒鏈表裏的數據即可;

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