1. 程式人生 > >select和epoll 原理概述&優缺點比較

select和epoll 原理概述&優缺點比較

這個問題在面試跟網路程式設計相關的崗位的時候基本都會被問到,剛剛看到一個很好的比喻:

就像收本子的班長,以前得一個個學生地去問有沒有本子,如果沒有,它還得等待一段時間而後又繼續問,現在好了,只走一次,如果沒有本子,班長就告訴大家去那裡交本子,當班長想起要取本子,就去那裡看看或者等待一定時間後離開,有本子到了就叫醒他,然後取走。

也許在細節方面不是特別恰當,但是總的來說,比較形象地說出了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慢了(就像我們常常說快排比插入排序快,但是在特定情況下這並不成立)。