I/O介紹
I/O主要為:網絡IO(本質是socket文件讀取)、磁盤IO
每次IO,都要經由兩個階段:
第一步:將數據從文件先加載至內核內存空間(緩沖區),等待數據准備完成,時間較長
第二步:將數據從內核緩沖區復制到用戶空間的進程的內存中,時間較短
I/O模型
同步/異步:關注的是消息通信機制
同步:synchronous,調用者等待被調用者返回消息,才能繼續執行
異步:asynchronous,被調用者通過狀態、通知或回調機制主動通知調用者被調用者的運行狀態
阻塞/非阻塞:關注調用者在等待結果返回之前所處的狀態
阻塞:blocking,指IO操作需要徹底完成后才返回到用戶空間,調用結果返回之前,調用者被掛起
非阻塞:nonblocking,指IO操作被調用后立即返回給用戶一個狀態值,無需等到IO操作徹底完成,最終的調用結果返回之前,調用者不會被掛起
注意:同步/異步、阻塞/非阻塞是兩組針對IO的處理邏輯,在這兩對邏輯上建立起了不同的IO模型,他們本身並不是I/O模型。
I/O模型分類:
發起系統調用的是運行在系統上的某個
應用的進程、對象是
磁盤上的數據、獲取數據需要
通過I/O、整個
過程就是
應用等待獲取磁盤數據。針對整個過程中應用進程的狀態不同,可以分為:
阻塞型
非阻塞型
復用型
信號驅動型
異步
一:阻塞I/O模型:在等待數據和數據復制兩個階段都處於阻塞狀態
1、阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操作時被阻塞
2、用戶線程通過系統調用read發起IO讀操作,由用戶空間轉到內核空間。內核等到數據包到達后,然后將接收的數據拷貝到用戶空間,完成read操作。
用戶需要等待read將數據讀取到buffer后,才繼續處理接收的數據。整個IO請求的過程中,用戶線程是被阻塞的,這導致用戶在發起IO請求時,不能做任何事情,對CPU的資源利用率不夠
3、優點:程序簡單,在阻塞等待數據期間進程/線程掛起,基本不會占用 CPU 資源
4、缺點:每個連接需要獨立的進程/線程單獨處理,當並發請求量大時為了維護程序,內存、線程切換開銷較大,這種模型在實際生產中很少使用
非阻塞IO模型:在等待數據和數據復制兩個階段都處於阻塞狀態
1、用戶線程發起IO請求時立即返回。但並未讀取到任何數據,用戶線程需要不斷地發起IO請求,直到數據到達后,才真正讀取到數據,繼續執行。即 “輪詢”機制
2、存在兩個問題:如果有大量文件描述符都要等,那么就得一個一個的read。這會帶來大量的Context Switch(read是系統調用,每調用一次就得在用戶態和核心態切換一次)。輪詢的時間不好把握。這里是要猜多久之后數據才能到。等待時間設的太長,程序響應延遲就過大;設的太短,就會造成過於頻繁的重試,干耗CPU而已
3、是比較浪費CPU的方式,一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性
IO多路復用模型:最常用
重點在於select,select可以監控多個IO上是否已有IO操作准備就緒,即可達到在同一個線程內同時處理多個IO請求的目的。而不像阻塞IO那種,一次只能監控一個IO.。
1、IO多路復用(IO Multiplexing) :是一種機制,程序注冊一組socket文件描述符給操作系統,表示“我要監視這些fd是否有IO事件發生,有了就告訴程序處理”
2、IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件准備讀取,就通知該進程
3、多個連接共用一個等待機制,本模型會阻塞進程,但是進程是阻塞在select或者poll這兩個系統調用上,而不是阻塞在真正的IO操作上
4、用戶首先將需要進行IO操作添加到select中,同時等待select系統調用返回。當數據到達時,IO被激活,select函數返回。用戶線程正式發起read請求,讀取數據並繼續執行
5、IO多路復用是最常使用的IO模型,但是其異步程度還不夠“徹底”,因它使用了會阻塞線程的select系統調用。因此IO多路復用只能稱為異步阻塞IO模型,而非真正的異步IO
信號驅動IO:signal-driven I/O
1、用戶進程可以通過sigaction系統調用注冊一個信號處理程序,然后主程序可以繼續向下執行,當有IO操作准備就緒時,由內核通知觸發一個SIGIO信號處理程序執行,然后將用戶進程所需要的數據從內核空間拷貝到用戶空間
2、此模型的優勢在於等待數據報到達期間進程不被阻塞。用戶主程序可以繼續執行,只要等待來自信號處理函數的通知
3、對於 TCP 而言,信號驅動的 I/O 方式近乎無用,因為導致這種通知的條件為數眾多,每一個來進行判別會消耗很大資源,與前幾種方式相比優勢盡失
4、優點:線程並沒有在等待數據時被阻塞,可以提高資源的利用率
5、缺點:信號 I/O 在大量 IO 操作時可能會因為信號隊列溢出導致沒法通

