之前自學以及在公司裡的一年多都沒有接觸過網絡IO相關的知識,六月份二面的時候有位面試官問了我一些有關網絡IO的相關問題,結果一臉懵逼。趁着現在有空,正好入門一下。
基礎概念
正式開始之前,需要鋪墊一些基本概念,以免接下來看到一臉懵逼。
我們都知道,在作業系統中,
CPU
負責執行指令,這些指令有些來自應用程式,有些是底層系統的自調用。有些指令是非常危險的,如清除記憶體,網絡連接配接等等,如果錯誤調用的話有可能導緻系統崩潰。因而
CPU
将指令分為特權指令和非特權指令,對于某些特定的指令,隻需要作業系統及其相關子產品進行調用。因而,根據這個特點,作業系統内部也劃分出了核心态和使用者态。
核心态
核心态擁有完全的底層資源控制權限,可以執行任何的
CPU
指令,通路任何記憶體位址,其占有的處理機是不允許被搶占的。
使用者态
使用者程式是運作在作業系統之上,這些程式運作時稱之為使用者态,使用者态下不能直接通路底層硬體和記憶體位址,隻能通過委托系統調用的方式來通路底層硬體和記憶體。
使用者态到核心态如何切換
從使用者态切換到核心态有三種方式:
- 系統調用:這是使用者态主動要求切換到核心态的一種方式。使用者程序通過系統調用申請使用作業系統提供的某些服務以便完成工作,比如,調用
指令實際上就是執行了一個建立新程序的系統調用。系統調用的機制其核心在于**使用了作業系統為使用者特别開放的一個中斷來實作的,例如fork()
的Linux
中斷;int 80h
- 外設中斷:當外圍裝置完成使用者請求的操作後,會向
發出相應的中斷信号。這時CPU
會暫停執行下一條即将要執行的指令轉而去執行與中斷信号對應的處理程式。如果先前執行的是使用者态下的指令,那麼這個切換過程就是使用者态轉為核心态。比如硬碟讀寫操作完成,系統會切換到硬碟讀寫的中斷處理程式中執行後續操作;CPU
- 異常:當
在執行運作處于使用者态的程式時,發生了一些不可知的異常,這個時候就會觸發由目前運作進行切換到處理此異常的核心相關程式中,也就是轉到了核心态,比如缺頁異常;CPU
這三種是使用者态切換到核心态的主要方式,系統調用是主動的,後面兩種是被動的。
Linux
的整體架構圖如下所示:

同步/異步
同步/異步關注的是消息通信機制。
同步:所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不傳回。等前一件做完了才能做下一件事。
異步:異步的概念和同步相對。當一個異步過程調用發出後,調用者若不能立刻得到結果,此時可以直接傳回然後執行其他任務,等到獲得了結果之後通過狀态、通知或者回調等手段通知調用者。
同步、異步一般發生在不同的線程/程序之間,如
Thread1
和
Thread2
是同步執行還是異步執行的。
阻塞和非阻塞
阻塞和非阻塞關注的是程式在等待調用結果時的狀态。
阻塞: 阻塞調用是指調用傳回之前,目前線程會被挂起,隻有當調用得到結果後才傳回。
非阻塞:與阻塞相反,非阻塞調用是指在不能立即得到結果之前,該函數不會将目前線程阻塞,而是立即傳回。
五種 IO 模型
IO
一般分為磁盤IO和網絡IO,這裡我們主要關注網絡IO。一次完整的網絡
IO
過程如下所示:
從上圖可以看出,資料無論從網卡到使用者空間還是從使用者空間到網卡都需要經過核心。
阻塞IO模型
當應用程式調用一個
IO
函數,其底層會委托作業系統的
recvfrom()
去完成,當資料還沒有準備好時,
revfrom
會一直阻塞,等待資料準備好。當資料準備好後,從核心拷貝到使用者空間,
recvfrom
傳回成功,
IO
函數調用完成。過程如下所示:
阻塞
IO
模型的優點是程式設計簡單,但缺點是需要配合大量線程使用。應用程序沒接收一個連接配接,就需要為此連接配接建立一個線程來處理該連接配接上的讀寫任務。
非阻塞IO模型
調用程序在等待資料的過程中不會被阻塞,而是會不斷地輪詢檢視資料有沒有準備好。當資料準備好後,将資料從核心空間拷貝到使用者空間,完成
IO
函數的調用。等待資料的過程是非阻塞的,但資料拷貝時仍是阻塞的。過程如下所示:
非阻塞
io
的優點在于可以實作使用一個線程同時處理多個連接配接的需求,減少線程的大量使用。缺點在于要不斷地去輪詢檢查資料是否準備好,比較耗費
CPU
。
IO複用模型
為了解決非阻塞
IO
不斷輪詢導緻
CPU
占用升高的問題,出現了
IO
複用模型。
IO
複用中,使用其他線程幫助去檢查多個線程資料的完成情況,提高效率。
Linux
中提供了
select
、
poll
epoll
三種方式來實作
IO
複用。一個線程可以對多個
IO
端口進行監聽,當有讀寫事件産生時會分發到具體的線程進行處理。過程如下所示:
IO
複用隻需要阻塞在
select
,
poll
或者
epoll
,可以同時處理和管理多個連接配接。缺點是當
select
poll
epoll
管理的連接配接數過少時,這種模型将退化成阻塞
IO
模型。并且還多了一次系統調用:一次
select
poll
epoll
一次
recvfrom
信号驅動IO模型
應用程式可以建立一個信号驅動程式
SIGIO
,當資料沒有處理好時,應用程式繼續運作,不會被阻塞。當資料準備好之後,作業系統向應用程式發送信号,之後信号驅動程式就會執行,在信号處理函數中調用
IO
函數處理資料。過程如下所示:
信号驅動
IO
模型的優點在于非阻塞,缺點在于串行處理信号驅動程式,目前一個
SIGIO
沒有被處理的情況下,後一個信号也不能被處理。在信号量大的時候會導緻後面的信号不能被及時感覺。
異步IO模型
相比于同步
IO
,異步
IO
不是順序執行的。應用程序在執行
aio_read
系統調用之後,無論資料是否準備好,都會直接傳回給使用者程序,然後應用程序可以去做别的事情。當資料準備好之後,核心直接複制資料給使用者程序,然後核心向程序發送通知。過程如下:
IO
模型中核心通知應用程序資料何時準備好,而在異步
IO
模型中核心将資料複制完成之後告知應用程序
IO
操作已完成。
在異步
IO
模型中,應用程序調用
aio_read
以及資料被拷貝到使用者空間這兩個過程都是非阻塞的。
總結
IO
模型公有五種,前四種模型差別在于第一部分,即系統調用,但是第二部分都是一樣的,即将資料從核心空間拷貝到使用者空間這個過程,程序阻塞于
redvfrom
的調用。而最後一種,異步
IO
模型,在系統調用和資料拷貝過程都是非阻塞的。
參考
了解Linux使用者态和核心态
線程,程序,協程, 并發,并行,同步,異步概念解析
伺服器網絡程式設計之 IO 模型
圖解Java IO模型(一)