五種IO模型和三種實現方式


I/O介紹

操作系統分為兩種I/O

  • 網絡IO:本質是socket讀取

  • 磁盤IO:DMA操作讀取

每次I/O過程

  1. (DMA)將數據從磁盤文件先加載至內核內存空間(緩沖區),等待數據准備完成,時間較長
  2. (CPU)將數據從內核緩沖區復制到用戶空間的進程的內存中,時間較短

I/O模型

同步/異步:關注的是消息通信機制

  • 同步:synchronous,調用者等待被調用者返回消息,才能繼續執行
  • 異步:asynchronous,被調用者通過狀態、通知或回調機制主動通知調用者被調用者的運行狀態

阻塞/非阻塞:關注調用者在等待結果返回之前所處的狀態

  • 阻塞:blocking,指IO操作需要徹底完成后才返回到用戶空間,調用結果返回之前,調用者被掛起
  • 非阻塞:nonblocking,指IO操作被調用后立即返回給用戶一個狀態值,無需等到IO操作徹底完成,最終的調用結果返回之前,調用者不會被掛起

五種I/O模型:

  • 同步阻塞型、同步非阻塞型、IO多路復用型、信號驅動I/O型、異步I/O型

同步阻塞型

  • 同步阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操作時被阻塞

  • 用戶線程通過系統調用read發起IO讀操作,由用戶空間轉到內核空間。內核等到數據包到達后,然后將接收的數據拷貝到用戶空間,完成read操作

  • 用戶需要等待read將數據讀取到buffer后,才繼續處理接收的數據。整個IO請求的過程中,用戶線程是被阻塞的,這導致用戶在發起IO請求時,不能做任何事情,對CPU的資源利用率不夠

同步非阻塞模型

  • 用戶線程發起IO請求時立即返回。但並未讀取到任何數據,用戶線程需要不斷地發起IO請求,直到數據到達后,才真正讀取到數據,繼續執行。即“輪詢”機制

  • 整個IO請求的過程中,雖然用戶線程每次發起IO請求后可以立即返回,但是為了等到數據,仍需要不斷地輪詢、重復請求,消耗了大量的CPU的資源

  • 是比較浪費CPU的方式,一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性

IO多路復用型

  • IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件准備讀取,就通知該進程

  • 多個連接共用一個等待機制,本模型會阻塞進程,但是進程是阻塞在select或者poll這兩個系統調用上,而不是阻塞在真正的IO操作上

  • 用戶首先將需要進行IO操作添加到select中,繼續執行做其他的工作(異步),同時等待select系統調用返回。當數據到達時,IO被激活,select函數返回。用戶線程正式發起read請求,讀取數據並繼續執行

  • 從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視IO,以及調用select函數的額外操作,效率更差。並且阻塞了兩次,但是第一次阻塞在select上時,select可以監控多個IO上是否已有IO操作准備就緒,即可達到在同一個線程內同時處理多個IO請求的目的。而不像阻塞IO那種,一次只能監控一個IO

  • 雖然上述方式允許單線程內處理多個IO請求,但是每個IO請求的過程還是阻塞的(在select函數上阻塞),平均時間甚至比同步阻塞IO模型還要長。如果用戶線程只是注冊自己需要的IO請求,然后去做自己的事情,等到數據到來時再進行處理,則可以提高CPU的利用率

  • IO多路復用是最常使用的IO模型,但是其異步程度還不夠“徹底”,因它使用了會阻塞線程的select系統調用。因此IO多路復用只能稱為異步阻塞IO模型,而非真正的異步IO

信號驅動I/O型

  • 信號驅動IO:signal-driven I/O

  • 用戶進程可以通過sigaction系統調用注冊一個信號處理程序,然后主程序可以繼續向下執行,當有IO操作准備就緒時,由內核通知觸發一個SIGIO信號處理程序執行,然后將用戶進程所需要的數據從內核空間拷貝到用戶空間

  • 此模型的優勢在於等待數據報到達期間進程不被阻塞。用戶主程序可以繼續執行,只要等待來自信號處理函數的通知

  • 該模型並不常用

