linux下epoll如何实现高效处理
linux下epoll如何實現高效處理
作者
digoal
日期
2016-11-10
標簽
Linux , 內核 , epoll , 網絡編程 , 高并發
背景
本文轉自
http://www.cnblogs.com/debian/archive/2012/02/16/2354469.html
開發高性能網絡程序時,windows開發者們言必稱iocp,linux開發者們則言必稱epoll。
大家都明白epoll是一種IO多路復用技術,可以非常高效的處理數以百萬計的socket句柄,比起以前的select和poll效率高大發了。
我們用起epoll來都感覺挺爽,確實快,那么,它到底為什么可以高速處理這么多并發連接呢?
原理介紹
先簡單回顧下如何使用C庫封裝的3個epoll系統調用吧。
1 int epoll_create(int size); 2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 3 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);使用起來很清晰,
1 首先要調用epoll_create建立一個epoll對象。參數size是內核保證能夠正確處理的最大句柄數,多于這個最大數時內核可不保證效果。
2 epoll_ctl可以操作上面建立的epoll,例如,將剛建立的socket加入到epoll中讓其監控,或者把 epoll正在監控的某個socket句柄移出epoll,不再監控它等等。
3 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的內存對象,每次使用時都是使用空閑的已分配好的對象。
1 static int __init eventpoll_init(void) 2 { 3 ... ... 4 5 /* Allocates slab cache used to allocate "struct epitem" items */ 6 epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem), 7 0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC, 8 NULL, NULL); 9 10 /* Allocates slab cache used to allocate "struct eppoll_entry" */ 11 pwq_cache = kmem_cache_create("eventpoll_pwq", 12 sizeof(struct eppoll_entry), 0, 13 EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); 14 15 ... ...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返回的。
1 /* 2 * Each file descriptor added to the eventpoll interface will 3 * have an entry of this type linked to the hash. 4 */ 5 struct epitem { 6 /* RB-Tree node used to link this structure to the eventpoll rb-tree */ 7 struct rb_node rbn; 8 //紅黑樹,用來保存eventpoll 9 10 /* List header used to link this structure to the eventpoll ready list */ 11 struct list_head rdllink; 12 //雙向鏈表,用來保存已經完成的eventpoll 13 14 /* The file descriptor information this item refers to */ 15 struct epoll_filefd ffd; 16 //這個結構體對應的被監聽的文件描述符信息 17 18 /* Number of active wait queue attached to poll operations */ 19 int nwait; 20 //poll操作中事件的個數 21 22 /* List containing poll wait queues */ 23 struct list_head pwqlist; 24 //雙向鏈表,保存著被監視文件的等待隊列,功能類似于select/poll中的poll_table 25 26 /* The "container" of this item */ 27 struct eventpoll *ep; 28 //指向eventpoll,多個epitem對應一個eventpoll 29 30 /* The structure that describe the interested events and the source fd */ 31 struct epoll_event event; 32 //記錄發生的事件和對應的fd 33 34 /* 35 * Used to keep track of the usage count of the structure. This avoids 36 * that the structure will desappear from underneath our processing. 37 */ 38 atomic_t usecnt; 39 //引用計數 40 41 /* List header used to link this item to the "struct file" items list */ 42 struct list_head fllink; 43 雙向鏈表,用來鏈接被監視的文件描述符對應的struct file。因為file里有f_ep_link,用來保存所有監視這個文件的epoll節點 44 45 /* List header used to link the item to the transfer list */ 46 struct list_head txlink; 47 雙向鏈表,用來保存傳輸隊列 48 49 /* 50 * This is used during the collection/transfer of events to userspace 51 * to pin items empty events set. 52 */ 53 unsigned int revents; 54 //文件描述符的狀態,在收集和傳輸時用來鎖住空的事件集合 55 }; 56 57 //該結構體用來保存與epoll節點關聯的多個文件描述符,保存的方式是使用紅黑樹實現的hash表. 58 //至于為什么要保存,下文有詳細解釋。它與被監聽的文件描述符一一對應. 59 struct eventpoll { 60 /* Protect the this structure access */ 61 rwlock_t lock; 62 //讀寫鎖 63 64 /* 65 * This semaphore is used to ensure that files are not removed 66 * while epoll is using them. This is read-held during the event 67 * collection loop and it is write-held during the file cleanup 68 * path, the epoll file exit code and the ctl operations. 69 */ 70 struct rw_semaphore sem; 71 //讀寫信號量 72 73 /* Wait queue used by sys_epoll_wait() */ 74 wait_queue_head_t wq; 75 /* Wait queue used by file->poll() */ 76 77 wait_queue_head_t poll_wait; 78 /* List of ready file descriptors */ 79 80 struct list_head rdllist; 81 //已經完成的操作事件的隊列。 82 83 /* RB-Tree root used to store monitored fd structs */ 84 struct rb_root rbr; 85 //保存epoll監視的文件描述符 86 }; 87 88 //這個結構體保存了epoll文件描述符的擴展信息,它被保存在file結構體的private_data 89 //中。它與epoll文件節點一一對應。通常一個epoll文件節點對應多個被監視的文件描述符。 90 //所以一個eventpoll結構體會對應多個epitem結構體。那么,epoll中的等待事件放在哪里呢?見下面 91 /* Wait structure used by the poll hooks */ 92 struct eppoll_entry { 93 /* List header used to link this structure to the "struct epitem" */ 94 struct list_head llink; 95 /* The "base" pointer is set to the container "struct epitem" */ 96 void *base; 97 /* 98 * Wait queue item that will be linked to the target file wait 99 * queue head. 100 */ 101 wait_queue_t wait; 102 /* The wait queue head that linked the "wait" wait queue item */ 103 wait_queue_head_t *whead; 104 }; 105 106 //與select/poll的struct poll_table_entry相比,epoll的表示等待隊列節點的結 107 //構體只是稍有不同,與struct poll_table_entry比較一下。 108 struct poll_table_entry { 109 struct file * filp; 110 wait_queue_t wait; 111 wait_queue_head_t * wait_address; 112 };總結
以上是生活随笔為你收集整理的linux下epoll如何实现高效处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 令子元素垂直居中(并且子元素的高度不固定
- 下一篇: HTML,CSS