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時立刻返回准備就緒鏈表里的數據即可。
對比
select缺點:
1.最大並發數限制:使用32個整數的32位,即32*32=1024來標識fd,雖然可修改,但是有以下第二點的瓶頸;
2.效率低:每次都會線性掃描整個fd_set,集合越大速度越慢;
3.內核/用戶空間內存拷貝問題。
epoll的提升:
1.本身沒有最大並發連接的限制,僅受系統中進程能打開的最大文件數目限制;
2.效率提升:只有活躍的socket才會主動的去調用callback函數;
3.省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
當然,以上的優缺點僅僅是特定場景下的情況:高並發,且任一時間只有少數socket是活躍的。