異步IO模型
異步IO與信號驅動IO最主要的區別是信號驅動IO是由內核通知應用程序何時可以進行IO操作,而異步IO則是由內核告訴用戶線程IO操作何時完成。信號驅動IO當內核通知觸發信號處理程序時,信號處理程序還需要阻塞在從內核空間緩沖區拷貝數據到用戶空間緩沖區這個階段,而異步IO直接是在第二個階段完成后,內核直接通知用戶線程可以進行后續操作了
由 POSIX 規范定義,應用程序告知內核啟動某個操作,並讓內核在整個操作(包括將數據從內核拷貝到應用程序的緩沖區)完成后通知應用程序
優點:異步 I/O 能夠充分利用 DMA 特性,讓 I/O 操作與計算重疊
缺點:要實現真正的異步 I/O,操作系統需要做大量的工作。目前 Windows 下通過 IOCP 實現了真正的異步 I/O,在 Linux 系統下,Linux 2.6才引入,目前AIO 並不完善,因此在 Linux 下實現高並發網絡編程時以 IO 復用模型模式+多線程任務的架構基本可以滿足需求

五種I/O模型對比
這五種 I/O 模型中,越往后,阻塞越少,理論上效率也是最優前四種屬於同步I/O,因為其中真正的 I/O 操作(recvfrom)將阻塞進程/線程,只有異步 I/O 模型才與 POSIX 定義的異步 I/O 相匹配
I/O模型的具體實現
主要實現方式有以下幾種:
Select:Linux實現對應,I/O復用模型,BSD4.2最早實現,POSIX標准,一般操作系統均有實現,
httpd使用
Poll:Linux實現,對應I/O復用模型,System V unix最早實現
Epoll:Linux特有,對應I/O復用模型,具有信號驅動I/O模型的某些特性,
nginx使用
Kqueue:FreeBSD實現,對應I/O復用模型,具有信號驅動I/O模型某些特性
/dev/poll:SUN的Solaris實現,對應I/O復用模型,具有信號驅動I/O模型的某些特性
Iocp Windows實現,對應第5種(異步I/O)模型
select/poll/epoll
1、三種實現方式的對比。
①三種都是I/O多路復用模型的是實現
②epoll在等待數據階段,使用了信號驅動的特性IO效率高。才使得nginx支持高並發。

2、三種實現方式的介紹
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減少復制開銷
零拷貝
傳統Linux中 I/O 的問題
傳統的 Linux 系統的標准 I/O 接口(read、write)是基於數據拷貝的,也就是數據都是 copy_to_user 或者 copy_from_user,這樣做的好處是,通過中間緩存的機制,減少磁盤 I/O 的操作,但是壞處也很明顯,大量數據的拷貝,用戶態和內核態的頻繁切換,會消耗大量的 CPU 資源,嚴重影響數據傳輸的性能,統計表明,在Linux協議棧中,數據包在內核態和用戶態之間的拷貝所用的時間甚至占到了數據包整個處理流程時間的57.1%
什么是零拷貝
零拷貝就是上述問題的一個解決方案,通過盡量避免拷貝操作來緩解 CPU 的壓力。零拷貝並沒有真正做到“0”拷貝,它更多是一種思想,很多的零拷貝技術都是基於這個思想去做的優化。
原始數據拷貝:
一次網絡或者磁盤io需要先從磁盤獲取數據到內核的緩存區,再拷貝到用戶空間的緩沖區。這是一次完成的磁盤IO。用戶空間程序處理后,構建相應報文,回復客戶端。這也要經過context切換和復制。

MMAP:Memory Mapping
數據到達內核的緩存后不會,復制到用戶空間的緩存。而是通過內存映射,告訴user數據的位置。減少了內核空間數據向用戶空間的復制,直接再內核的內存區域中復制到socket緩存,發送給客戶端。
SENDFILE:直接再內核空間內復制數據,相應請求
DMA 輔助的 SENDFILE,省去了kerner 緩存到socket緩存的復制。