select&epoll&poll

內核空間和用戶空間

如今操做系統都是採用虛擬存儲器,那麼對 32 位操做系統而言,它的尋址空間(虛擬地址空間,或叫線性地址空間)爲 4G(2的32次方)。也就是說一個進程的最大地址空間爲 4G。操做系統的核心是內核(kernel),它獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證保證內核的安全,如今的操做系統通常都強制用戶進程不能直接操做內核。具實現方式基本都是由操做系統將虛擬地址空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對 Linux 操做系統而言,最高的 1G 字節(從虛擬地址 0xC0000000 到 0xFFFFFFFF),供內核使用,稱爲內核空間。而較低的 3G 字節(從虛擬地址 0x00000000 到 0xBFFFFFFF)由各個進程使用,稱爲用戶空間。
每一個進程的 4G 地址空間中,最高 1G 都是同樣的,即內核空間。只有剩餘的 3G 才歸進程本身使用。換句話說就是, 最高 1G 的內核空間是被全部進程共享的!linux

 

 

進程切換

爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換(process switch)、任務切換(task switch)或上下文切換(content switch)。所以能夠說,任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。數組

從一個進程的運行轉到另外一個進程上運行,這個過程當中通過下面這些變化:
1. 保存處理機上下文,包括程序計數器和其餘寄存器。
2. 更新PCB信息。
3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
4. 選擇另外一個進程執行,並更新其PCB。
5. 更新內存管理的數據結構。
6. 恢復處理機上下文。緩存

注:總而言之進程間的切換就是很耗費系統資源安全

 

進程的阻塞

正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則系統將自動執行阻塞(Block),使本身由運行狀態變爲阻塞狀態。因此進程的阻塞,是進程自身的一種主動行爲,也只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的服務器

 

文件描述符fd

文件描述符(File descriptor)是一個用於描述指向文件的引用。文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核中爲每個進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。網絡

 

緩存 I/O

緩存 I/O 又被稱做標準 I/O,大多數文件系統的默認 I/O 操做都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操做系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。數據結構

緩存 I/O 的缺點:
數據在傳輸過程當中須要在應用程序地址空間和內核進行屢次數據拷貝操做,這些數據拷貝操做所帶來的 CPU 以及內存開銷是很是大的。多線程

 

栗子: 一次IO訪問(以read舉例),數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。因此說,當一個read操做發生時,它會經歷兩個階段:
1. 等待數據準備 (Waiting for the data to be ready)
2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)併發

所以linux系統產生五種網絡模式的方案。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路複用( IO multiplexing)
- 信號驅動 I/O( signal driven IO)
- 異步 I/O(asynchronous IO)異步

 

阻塞:

一個函數在等待某些事情的返回值的時候會被 阻塞. 函數被阻塞的緣由有不少: 網絡I/O,磁盤I/O,互斥鎖等.事實上 每一個 函數在運行和使用CPU的時候都或多或少都會被阻塞(舉個極端的例子來講明爲何對待CPU阻塞要和對待通常阻塞同樣的嚴肅: 好比密碼哈希函數 bcrypt, 須要消耗幾百毫秒的CPU時間,這已經遠遠超過了通常的網絡或者磁盤請求時間了).

 

異步:函數在會在某些事情完成以前就返回,僅需在函數中觸發這個事情的調用便可,而再也不關心執行結果如何

 

 

IO多路複用

IO多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程。 多路複用最高效的是:它能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態, select()函數就能夠返回。

IO多路複用適用以下場合:

  (1)當客戶端須要同時處理多個文件描述符的輸入輸出操做時(通常是交互式輸入和網絡套接口),必須使用I/O複用。

  (2)當程序須要同時進行多個套接字的操做的時候。

  (3)若是一個TCP服務器既要處理監聽套接口,又要處理已鏈接套接口,通常也要用到I/O複用。

  (4)若是一個服務器須要同時使用TCP和UDP協議。

  (5)若是一個服務器要處理多個服務或者多個協議的時候。

  與多進程和多線程技術相比,I/O多路複用技術的最大優點是系統開銷小,系統沒必要建立進程/線程,也沒必要維護這些進程/線程,從而大大減少了系統的開銷。

  select/poll是順序掃描fd是否就緒,並且支持的fd數量有限。而epoll使用基於事件驅動方式代替順序掃描,所以性能更高。當fd就緒時,當即回調函數rollback。



 

select、poll、epoll都是IO多路複用的機制,I/O多路複用就是經過一種機制,讓一個進程能夠監視多個描述符的讀/寫等事件,一旦某個描述符就緒(通常是讀或者寫事件發生了),就可以通知程序進行相應的讀寫操做。但select,epoll本質上都是同步I/O,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需本身負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

 

select

select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時函數返回(timeout指定等待時間,若是當即返回設爲null便可),當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符。

select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢。select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制,可是這樣也會形成效率的下降。

select本質上是經過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:

  1. select最大的缺陷就是單個進程所打開的FD是有必定限制的,它由FD_SETSIZE設置,默認值是1024。通常來講這個數目和系統內存關係很大,具體數目能夠cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

 

  1. socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低。當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZESocket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。(若是可以給套接字註冊某個回調函數,當他們活躍時自動完成相關操做,就避免了輪訓,這正是epoll與kqueue作的。)

  3. 須要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大。

 

