epool如何高效

2022-09-11 01:39:23 字數 3092 閱讀 6376

開發高效能網路程式時,windows開發者們言必稱iocp,linux開發者們則言必稱epoll。大家都明白epoll是一種io多路復用技術,可以非常高效的處理數以百萬計的socket控制代碼,比起以前的select和poll效率高大發了。我們用起epoll來都感覺挺爽,確實快,那麼,它到底為什麼可以高速處理這麼多併發連線呢?

先簡單回顧下如何使用c庫封裝的3個epoll系統呼叫吧。

int epoll_create(int size);  

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

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

使用起來很清晰,首先要呼叫epoll_create建立乙個epoll物件。引數size是核心保證能夠正確處理的最大控制代碼數,多於這個最大數時核心可不保證效果。

epoll_ctl可以操作上面建立的epoll,例如,將剛建立的socket加入到epoll中讓其監控,或者把 epoll正在監控的某個socket控制代碼移出epoll,不再監控它等等。

epoll_wait在呼叫時,在給定的timeout時間內,當在監控的所有控制代碼中有事件發生時,就返回使用者態的程序。

從上面的呼叫方式就可以看到epoll比select/poll的優越之處:因為後者每次呼叫時都要傳遞你所要監控的所有socket給select/poll系統呼叫,這意味著需要將使用者態的socket列表copy到核心態,如果以萬計的控制代碼會導致每次都要copy幾十幾百kb的記憶體到核心態,非常低效。而我們呼叫epoll_wait時就相當於以往呼叫select/poll,但是這時卻不用傳遞socket控制代碼給核心,因為核心已經在epoll_ctl中拿到了要監控的控制代碼列表。

所以,實際上在你呼叫epoll_create後,核心就已經在核心態開始準備幫你儲存要監控的控制代碼了,每次呼叫epoll_ctl只是在往核心的資料結構裡塞入新的socket控制代碼。

在核心裡,一切皆檔案。所以,epoll向核心註冊了乙個檔案系統,用於儲存上述的被監控socket。當你呼叫epoll_create時,就會在這個虛擬的epoll檔案系統裡建立乙個file結點。當然這個file不是普通檔案,它只服務於epoll。

epoll在被核心初始化時(作業系統啟動),同時會開闢出epoll自己的核心高速cache區,用於安置每乙個我們想監控的socket,這些socket會以紅黑樹的形式儲存在核心cache裡,以支援快速的查詢、插入、刪除。這個核心高速cache區,就是建立連續的物理記憶體頁,然後在之上建立slab層,簡單的說,就是物理上分配好你想要的size的記憶體物件,每次使用時都是使用空閒的已分配好的物件。

static int __init eventpoll_init(void)  

{  ... ...  

/* allocates slab cache used to allocate "struct epitem" items */  

epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),  

0, slab_hwcache_align|epi_slab_debug|slab_panic,  

null, null);  

/* allocates slab cache used to allocate "struct eppoll_entry" */  

pwq_cache = kmem_cache_create("eventpoll_pwq",  

sizeof(struct eppoll_entry), 0,  

epi_slab_debug|slab_panic, null, null);  

... ...  

epoll的高效就在於,當我們呼叫epoll_ctl往裡塞入百萬個控制代碼時,epoll_wait仍然可以飛快的返回,並有效的將發生事件的控制代碼給我們使用者。這是由於我們在呼叫epoll_create時,核心除了幫我們在epoll檔案系統裡建了個file結點,在核心cache裡建了個紅黑樹用於儲存以後epoll_ctl傳來的socket外,還會再建立乙個list鍊錶,用於儲存準備就緒的事件,當epoll_wait呼叫時,僅僅觀察這個list煉表裡有沒有資料即可。有資料就返回,沒有資料就sleep,等到timeout時間到後即使鍊錶沒資料也返回。所以,epoll_wait非常高效。

而且,通常情況下即使我們要監控百萬計的控制代碼,大多一次也只返回很少量的準備就緒控制代碼而已,所以,epoll_wait僅需要從核心態copy少量的控制代碼到使用者態而已,如何能不高效?!

那麼,這個準備就緒list鍊錶是怎麼維護的呢?當我們執行epoll_ctl時,除了把socket放到epoll檔案系統裡file物件對應的紅黑樹上之外,還會給核心中斷處理程式註冊乙個**函式,告訴核心,如果這個控制代碼的中斷到了,就把它放到準備就緒list煉表裡。所以,當乙個socket上有資料到了,核心在把網絡卡上的資料copy到核心中後就來把socket插入到準備就緒煉表裡了。

如此,一顆紅黑樹,一張準備就緒控制代碼鍊錶,少量的核心cache,就幫我們解決了大併發下的socket處理問題。執行epoll_create時,建立了紅黑樹和就緒鍊錶,執行epoll_ctl時,如果增加socket控制代碼,則檢查在紅黑樹中是否存在,存在立即返回,不存在則新增到樹幹上,然後向核心註冊**函式,用於當中斷事件來臨時向準備就緒鍊錶中插入資料。執行epoll_wait時立刻返回準備就緒煉表裡的資料即可。

最後看看epoll獨有的兩種模式lt和et。無論是lt和et模式,都適用於以上所說的流程。區別是,lt模式下,只要乙個控制代碼上的事件一次沒有處理完,會在以後呼叫epoll_wait時次次返回這個控制代碼,而et模式僅在第一次返回。

這件事怎麼做到的呢?當乙個socket控制代碼上有事件時,核心會把該控制代碼插入上面所說的準備就緒list鍊錶,這時我們呼叫epoll_wait,會把準備就緒的socket拷貝到使用者態記憶體,然後清空準備就緒list鍊錶,最後,epoll_wait幹了件事,就是檢查這些socket,如果不是et模式(就是lt模式的控制代碼了),並且這些socket上確實有未處理的事件時,又把該控制代碼放回到剛剛清空的準備就緒鍊錶了。所以,非et的控制代碼,只要它上面還有事件,epoll_wait每次都會返回。而et模式的控制代碼,除非有新中斷到,即使socket上的事件沒有處理完,也是不會次次從epoll_wait返回的。

web靜態伺服器 epool

以下 支援http的長連線,即使用了content length import socket import time import sys import re import select class wsgiserver object 定義乙個wsgi伺服器的類 def init self,port...

如何高效讀書

雖然有人英文很強,有的翻譯很差,但anyway 中文閱讀與理解的時間,略讀與快速定位的速度還是要快一些。2.即時批註 總結筆記與交流 雖然愛書,但發現最有效的讀書方式還是不斷的製造脂批本,讀書時在重要的文字下劃線,把自己的心得寫在頁旁。在明天覆習一次批註,最好可以有空重新整理筆記,或者拿來與人討論。...

如何高效學習

整體學習的關鍵在於建立知識與知識之間的聯絡 在你不斷建立聯絡的過程中,小土路變成了柏油路,一環修到了六環,四通八達,遍布京城。這樣的結構能讓你快速到達目的地。模型是簡化的結構,是框架,對於書來說就是目錄 模型就像樓的架子,不僅樓有骨架,每個房間也有。有些像遞迴,乙個大的問題,不斷分解成小的問題,問題...