原文出處:http://miaoo.in/talk-about-unix-io-model.htmlhtml
在實際應用中,數據操做一般分爲輸入和輸出,那麼以輸入爲例,在操做系統中,一個數據的輸入一般分爲如下兩個過程:linux
a. 等待數據準備好.
b. 將準備好的數據從內核拷貝到用戶空間.緩存
下面咱們將會分別討論 I/O 模型中的兩個大類,即 同步 I/O 與 異步 I/O。異步
1. 同步 I/O函數
同步與異步 I/O 的最大不一樣,就是在在進行數據複製時(即過程 b ),全部的同步 I/O 模型均會發生阻塞。進一步來看,又能夠根據在等待數據準備好時( 過程 a )是否發生阻塞,將同步 I/O 分爲:oop
- 阻塞 I/O ( blocking I/O ). 其在過程 a 與 過程 b 中均會阻塞。
- 非阻塞 I/O ( non-blocking I/O ). 過程 a 不阻塞, 過程 b 阻塞。在過程 a 階段若無數據準備好,則內核當即返回 EWOULDBLOCK 錯誤 (經過設置 errno),用戶進程當即返回於是不會發生阻塞。此時,用戶進程能夠不斷的經過輪詢( polling )的方式查詢數據是否準備好。一旦數據準備就緒, 進程便會進入阻塞模式(即阻塞於過程 b ),進行數據的拷貝直至完成。
- I/O 複用( I/O multiplexing, event-driven ). I/O 複用有時又被稱爲 事件驅動 I/O, 它的最大優點在於,咱們能夠將感興趣的多個I/O事件(更精確的說,應該是 I/O 所對應的文件描述符)註冊到 select/poll/epoll/kqueue 之中某一個系統調用上(不少時候,這些系統調用又被稱爲多路複用器。假設此時咱們選擇了 select() )。此後,調用進程會阻塞在 select() 系統調用之上(而不是阻塞在真正的 I/O 系統調用(如 read(), write() 等)上)。select() 會負責監視全部已註冊的 I/O 事件,一旦有任意一個事件的數據準備好,那麼 select() 會當即返回,此時咱們的用戶進程便可以進行數據的複製操做(過程 b )。總而言之,I/O 複用的優勢就在於能夠同時等待多個I/O事件;而缺點是會進行兩次系統調用(一次 select(), 一次 read() )。
經過上面的討論能夠清楚的看到,同步 I/O 總會有阻塞的過程,這就是「同步」最本質的特徵。post
2.異步 I/O操作系統
如前文所說,異步 I/O 的最大特色在於在過程 a 和過程 b 中, 用戶進程均不阻塞。 用戶進程告知內核啓動某一 I/O 操做, 並讓內核全權代爲執行(包括等待數據及拷貝數據至用戶空間),此後用戶進程能夠當即執行其它的任何操做。等到全部 I/O 過程執行完成後, 內核會通知用戶程。因而可知,在整個過程當中,用戶進程均不阻塞。unix
以上談到了 Unix 系統當中 I/O 操做的具體執行方式。在此基礎之上, 咱們介紹兩種常見的 I/O 事件處理模型。事件處理模型的意義在於咱們從更宏觀的角度來看待實際應用中如何來處理 I/O 事件。咱們仍是以一個讀操做( read() )爲例, 根據 I/O 操做是基於同步或異步,分別介紹以下兩種模型:orm
- Reactor 模式( event loop ) . 基於同步 I/O
- 將要讀的文件描述符註冊到多路複用器中(如 select(), poll(), epoll() )。
- 調用進程阻塞在多路複用器上,其等待已註冊的任意一個 I/O 事件發生。
- 一旦事件發生(即文件描述符變爲可讀, 多路複用器返回), 將調用用戶提供的事件處理函數( event handler)進行實際的 I/O 複製操做。
- 讀取完成後,事件處理函數能夠對數據進行進一步的處理。
- Proactor 模式. 基於異步 I/O
- 用戶進程啓動一個異步讀文件操做, 同時將該操做註冊到多路複用器上。多路複用器並不關心文件是否可讀,而只關心該異步操做是否完成。
- 整個異步讀文件操做由內核完成,用戶進程不須要關心。多路複用器阻塞以等待某個完成通知的到達。
- 當內核完成了整個讀文件操做 – 即數據已經準備好,並已由內核複製到了用戶事先提供的緩衝區後 – 通知多路複用器讀操做已完成。
- 多路複用器再調用相應的事件處理函數( event handler )處理位於用戶緩衝區的數據。
由此咱們能夠看到 Reactor 與 Proactor 最大的區別在於數據準備好後,是由誰來將這些數據複製到用戶空間中的緩存區之中。Reactor 因爲調用的是同步 I/O , 因此當多路複用器因爲數據準備好而返回以後,將會由用戶的事件處理函數自行將數據複製到用戶緩衝區中, 而 Proactor 因爲調用的是異步 I/O , 所以等待及複製數據均由內核完成,用戶進程只須要等待內核的完成通知,此後由事件處理函數對已在緩衝區中的數據進行進一步的處理。
因爲 Proactor 模型須要操做系統提供異步 I/O 的支持,要求較高,故基於上面所描述的思想, 咱們能夠用 Reactor 模型來模擬 Proactor (只須要用戶額外提供用戶緩衝區來存放讀取出的數據)。簡單來講,就是由多路複用器來代替用戶進程來完成實際的 I/O 複製操做。
在 Reactor 模型的第 3 步中,當某個文件描述符可讀使得多路複用器返回以後,多路複用器執行一個非阻塞讀操做, 將數據從內核讀至用戶提供的緩衝區中,此操做完成後通知(調用)對應的事件處理函數,告知其 I/O 操做已完成。 這樣一來,咱們就能夠在各類系統中(不管支持異步 I/O 與否)均對外提供統一的 Proactor 模型接口,而對用戶隱藏其後的具體實現細節。
參考資料
1. < Unix network programming >, Chapter 6.