![](http://static.javashuo.com/static/loading.gif)
I/O介紹
網絡IO:本質是socket讀取數組
磁盤IO:服務器
每次IO,都要經由兩個階段:網絡
第一步:將數據從磁盤文件先加載至內核內存空間(緩衝區),等待數據準備完成,時間較長數據結構
第二步:將數據從內核緩衝區複製到用戶空間的進程的內存中,時間較短多線程
I/O模型
同步/異步:關注的是消息通訊機制架構
同步:synchronous,調用者等待被調用者返回消息,才能繼續執行併發
異步:asynchronous,被調用者經過狀態、通知或回調機制主動通知調用者被調用者的運行狀態app
阻塞/非阻塞:關注調用者在等待結果返回以前所處的狀態異步
阻塞:blocking,指IO操做須要完全完成後才返回到用戶空間,調用結果返回socket
以前,調用者被掛起
非阻塞:nonblocking,指IO操做被調用後當即返回給用戶一個狀態
同步阻塞IO模型blocking I/O
![](http://static.javashuo.com/static/loading.gif)
- 同步阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操做時被阻塞
- 用戶線程經過系統調用read發起IO讀操做,由用戶空間轉到內核空間。內核等到數據包到達後,而後將接收的數據拷貝到用戶空間,完成read操做
- 用戶須要等待read將數據讀取到buffer後,才繼續處理接收的數據。整個IO請求的過程當中,用戶線程是被阻塞的,這致使用戶在發起IO請求時,不能作任何事情,對CPU的資源利用率不夠
同步非阻塞IO模型nonblocking I/O
![](http://static.javashuo.com/static/loading.gif)
- 用戶線程發起IO請求時當即返回。但並未讀取到任何數據,用戶線程須要不斷地發起IO請求,直到數據到達後,才真正讀取到數據,繼續執行。即 「輪詢」機制
- 整個IO請求的過程當中,雖然用戶線程每次發起IO請求後能夠當即返回,可是爲了等到數據,仍須要不斷地輪詢、重複請求,消耗了大量的CPU的資源
- 是比較浪費CPU的方式,通常不多直接使用這種模型,而是在其餘IO模型中使用非阻塞IO這一特性
IO多路複用模型multiplexing
![](http://static.javashuo.com/static/loading.gif)
- 多個鏈接共用一個等待機制,本模型會阻塞進程,可是進程是阻塞在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
- IO多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,就通知該進程
IO多路複用適用以下場合
- 當客戶端處理多個描述符時(通常是交互式輸入和網絡套接口),必須使用I/O複用
- 當一個客戶端同時處理多個套接字時,此狀況可能的但不多出現
- 當一個TCP服務器既要處理監聽套接口,又要處理已鏈接套接口,通常也要用到I/O複用
- 當一個服務器即要處理TCP,又要處理UDP,通常要使用I/O複用
- 當一個服務器要處理多個服務或多個協議,通常要使用I/O複用
信號驅動IO模型signal-driven I/O
![](http://static.javashuo.com/static/loading.gif)
- 用戶進程能夠經過sigaction系統調用註冊一個信號處理程序,而後主程序能夠繼續向下執行,當有IO操做準備就緒時,由內核通知觸發一個SIGIO信號處理程序執行,而後將用戶進程所須要的數據從內核空間拷貝到用戶空間
- 此模型的優點在於等待數據報到達期間進程不被阻塞。用戶主程序能夠繼續執行,只要等待來自信號處理函數的通知
- 該模型並不經常使用
異步IO模型
![](http://static.javashuo.com/static/loading.gif)
- 異步IO與信號驅動IO最主要的區別是信號驅動IO是由內核通知什麼時候能夠進行IO操做,而異步IO則是由內核告訴咱們IO操做什麼時候完成了。具體來講就是,信號驅動IO當內核通知觸發信號處理程序時,信號處理程序還須要阻塞在從內核空間緩衝區拷貝數據到用戶空間緩衝區這個階段,而異步IO直接是在第二個階段完成後內核直接通知能夠進行後續操做了
- 相比於IO多路複用模型,異步IO並不十分經常使用,很多高性能併發服務程序使用IO多路複用模型+多線程任務處理的架構基本能夠知足需求。何況目前操做系統對異步IO的支持並不是特別完善,更多的是採用IO多路複用模型模擬異步IO的方式(IO事件觸發時不直接通知用戶線程,而是將數據讀寫完畢後放到用戶指定的緩衝區中)
五種I/O模型比較
![](http://static.javashuo.com/static/loading.gif)
I/O模型的具體實現
主要實現方式有如下幾種:
Select:Linux實現對應,I/O複用模型,BSD4.2最先實現
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)模型
select/poll/epoll的比較:
![](http://static.javashuo.com/static/loading.gif)
select
目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,本質上是經過設置或者檢查存放fd標誌位的數據結構來進行下一步處理
缺點:
- 單個進程可監視的fd數量被限制,即能監聽端口的數量有限
/proc/sys/fs/file-max
- 對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萬個端口)
- 效率提高:非輪詢的方式,不會隨着FD數目的增長而效率降低;只有活躍可用的FD纔會調用callback函數,即epoll最大的優勢就在於它只管理「活躍」的鏈接,而跟鏈接總數無關
- 內存拷貝,利用mmap(Memory Mapping)加速與內核空間的消息傳遞;即epoll使用mmap減小複製開銷
![](http://static.javashuo.com/static/loading.gif)