select
監聽一組句柄fd_set,第一次調用的時候循環所有句柄對應的驅動函數xx_poll,socket的話就是sock_poll。
循環遍歷完畢之后會如果發現有可用的(活躍狀態的)fd,則返回,返回的時候會返回活躍的fd個數,同時會
把不活躍的fd在fd_set移除。如果循環fd_set一遍以后發現沒有活躍的fd。假設此時socket在非阻塞模式下,
那么select會重復遍歷這些fd_set直到超過了我們設置的時間限制,可是在阻塞模式下呢,怎么處理?
阻塞模式下我們沒有必要設置超時限制,因為如果第一遍變量fd_set發現沒有活躍的fd那么,這條進程會被
掛起的。進程怎么被激活呢,實際上進程是被掛在了n個fd的等待隊列里,只要一個fd准備就緒那么進程就
會被喚醒,網卡接收了數據包發送一個中斷給內核,內核處理這個中斷,喚醒進程,這里是涉及到軟中斷的
,有興趣可以看看。select被喚醒之后,並不知道是哪一個fd把自己喚醒的,所以還要來一個遍歷,過程跟
上面說的是一樣的。select阻塞到返回經歷的內核和用戶空間的拷貝,先把fd_set從用戶空間拷貝給內核空
間,內核空間處理完畢之后把活動的fd再copy回來。其實后者還好,但是前者當fd_set百萬級別的時候,就
費勁了。個中細節我並非完全了解,可是單看這個設計模式就覺着有效率問題了,感覺真的很傻,copy不說,
這個遍歷所有描述符實在是太費勁了,而且每次在調用開始時,要把當前進程放入各個文件描述符的等待隊列
在調用結束后,又把進程從各個等待隊列中刪除。
epoll
epoll的實現說白了,就是新發明了一個迷你文件系統:eventpollfs。其實,這需要sock_poll驅動的輔助
才能完成這樣的設計,話說如果關注到epoll和select了,還不了解一下驅動的話,是看不動的。epoll的一
組函數:epoll_create,epoll_ctl,epoll_wait,就這三個函數完成百萬並發醉了么?簡單的可以這么理解,
epoll_create : 創建紅黑樹和就緒列表,實際他創建了一個專屬於epoll文件系統的一個文件
epoll_ctl :向紅黑樹添加socket句柄,向內核注冊回調函數,用於當中斷事件來臨時,向准備就緒列表插
入數據
epoll_wait :返回准備就緒列表的數據
准備就緒列表怎么維護的呢?當我們在做epoll_ctl時會給內核中斷處理程序注冊一個函數,告訴內核,如果這個句柄的中斷到了,就把他放在准備就緒list鏈表里。
另外還有就是epoll的水平觸發和邊緣觸發
水平觸發level-triggered lT 只要滿足條件就觸發一個事件,只要數據沒有被獲取,內核會不斷通知你
邊緣出發edge-triggered ET 每當狀態變化,觸發一個事件。
具體例子吧,假設一個socket本來無數據可讀,現在來了100個字節,這時候狀態發生了變化,那么水平觸發
和邊緣出發都會被激活,可是呢,我們這次只讀了20個字節,對於水平觸發來說,還有80字節沒讀呢,再去監
聽端口還是應該被觸發可讀調用,但是對於邊緣觸發的設計模式來說,因為剛才狀態是從不可讀變為可讀,而現在是可讀維持為可讀,狀態沒變化,所以現在沒必要再觸發可讀調用了。
這只是種設計思想而已,比如epoll,在就緒列表做點名堂就可以了,比如一個socket,假設沒有被讀完,提供是否再次放回到就緒列表中這兩個選項,就成了兩種觸發了。