之前自學以及在公司里的一年多都沒有接觸過網絡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模型,在系統調用和數據拷貝過程都是非阻塞的。

