本文是筆者的第一篇博文,在這篇文章的大部份內容基於steven大神的《Unix Network Programming》。一來是對書本內容的整理與概括。二來也是爲接下來的博文奠基基礎。linux
在實際應用中,數據操做一般分爲輸入和輸出,那麼以輸入爲例,在操做系統中,一個數據的輸入一般分爲如下兩個過程:異步
下面咱們將會分別討論 I/O 模型中的兩個大類,即 同步 I/O 與 異步 I/O。async
最經常使用的一個模型是同步阻塞 I/O 模型。其行爲很是容易理解,其用法對於典型的應用程序來講都很是有效。在調用 read
系統調用時,應用程序會阻塞並對內核進行上下文切換。而後會觸發讀操做,當響應返回時(從咱們正在從中讀取的設備中返回),數據就被移動到用戶空間的緩衝區中。而後應用程序就會解除阻塞(read
調用返回)。函數
同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,設備是以非阻塞的形式打開的。這意味着 I/O 操做不會當即完成,read
操做可能會返回一個錯誤代碼,說明這個命令不能當即知足(EAGAIN
或 EWOULDBLOCK
)性能
當一個應用進程像這樣對一個非阻塞描述符循環調用recvfrom時,咱們稱之爲輪詢(polling)。應用進程只需輪詢內核,以查看某個操做是否就緒。這麼作每每耗費大量CPU時間。spa
I/O 複用有時又被稱爲 事件驅動 I/O, 它的最大優點在於,咱們能夠將感興趣的多個I/O事件(更精確的說,應該是 I/O 所對應的文件描述符)註冊到 select/poll/epoll/kqueue 之中某一個系統調用上(不少時候,這些系統調用又被稱爲多路複用器。假設此時咱們選擇了 select() )。此後,調用進程會阻塞在 select() 系統調用之上(而不是阻塞在真正的 I/O 系統調用(如 read(), write() 等)上)。select() 會負責監視全部已註冊的 I/O 事件,一旦有任意一個事件的數據準備好,那麼 select() 會當即返回,此時咱們的用戶進程便可以進行數據的複製操做。操作系統
總而言之,I/O 複用的優勢就在於能夠同時等待多個I/O事件;而缺點是會進行兩次系統調用(一次 select(), 一次 read() )。線程
在這種模型下,咱們首先開啓套接字的信號驅動式I/O功能,並經過sigaction系統調用安裝一個信號處理函數。改系統調用將當即返回,咱們的進程繼續工做,也就是說他沒有被阻塞。當數據報準備好讀取時,內核就爲該進程產生一個SIGIO信號。咱們隨後就能夠在信號處理函數中調用read讀取數據報,並通知主循環數據已經準備好待處理,也能夠當即通知主循環,讓它讀取數據報。code
不管如何處理SIGIO信號,這種模型的優點在於等待數據報到達期間進程不被阻塞。主循環能夠繼續執行,只要等到來自信號處理函數的通知:既能夠是數據已準備好被處理,也能夠是數據報已準備好被讀取。blog
異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會當即返回,說明 read
請求已經成功發起了。在後臺完成讀操做時,應用程序而後會執行其餘處理操做。當 read
的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成此次 I/O 處理過程。
在一個進程中爲了執行多個 I/O 請求而對計算操做和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差別。當一個或多個 I/O 請求掛起時,CPU 能夠執行其餘任務;或者更爲常見的是,在發起其餘 I/O 的同時對已經完成的 I/O 進行操做。
經過上面的討論能夠清楚的看到,同步 I/O 總會有阻塞的過程,這就是「同步」最本質的特徵。而如前文所說,異步 I/O 的最大特色在於用戶進程均不阻塞。 用戶進程告知內核啓動某一 I/O 操做, 並讓內核全權代爲執行(包括等待數據及拷貝數據至用戶空間),此後用戶進程能夠當即執行其它的任何操做。等到全部 I/O 過程執行完成後, 內核會通知用戶程。因而可知,在整個過程當中,用戶進程均不阻塞。
I/O模型 | 讀寫操做和阻塞階段 |
阻塞I/O | 應用阻塞於讀寫函數 |
I/O複用 | 應用阻塞於I/O複用系統調用,但可同時監聽多個I/O事件。對I/O自己的讀寫操做是非阻塞的 |
信號驅動I/O | 信號觸發讀寫就緒事件,用戶程序執行讀寫操做。應用沒有阻塞階段 |
異步I/O | 內核執行讀寫操做並觸發讀寫完成事件。應用沒有阻塞階段 |
參考
《Unix Network Programming》(volume 1)
《使用異步 I/O 大大提升應用程序的性能》