引入
首先來說一下進程:
進程在就是一段執行中的代碼,他是由一條條指令和數據組成的一個具有生命周期的有頭有尾的實體。
進程根據權限大體上可以分為兩類:用戶進程 和 內核進程。
這兩者的主要區別在于權限不同。用戶進程無法直接訪問I/O設備,如果用戶進程想要訪問I/O設備,需要調用內核提供的接口,由內核進程對I/O設備進行操作,讀取其中的數據到內核空間,然后將數據從內核空間移動到用戶空間。
大體介紹完進程,我們來考慮這種情況:
我們可能有很多進程需要運行,假如我們只有一顆cpu,那么同一時刻只能有一個進程運行在cpu上,為了讓人們產生多進程同時運行的錯覺,內核被設計為這樣:
1 每個用戶進程僅僅被允許在cpu上運行一小段時間
2 當某個用戶進程在cpu上的運行時間達到限定時間或者進程進入阻塞狀態,內核就會負責將該進程的上下文環境保存(保存現場),然后根據鏈表的排序將下一個用戶進程運行在cpu上,并恢復下一個進程的上下文環境(恢復現場)。 在這里我們可以看到內核的主要職責之一:進行進程調度切換或者說上下文切換
3 由于每個進程單次在cpu上運行的時間很短,并且進程切換也很快,這就給了我們多進程同時運行的錯覺。
4 我們知道cpu的資源是有限的,如果內核占用cpu的時間百分比大,那么就說明用戶進程占用cpu的時間小。換句話說如果我們有成千上萬個用戶進程需要運行,內核為了滿足我們多進程同時運行的錯覺,可能就需要縮短每個進程單次在cpu上的運行時間,然后瘋狂的進行上下文切換。
5 我們的用戶進程可能是web服務進程,數據庫進程,負載均衡進程………… 總之:對于作為非內核開發的我們來說:我們所開發的所有進程都屬于用戶進程。
6 為了能更好的利用cpu資源,在保證系統穩定安全的前提下,我們需要盡可能的增加用戶進程對cpu的時間占用比,也就是說盡可能的縮減內核對cpu的時間占用比(實際上在這里主要討論如何減少上下文切換對cpu的占用)
7 那么問題來了:如何縮減內核對cpu的時間占用比?
解決辦法一:
使用線程,線程是更小的執行單位,線程比較之進程更加輕量級,也就是說,線程在進行上下文切換時消耗系統資源更少(這是理論上,因為各種原因可能導致相反的結果)。這樣通過減少單次進程(線程)上下文切換的時間來降低內核對cpu的占用。(本文主要講I/O多路復用,所以對線程不做過多討論),
并且屬于同一個進程的線程可以共享該進程的數據,這就使得多線程相較于多進程消耗的內存更少。當然這也是個問題:
因為共享數據就容易引起競爭,我們需要各種鎖機制來確立進程內安定祥和的局面(最起碼不能烽煙四起!)
而且,多線程相較于多進程來說不算穩定,一個線程崩毀容易引起進程的崩潰。
基于以上幾點這個解決辦法可以根據情況自行斟酌
解決辦法二:
I/O多路復用
說到I/O多路復用,我還是介紹一下UNIX下的I/O模型
1 對同步、異步術語的定義
在POSIX中:
同步:導致請求進程阻塞,直到I/O完成
異步:不導致請求進程阻塞。
2 I/O的大體過程
用戶進程讀取I/O流需要兩個階段:
第一階段: 將數據分組復制到內核空間
第二階段: 將內核空間的相關數據復制到用戶空間
3 五種I/O模型的解釋
1 阻塞式I/O
默認情況下所有的套接字都是阻塞的(當然我們可以設置為非阻塞),當我們調用系統提供的接口訪問套接字時,就會將進程阻塞。
上圖中,粉紅色箭頭表示進程處于running狀態,也就是正在cpu上運行。而藍色箭頭表示進程處于sleep阻塞狀態。(若無特殊說明,以下各圖均為如此)
2 非阻塞式I/O
進程把一個套接字設置成非阻塞是在通知內核,當所請求的I/O操作非得把本進程投入睡眠才能完成時,不要把進程投入睡眠,而是返回一個錯誤。
但這就要求我們得不停的調用recvfrom函數,嘗試讀取socket中的數據,直到讀取成功后,才繼續處理接收的數據。整個IO請求的過程中,雖然用戶線程每次發起IO請求后可以立即返回,但是為了等到數據,仍需要不斷地輪詢、重復請求,消耗了大量的CPU的資源。一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性
3 I/O多路復用
Linux中I/O多路復用的實現方式有三個,分別是select poll epoll 。這三個都可以監聽許多文件描述符(在linux中套接字也是文件描述符),就會返回那些發生變化(可以理解為第一階段完成)的文件描述符。
來看一下select的:
通過調用select(或者poll epoll),我們可以在一個進程(或者線程)中監控多個文件描述符,當某個文件描述符狀態改變時,進程可以得到通知。
再來一張圖片介紹一下這三者的不同:
注:上圖轉載自https://pic1.zhimg.com/v2-e6a869884585625dfc7eace1b90c3024_r.png
4 信號驅動式I/O(本文不做討論)
5 異步I/O
這類操作就是告知內核,等兩個階段操作都完成后再來通知我。
異步I/O需要調用操作系統提供的特殊API
Linux中為:AIO
windows: IOCP
總結:阻塞式I/O(默認),非阻塞式I/O(nonblock),I/O復用(select/poll/epoll)都屬于同步I/O,因為它們在數據由內核空間復制回進程緩沖區時都是阻塞的(不能干別的事)。只有異步I/O模型(AIO)是符合異步I/O操作的含義的。
本文由“實戰訓練營”發布,2017年1月24日