簡單版:

  1. 鏈接數受限
  2. 查找配對速度慢
  3. 數據由內核拷貝到用戶態

 

 

 

poll

基本原理:poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,而後查詢每一個fd對應的設備狀態,若是設備就緒則在設備等待隊列中加入一項並繼續遍歷,若是遍歷完全部fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了屢次無謂的遍歷。

它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的,可是一樣有一個缺點:

  1. 大量的fd數組被總體複製於用戶態和內核地址空間之間,而無論這樣的複製是否是有意義。

  2. poll還有一個特色是「水平觸發」若是報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

  3. poll改善了select的第一個鏈接數受限的缺點

注意:select和poll都須要在返回後,經過遍歷文件描述符來獲取已經就緒的socket。事實上,同時鏈接的大量客戶端在同一時刻可能只有不多的處於就緒狀態,所以隨着監視的描述符數量的增加,其效率也會線性降低。




epoll

基本原理:在 select/poll中,進程只有在調用必定的方法後,內核纔對全部監視的文件描述符進行掃描,而epollepoll更加靈活,沒有描述符限制,事先經過epoll_ctl()來註冊一個文件描述符,使用一個文件描述符管理多個描述符,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait() 時便獲得通知。(此處去掉了遍歷文件描述符,而是經過監聽回調的的機制。這正是epoll的魅力所在。)

 

epoll的優勢:

  1. 監視的描述符數量不受限制,沒有最大併發鏈接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口,具體數目能夠 cat /proc/sys/fs/file-max察看)。

  2. 效率提高,不是採用輪詢的方式,IO的效率不會隨着監視fd的數量的增加而降低,只有活躍可用的FD纔會調用callback函數。即:epoll最大的優勢在於它只關心「活躍」的鏈接,而跟鏈接總數無關,所以在實際的網絡環境中,epoll的效率就會遠遠高於select和poll。

  3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減小了複製的開銷。

 

epoll對文件描述符的操做:

LT模式:默認模式,當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序能夠不當即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。

     .socket的接收緩衝區狀態變化時觸發讀事件,即空的接收緩衝區剛接收到數據時觸發讀事件

     .socket的發送緩衝區狀態變化時觸發寫事件,即滿的緩衝區剛空出空間時觸發讀事件

     僅在緩衝區狀態變化時觸發事件,好比數據緩衝去從無到有的時候(不可讀-可讀)

LT(level triggered)是缺省的工做方式,並同時支持block和no-block socket。此種模式下,內核會告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不作任何操做,內核仍是會繼續通知你。

 

ET模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。

     .socket接收緩衝區不爲空,有數據可讀,則讀事件一直觸發

     .socket發送緩衝區不滿能夠繼續寫入數據,則寫事件一直觸發

      epoll_wait返回的事件就是socket的狀態

ET(edge-triggered)是高速工做方式,只支持no-block socket 。在這種模式下,當描述符從 未就緒變爲就緒時,內核會經過epoll告訴你,而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,直到你作了某些操做致使那個文件描述符再也不爲就緒狀態了(好比,你在發送,接收或者接收請求,或者發送接收的數據少於必定量時致使了一個EWOULDBLOCK 錯誤)。可是請注意,若是一直不對這個fd執行IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once)。ET模式在很大程度上減小了epoll事件被重複觸發的次數,所以效率要比LT模式高 。epoll工做在ET模式的時候,必須使用非阻塞套接口, 以免因爲一個文件句柄的 阻塞讀/阻塞寫操做 把處理多個文件描述符的任務阻塞。
 


epoll解決的問題:
  • epoll沒有fd數量限制,每一個epoll監聽一個fd,因此最大數量與能打開的fd數量有關,一個g的內存的機器上,能打開10萬個左右

  • epoll不須要每次都從用戶空間將fd set複製到內核空間,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、epoll區別

 selectepoll都是I/O多路複用的方式,可是select是經過不斷輪詢監聽socket實現,epoll是當socket有變化時經過回掉的方式主動告知用戶進程實現

  1. 支持一個進程所能打開的最大鏈接數
輸入圖片說明
 
 
 
  1. FD劇增後帶來的IO效率問題
輸入圖片說明
 
 
 
  1. 消息傳遞方式
輸入圖片說明
 
 

使用場景:

  1. 表面上看epoll的性能最好,可是在鏈接數少而且鏈接都十分活躍的狀況下,select和poll的性能會比epoll好,畢竟epoll的通知機制須要不少函數回調。

  2. select低效是由於每次它都須要輪詢,而epoll採用的是被動觸發方式,只須要查看就緒隊列中是否加入了新成員便可。

  3. select, poll是爲了解決同時大量IO的情況(尤爲網絡服務器),可是隨着鏈接數越多,性能越差
  4. epoll是select和poll的改進方案,在 linux 上能夠取代 select 和 poll,能夠處理大量鏈接的性能問題
本站公眾號
   歡迎關注本站公眾號,獲取更多信息