poll、select和epoll的區別

poll、select和epoll的區別
select原理概述
調用select時,會發生以下事情:
1. 從用戶空間拷貝fd_set到內核空間;
2. 註冊回調函數__pollwait;
3. 遍歷所有fd,對全部指定設備做一次poll(這裏的poll是一個文件操作,它有兩個參數,一個是文件fd本身,一個是當設備尚未就緒時調用的回調函數__pollwait,這個函數把設備自己特有的等待隊列傳給內核,讓內核把當前的進程掛載到其中);
4. 當設備就緒時,設備就會喚醒在自己特有等待隊列中的【所有】節點,於是當前進程就獲取到了完成的信號。poll文件操作返回的是一組標準的掩碼,其中的各個位指示當前的不同的就緒狀態(全0爲沒有任何事件觸發),根據mask可對fd_set賦值;
5. 如果所有設備返回的掩碼都沒有顯示任何的事件觸發,就去掉回調函數的函數指針,進入有限時的睡眠狀態,再恢復和不斷做poll,再作有限時的睡眠,直到其中一個設備有事件觸發爲止。
6. 只要有事件觸發,系統調用返回,將fd_set從內核空間拷貝到用戶空間,回到用戶態,用戶就可以對相關的fd作進一步的讀或者寫操作了。
epoll原理概述
調用epoll_create時,做了以下事情:
1. 內核幫我們在epoll文件系統裏建了個file結點;
2. 在內核cache裏建了個紅黑樹用於存儲以後epoll_ctl傳來的socket;
3. 建立一個list鏈表,用於存儲準備就緒的事件。
調用epoll_ctl時,做了以下事情:
1. 把socket放到epoll文件系統裏file對象對應的紅黑樹上;
2. 給內核中斷處理程序註冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。
調用epoll_wait時,做了以下事情:
觀察list鏈表裏有沒有數據。有數據就返回,沒有數據就sleep,等到timeout時間到後即使鏈表沒數據也返回。而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的準備就緒句柄而已,所以,epoll_wait僅需要從內核態copy少量的句柄到用戶態而已。
總結如下:
一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,解決了大併發下的socket處理問題。
執行epoll_create時,創建了紅黑樹和就緒鏈表;
執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹幹上,然後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據;
執行epoll_wait時立刻返回準備就緒鏈表裏的數據即可。
兩種模式的區別:
LT模式下,只要一個句柄上的事件一次沒有處理完,會在以後調用epoll_wait時重複返回這個句柄,而ET模式僅在第一次返回。
兩種模式的實現:
當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,然後清空準備就緒list鏈表,最後,epoll_wait檢查這些socket,如果是LT模式,並且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表。所以,LT模式的句柄,只要它上面還有事件,epoll_wait每次都會返回。
對比
select缺點:
1. 最大併發數限制:使用32個整數的32位,即32*32=1024來標識fd,雖然可修改,但是有以下第二點的瓶頸;
2. 效率低:每次都會線性掃描整個fd_set,集合越大速度越慢;
3. 內核/用戶空間內存拷貝問題。
epoll的提升:
1. 本身沒有最大併發連接的限制,僅受系統中進程能打開的最大文件數目限制;
2. 效率提升:只有活躍的socket纔會主動的去調用callback函數;
3. 省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
當然,以上的優缺點僅僅是特定場景下的情況:高併發,且任一時間只有少數socket是活躍的。
如果在併發量低,socket都比較活躍的情況下,select就不見得比epoll慢了(就像我們常常說快排比插入排序快,但是在特定情況下這並不成立)。

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