select,poll,epoll 簡單概述 水平觸發與邊緣觸發 select epoll Java中select BUG

簡單概述

select,poll,epoll都是用來實現IO多路複用的機制,在Linux網絡模型中對應着IO複用模型Unix上的IO模型

select:最大支持1024個文件描述符,在描述符較多情況下性能較差,水平觸發
poll:poll與select基本相同,只是沒有文件描述符的限制,水平觸發
epoll:文件描述符爲系統上限,在描述符較多情況下性能較好,同時支持水平與邊緣觸發

水平觸發與邊緣觸發

水平觸發:只要文件描述符關聯的讀內核緩衝區非空,有數據可以讀取,就一直髮出可讀信號進行通知,當文件描述符關聯的內核寫緩衝區不滿,有空間可以寫入,就一直髮出可寫信號進行通知,如果系統中有大量不需要讀寫的就緒文件描述符,而它們每次都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率

邊緣觸發:當文件描述符關聯的讀內核緩衝區由空轉化爲非空的時候,則發出可讀信號進行通知,當文件描述符關聯的內核寫緩衝區由滿轉化爲不滿的時候,則發出可寫信號進行通知。如果這次沒有把數據全部讀寫完(如讀寫緩衝區太小),它不會再次通知你,直到該文件描述符上出現第二次可讀寫事件纔會通知你,這種模式比水平觸發效率高,系統不會充斥大量不關心的就緒文件描述符

使用Linux epoll模型的水平觸發模式,當socket可寫時,會不停的觸發socket可寫的事件,如何處理?當需要向socket寫數據時,將該socket加入到epoll等待可寫事件。接收到socket可寫事件後,調用write或send發送數據,當數據全部寫完後, 將socket描述符移出epoll列表,這種做法需要反覆添加和刪除。邊緣模式就可以直接搞定,並不需要用戶層程序的補丁操作。

select

Linux將進程分類爲兩個隊列,阻塞隊列和工作隊列。網絡應用程序調用linux read讀取數據時,如果沒有數據就一直阻塞。假設當進程執行到read時,A進程就從工作隊列中移到該socket的等待隊列中,A進程就被阻塞。阻塞期間,如果來數據了,中斷處理就會將數據裝入接收隊列,然後喚醒A進程,移動到工作隊列中。

read阻塞原理:進程分爲“運行”和“等待”等幾種狀態。當進程 A 執行到創建 socket 的語句時,操作系統會創建一個由文件系統管理的 socket 對象。這個 socket 對象包含了發送緩衝區、接收緩衝區與等待隊列等成員。當程序執行到 read 時,操作系統會將進程 A 從工作隊列移動到該 socket 的等待隊列中,那麼進程A就不會被執行,也不會佔用 CPU 資源。當 socket 接收到數據後,操作系統將該 socket 等待隊列上的進程重新放回到工作隊列,該進程變成運行狀態

如何從阻塞隊列移到工作隊列:網卡將數據寫入內存後,網卡產生一箇中斷,處理器立即停止它正在做的事,然後跳轉到內存中預定義的中斷程序開始執行。

但是每個read方法只能監控一個socket,select的原理就是使用一個數據存放socket,如果數組(代碼中寫死了數組長度爲1024)中的所有socket都沒有數據,進程就被掛起,直到有socket收到數據,喚醒進程。

假如程序同時監視如下圖的 sock1、sock2 和 sock3 三個 socket,那麼在調用 select 之後,操作系統把進程 A 分別加入這三個 socket 的等待隊列中。

當任何一個 socket 收到數據後,中斷程序將喚起進程,所謂喚起進程,就是將進程從所有的等待隊列中移除,加入到工作隊列裏面

當進程 A 被喚醒後,它知道至少有一個 socket 接收了數據。程序只需遍歷一遍 socket 列表,就可以得到就緒的 socket。

缺點:每次調用select都需要將進程加入到所有要監控的socket的等待隊列中,每次喚醒都要從所有的隊列中移除;被喚醒後,進程並不知道哪些socket有數據,需要遍歷。

epoll

epoll將等待隊列和阻塞進程分開了,使用epoll_ctl維護等待隊列,使用epoll_wait阻塞進程,epoll內部維護了一個就緒隊列,收到數據的socket直接加入就緒隊列,當 epoll 監聽的 socket 狀態發生改變(變爲可讀或可寫)時,就會把就緒的 socket 添加到就緒隊列中,當進程被喚醒,只要獲取就緒隊列就能知道哪些socket收到數據了。epoll內部使用紅黑樹保存所有監聽的socket,添加和查找元素的時間複雜度爲 O(log n)

當某個進程調用 epoll_create 方法時,內核會創建一個 eventpoll 對象。eventpoll 對象也是文件系統中的一員,和 socket 一樣,它也會有等待隊列。

rdllist: 保存已經就緒的文件列表。
rbr: 使用紅黑樹來管理所有被監聽的文件。紅黑樹節點是epitem

創建 epoll 對象後,可以用 epoll_ctl 添加或刪除所要監聽的 socket。以添加 socket 爲例,如果通過 epoll_ctl 添加 sock1、sock2 和 sock3 的監視,內核會將 eventpoll 添加到這三個 socket 的等待隊列中。

當 socket 收到數據後,中斷程序會給 eventpoll 的“就緒列表”添加 socket 引用。如下圖展示的是 sock2 和 sock3 收到數據後,中斷程序讓 rdlist 引用這兩個 socket。

eventpoll 對象相當於 socket 和進程之間的中介,socket 的數據接收並不直接影響進程,而是通過改變 eventpoll 的就緒列表來改變進程狀態。

當 socket 接收到數據,中斷程序一方面修改 rdlist,另一方面喚醒 eventpoll 等待隊列中的進程,進程 A 再次進入運行狀態(如下圖)。也因爲 rdlist 的存在,進程 A 可以知道哪些 socket 發生了變化。

Java中select BUG

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