由select引起的思考

1、前言html

  網絡編程裏一個經典的問題,select,poll和epoll的區別?這個問題剛學習編程時就接觸了,當時看了材料很不明白,許多概念和思想沒有體會,如今在這個階段,再從新回頭看這個問題,有一種豁然開朗的感受,編程

把目前我所能理解到的記錄下來。緩存

  參考資料:https://www.cnblogs.com/zingp/p/6863170.html安全

       http://blog.csdn.net/a837199685/article/details/45954349服務器

       https://www.zhihu.com/question/32163005網絡

       http://shmilyaw-hotmail-com.iteye.com/blog/1896683多線程

       http://www.cnblogs.com/my_life/articles/3968782.html併發

       https://www.cnblogs.com/Anker/p/3265058.html異步

2、從操做系統開始談起socket

  有幾個之後一直談到的概念,有必要先了解,之前就是這個步驟沒作,學習的時候一臉懵逼。

 

  1. 用戶空間和內核空間

    目前操做系統尋址模式爲虛擬尋址,即處理器產生虛擬地址,以後翻譯成物理地址,總線到物理地址處理,處理器拿處處理後的數據

    操做系統 的核心爲內核,能夠訪問受保護的內存空間,能夠訪問底層硬件設施,能夠作任何事情。因此爲了系統的穩定,講道理內核應該被保護起來。

    因此虛擬空間被分爲內核空間和用戶空間。內核空間在最高的1G中,用戶空間在剩下的內存中。

    多數咱們所用的進程在用戶空間處理,把請求發給內核,在內核空間中操做硬件。

  

  2. 進程上下文切換(進程切換)

    掛起當前進程,恢復某個進程,你們都知道這是個開銷大的過程,那具體有哪些步驟呢?

    首先,保存當前進程一些必要信息用以往後恢復,如描繪地址空間的頁表,進程表,文件表等等。

    而後切換頁全局目錄,安裝一個新的地址空間

    最後回覆目標進程的上下文

 

  3. 文件描述符(fd)

    計算機的一個術語,指向文件對象的一個抽象表示。形式上是一個非負整數的索引值,指向文件表中的文件

    當程序打開或建立一個文件時,內核向進程返回一個文件描述符,表明該文件。

    經過操做文件描述符,咱們實現真實操做文件的目的

 

  4.  進程阻塞(process block)

    當某個進程等待一個執行結果時,自身阻塞(不幹事),直到獲得結果,再繼續往下,

    重點是:這個是進程自身行爲,且阻塞時不佔用cpu資源(cpu也不幹事),

    因此I/O請求最拉低性能,俗話說佔着茅坑不拉屎,在計算機裏也是存在的,因此人們設計了多線程,多進程等方案來解決這個問題。

  

  5.  I/O過程

    通常有兩種模式,直接 I/O,緩存 I/O(默認),

    緩存 I/O:

      進程發起系統調用(通知系統我要讀寫了!)

      寫: 進程(數據) -------》 進程緩衝池   ------------》 內核緩衝池 ----------》存儲設備

      讀: 存儲設備(數據)---------》內核緩衝池 ----------》進程緩衝池 ---------》進程

 

    直接 I/O(進程緩存池消失了!):      

      進程發起系統調用(通知系統我要讀寫了!)

      寫: 進程(數據)    ------------》 內核緩衝池 ----------》存儲設備

      讀: 存儲設備(數據)---------》內核緩衝池  ---------》進程

    以上每一個步驟之間都是有可能進程會有阻塞(block)發生,根據不一樣位置的阻塞,就產生了多種網絡模式,以適應於不一樣場景。

 

     

3、 I/O模式(以讀爲例)

   1.  block I/O 

    過程 :  

      進程發起系統調用(通知系統我要讀寫了!)

      讀: 存儲設備(數據)---------》內核緩衝池  ---------》進程------------》內核通知進程ok,進程解除阻塞

                 進程阻塞                   進程阻塞

    解釋:

      進程一直等文件準備好,再繼續下一步。

    應用場景:

      以上原理是一個用戶鏈接的狀況,很容易理解。

      當一個服務器對接多個客戶端的時候:

      初級方案:開多進程(大數據或長時間任務、開銷大,更安全)或多線程(不少鏈接,開銷低,數據放一塊兒不太安全)爲每個客戶端創建一個鏈接來處理。

      不足:高併發的狀況就體現出開銷大,性能低。一個是多線程切換,上下文切換的性能開銷,另外一個是多線程數量大,會佔據大量系統資源

 

      優化方案:採用線程池(減小建立和銷燬線程的頻率)或鏈接池(維持鏈接的緩存池,儘可能重用已有的鏈接),下降系統開銷

      不足:下降開銷仍是有限度的,在這個時代,高併發大,很容易到達瓶頸。

   

   2. non-block I/O   

    過程 :  

      發起系統調用(通知系統我要讀寫了!)

      讀: 存儲設備(數據)-------------》內核緩衝池  -----------》進程-------------》內核通知進程ok,進程解除阻塞

                 非阻塞                              阻塞

    解釋:

      在內核準備數據階段,當即返回一個error給進程,

      所以進程知道內核還沒準備好,

      因此進程再問內核,內核再回error,直到內核準備好,被詢問時返回準備好的信號,進程再接觸阻塞,

    應用場景:

      鏈接量小,沒差異,

      當鏈接數大的時候,這個模式理論上能夠用一個線程實現多個鏈接:

      因爲非阻塞,這個線程能夠循環去詢問全部鏈接目標有沒有準備好,內核都是立馬回覆,error往下,準備好就交給進程,因此不會浪費時間,

      可是,(凡事都有可是),這個簡單的實現方案,效率仍是很低的,畢竟從內核空間到用戶空間仍是block的,並且會極大推高cpu佔用率。

      特別是當響應事件(讀取或者其餘)龐大的時候,執行速度就會很緩慢。

      下面的select等就是基於此想法的發展。

 

   3. I/O multiplexing

    目標:低開銷,高效率得處理高併發請求

    方案:select 、poll、epoll 三種實現方案

    本質:用 select、poll、epoll 去監聽全部 socket對象,當socket對象發生變化時,通知用戶進程處理。

    特徵:

       select(最先出現):多平臺支持

                經過輪詢,效率較低

                處理鏈接數量有限制,默認1024個,

                大量用戶態和內核態fd的拷貝,性能低,

                返回的是全部句柄列表,沒有告訴是哪個發生變化,用戶進程還得再次遍歷。

       poll(略微改進):改進了數量鏈接限制,作到了數量無限制

       epoll(改進全部缺點):當socket變化時,通知進程哪個完成了,

                  數量無限制(爲系統最大打開文件數量)

                  fd句柄只拷貝一次,性能高

    性能對比:

      

      橫座標是鏈接數,Dead Connections 是軟件命名的,縱座標是此時處理鏈接數

      能夠看出 epoll 性能比較穩定,並且性能較優。

    仔細過程討論:

        這裏只討論大體原理,具體實現不一樣語言有不一樣的差異。(不是由於我沒作過,不是的)

        首先。在多路複用模型中,對於每個 socket,通常都設置成爲 non-blocking,可是,整個用戶的 process 實際上是一直被 block 的

        即用戶進程被select、poll阻塞,可是select、poll是非阻塞的,他們不斷輪詢、掛起來完成工做。

 

        select:

          1. 從用戶態拷貝 fd_set 至內核空間(告訴內核要監聽的socket)

          2..註冊回調函數pollwait(將進程掛到等待隊列中,當socket準備好後(執行mask狀態碼判斷),再喚醒進程)

          3. 內核遍歷fd,調用每個的poll方法(本質上是pollwait回調函數,返回值socket的mask狀態掩碼,即如今準備好了沒,給fd_set賦值)

          4. 當無可讀寫mask碼(沒有任何準備好的),select睡眠,等睡眠時間到,再次醒來輪詢fd-set

          5. 有值時返回fd_set(已經賦值完,例如能夠讀的value爲1)、將其拷貝至用戶空間

          6. 用戶進程循環fd_set,

        分析:

          每次循環都要執行上面流程,

          一次循環兩次拷貝fi_set,即每次監聽都從新告訴內核要監聽的事件,在用戶量很大的時候是一個很大的開銷

          返回全部的fd_set,卻沒有告訴進程哪個是完成的,進程還得循環判斷,用戶量很大(十萬,百萬)的時候,性能過低

          所以,select只支持1024個鏈接。

          這也解釋了上圖中爲何鏈接量越大,性能越低的現象,許多時間用來處理無活躍的鏈接、循環判斷中,在高併發低活躍的場景中尤其如此。

        poll:

          將fd_set結構改成pollfd結構,能夠不限數量,可是其餘問題咩有解決。

        epoll:

          改進:

            fd只拷貝一次(開始就告訴內核全部註冊事件,監聽對象)。

            只返回包含全部變化的fd的鏈表。

            鏈接無限制

          epoll提供三個函數

          epoll_create(句柄),開始是有size參數,說明fd數量,如今內核動態分配,

          epoll_ctl(註冊事件類型),註冊監聽事件

          epoll_wait(等待事件發生),捕捉fd信號,

    三者區別小結:

      select、poll 孿生兄弟,有許多缺點,優勢很少,應用場景也很少。是時代的產物

      epoll  是進階版,可是隻有Linux有,

      具體狀況具體分析。

  

  4. 異步io

    解釋:進程徹底不阻塞,請求發完就去作其餘事,等數據所有準備好,內核發消息給進程,進程接着處理,

    實現:據說很複雜,沒研究。

 

4、總結和挖坑

  研究了一些操做系統的概念,研究了I/O模式,着重研究了select、poll、epoll 的區別,

  有時間 具體實現和操做,實踐出真知,許多細節可能還有謬誤,待之後水平上升,再來修改。

相關文章
相關標籤/搜索