異步I/O型

  • 異步IO與信號驅動IO最主要的區別是信號驅動IO是由內核通知何時可以進行IO操作,而異步IO則是由內核告訴用戶線程IO操作何時完成。信號驅動IO當內核通知觸發信號處理程序時,信號處理程序還需要阻塞在從內核空間緩沖區復制數據到用戶空間緩沖區這個階段,而異步IO直接是在第二個階段完成后,內核直接通知用戶線程可以進行后續操作了

  • 相比於IO多路復用模型,異步IO並不十分常用,不少高性能並發服務程序使用IO多路復用模型+多線程任務處理的架構基本可以滿足需求。目前操作系統對異步IO的支持並非特別完善,更多的是采用IO多路復用模型模擬異步IO的方式(IO事件觸發時不直接通知用戶線程,而是將數據讀寫完畢后放到用戶指定的緩沖區中)

五種I/O模型

常用的是IO多路復用型,比如Nginx

實現I/O模型的方式

  • Select:Linux實現對應,I/O復用模型,BSD4.2最早實現,POSIX標准,一般操作系統均有實現

  • Poll:Linux實現,對應I/O復用模型,System V unix最早實現

  • Epoll:Linux特有,對應I/O復用模型,具有信號驅動I/O模型的某些特性

  • Kqueue:FreeBSD實現,對應I/O復用模型,具有信號驅動I/O模型某些特性

  • /dev/poll:SUN的Solaris實現,對應I/O復用模型,具有信號驅動I/O模型的某些特性

  • Iocp Windows實現,對應第5種(異步I/O)模型

Apache使用的是Select,Nginx使用的是Epoll。所以Nginx支持C10K(10K Connections)

\ select poll epoll
操作方式 遍歷 遍歷 回調
底層實現 數組 鏈表 哈希表
IO效率 每次調用進行線性遍歷,時間復雜度為O(n) 每次調用進行線性遍歷,時間復雜度為O(n) 事件通知方式,每當fd就緒,系統注冊的回調函數就會被調用,將就緒fd放到dllis里面,時間復雜度O(1)
最大連接數 1024(x86)或2048(x64) 無上限 無上限
fd拷貝 每次調用select,都需要把fd集合從用戶態拷貝到內核態 每次調用poll,都需要把fd集合從用戶態復制到內核態 調用epoll_ctl時復制進內核並保存,之后每次epoll_wait不復制

Select

POSIX所規定,目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點,本質上是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理

缺點:

  • 單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024(多年生產環境得到的最佳值),可以通過修改宏定義FD_SETSIZE,再重新編譯內核實現,但是這樣也會造成效率的降低

  • 單個進程可監視的fd數量被限制,默認是1024,修改此值需要重新編譯內核

  • 對socket是線性掃描,即采用輪詢的方法,效率較低

  • select 采取了內存拷貝方法來實現內核將 FD 消息通知給用戶空間,這樣一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大

Poll

  • 本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態

  • 其沒有最大連接數的限制,原因是它是基於鏈表來存儲的

  • 大量的fd的數組被整體復制於用戶態和內核地址空間之間,而不管這樣的復制是不是有意義

  • poll特點是“水平觸發”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd

  • 邊緣觸發:只通知一次

Epoll

在Linux 2.6內核中提出的select和poll的增強版本

  • 支持水平觸發LT和邊緣觸發ET,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變為就需態,並且只會通知一次

  • 使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知

優點::

  • 沒有最大並發連接的限制:能打開的FD的上限遠大於1024(1G的內存能監聽約10萬個端口),具體查看/proc/sys/fs/file-max,此值和系統內存大小相關

  • 效率提升:非輪詢的方式,不會隨着FD數目的增加而效率下降;只有活躍可用的FD才會調用callback函數,即epoll最大的優點就在於它只管理“活躍”的連接,而跟連接總數無關

  • 內存拷貝,利用mmap(Memory Mapping)加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM