細談Select,Poll,Epoll

  • 阻塞 io 模型 blocking IO
  • 非阻塞 io 模型 nonblocking IO
  • io多路複用模型 IO multiplexing
  • 細談 io 多路複用技術 select 和poll
  • 細談事件驅動--epoll
  • 總結

操做系統在處理io的時候,主要有兩個階段:linux

  • 等待數據傳到io設備
  • io設備將數據複製到user space

咱們通常將上述過程簡化理解爲:服務器

  • 等到數據傳到kernel內核space
  • kernel內核區域將數據複製到user space(理解爲進程或者線程的緩衝區)

而根據這兩個階段而不一樣的操做方法,就會產生多種io模型,本文只討論select,poll,epoll,因此只引出三種io模型。網絡

阻塞 io 模型 blocking IO

最經常使用的也就是阻塞io模型。默認狀況下,全部文件操做都是阻塞的。咱們以套接字接口爲例來說解此模型,在進程空間調用recvfrom,其系統調用知道數據包到達而且被複制到進程緩衝中或者發生錯誤時纔會返回,在此期間會一直阻塞,因此進程在調用recvfrom開始到它返回的整段時間都是阻塞的,所以稱之爲阻塞io模型。多線程

注意:在阻塞狀態下,程序是不會浪費CPU的,cpu只是不執行io操做了,還會去作別的。socket

image.png

應用層有數據過來,會調用recvfrom方法,可是這個時候應用層的數據還沒複製到kernel中,將應用層數據複製到kerne這個階段是須要時間的,因此recvfrom方法會阻塞,當內核中的數據準備好以後,recvfrom方法還不會返回,而是會發起一個系統調用將kernel中的數據複製到進程的緩衝區中,也就是user space,當這個工做完成以後,recvfrom纔會返回並解除程序的阻塞。tcp

因此咱們總結能夠發現,主要就是上面兩個階段函數

  • 應用層數據到kernel
  • kernel複製到user space

阻塞io模型就是將這個兩個過程合併在一塊兒,一塊兒阻塞。 而非阻塞模型則是將第一個過程的阻塞變成非阻塞,第二個階段是系統調用,是必須阻塞的,因此非阻塞模型也是同步的,由於它們在kernel裏的數據準備好以後,進行系統調用,將數據拷貝到進程緩衝區中。性能

非阻塞 io 模型 nonblocking IO

就是對於第一個階段,也就是應用層數據到kernel的過程當中,recvfrom會輪詢檢查,若是kernel數據沒有準備還,就返回一個EWOULDBLOCK錯誤。不斷的輪詢檢查,直到發現kernel中的數據準備好了,就返回,而後進行系統調用,將數據從kernel拷貝到進程緩衝區中。有點類似busy-waiting的方法。spa

image.png

io多路複用模型 IO multiplexing

  • 目的:由於阻塞模型在沒有收到數據的時候就會阻塞卡住,若是一次須要接受多個socket fd的時候,就會致使必須處理完前面的fd,才能處理後面的fd,即便可能後面的fd比前面的fd還要先準備好,因此這樣就會形成客戶端的嚴重延遲。爲了處理多個請求,咱們天然先想到用多線程來處理多個socket fd,可是這樣又會啓動大量的線程,形成資源的浪費,因此這個時候就出現了io多路複用技術。就是用一個進程來處理多個fd的請求。操作系統

  • 應用:適用於針對大量的io請求的狀況,對於服務器必須在同時處理來自客戶端的大量的io操做的時候,就很是適合

image.png

細談 io 多路複用技術 select 和poll

select

select的工做流程: 單個進程就能夠同時處理多個網絡鏈接的io請求(同時阻塞多個io操做)。基本原理就是程序呼叫select,而後整個程序就阻塞了,這時候,kernel就會輪詢檢查全部select負責的fd,當找到一個client中的數據準備好了,select就會返回,這個時候程序就會系統調用,將數據從kernel複製到進程緩衝區。

image.png

下圖爲select同時從多個客戶端接受數據的過程

雖然服務器進程會被select阻塞,可是select會利用內核不斷輪詢監聽其餘客戶端的io操做是否完成。

image.png

Poll介紹

poll的原理與select很是類似,差異以下:

  • 描述fd集合的方式不一樣,poll使用 pollfd 結構而不是select結構fd_set結構,因此poll是鏈式的,沒有最大鏈接數的限制
  • poll有一個特色是水平觸發,也就是通知程序fd就緒後,此次沒有被處理,那麼下次poll的時候會再次通知同個fd已經就緒。

select缺點

  • 根據fd_size的定義,它的大小爲32個整數大小(32位機器爲32*32,全部共有1024bits能夠記錄fd),每一個fd一個bit,因此最大隻能同時處理1024個fd

  • 每次要判斷【有哪些event發生】這件事的成本很高,由於select(polling也是)採起主動輪詢機制

1.每一次呼叫 select( ) 都須要先從 user space把 FD_SET複製到 kernel(約線性時間成本) 爲何 select 不能像epoll同樣,只作一次複製就好呢? 每一次呼叫 select()前,FD_SET均可能更動,而 epoll 提供了共享記憶存儲結構,因此不須要有 kernel 與 user之間的數據溝通

2.而後kernel還要輪詢每一個fd,約線性時間

  • 假設現實中,有1百萬個客戶端同時與一個服務器保持着tcp鏈接,而每個時刻,一般只有幾百上千個tcp鏈接是活躍的,這時候咱們仍然使用select/poll機制,kernel必須在搜尋完100萬個fd以後,才能找到其中狀態是active的,這樣資源消耗大並且效率低下。

對於select和poll的上述缺點,就引進了一種新的技術,epoll技術

細談事件驅動--epoll

epoll 提供了三個函數:

  • int epoll_create(int size); 創建一個 epoll 對象,並傳回它的id

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 事件註冊函數,將須要監聽的事件和須要監聽的fd交給epoll對象

  • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 等待註冊的事件被觸發或者timeout發生

epoll解決的問題:

  • epoll沒有fd數量限制 epoll沒有這個限制,咱們知道每一個epoll監聽一個fd,因此最大數量與能打開的fd數量有關,一個g的內存的機器上,能打開10萬個左右

  • epoll不須要每次都從user space 將fd set複製到內核kernel epoll在用epoll_ctl函數進行事件註冊的時候,已經將fd複製到內核中,因此不須要每次都從新複製一次

  • select 和 poll 都是主動輪詢機制,須要拜訪每一個 FD; epoll是被動觸發方式,給fd註冊了相應事件的時候,咱們爲每個fd指定了一個回調函數,當數據準備好以後,就會把就緒的fd加入一個就緒的隊列中,epoll_wait的工做方式實際上就是在這個就緒隊列中查看有沒有就緒的fd,若是有,就喚醒就緒隊列上的等待者,而後調用回調函數。

  • 雖然epoll。poll。epoll都須要查看是否有fd就緒,可是epoll之因此是被動觸發,就在於它只要去查找就緒隊列中有沒有fd,就緒的fd是主動加到隊列中,epoll不須要一個個輪詢確認。 換一句話講,就是select和poll只能通知有fd已經就緒了,但不能知道到底是哪一個fd就緒,因此select和poll就要去主動輪詢一遍找到就緒的fd。而epoll則是不但能夠知道有fd能夠就緒,並且還具體能夠知道就緒fd的編號,因此直接找到就能夠,不用輪詢。

總結

  • select, poll是爲了解決同時大量IO的情況(尤爲網絡服務器),可是隨着鏈接數越多,性能越差
  • epoll是select和poll的改進方案,在 linux 上能夠取代 select 和 poll,能夠處理大量鏈接的性能問題
相關文章
相關標籤/搜索