IO模型知多少

2022-02-16 欄目:技術知識
1. 引言

同步異步I/O,阻塞非阻塞I/O是(yes)程序員老生(born)常談的(of)話題了(Got it),也是(yes)自己一(one)直以(by)來(Come)懵懵懂懂的(of)一(one)個(indivual)話題。比如:何爲(for)同步異步?何爲(for)阻塞與非阻塞?二者的(of)區别在(exist)哪裏?阻塞在(exist)何處?爲(for)什麽會有多種IO模型,分别用(use)來(Come)解決問題?常用(use)的(of)框架采用(use)的(of)是(yes)何種I/O模型?各種IO模型的(of)優劣勢在(exist)哪裏,适用(use)于(At)何種應用(use)場景?

簡而言之,對于(At)I/O的(of)認知,不(No)能僅僅停留在(exist)字面上(superior)認識,了(Got it)解内部玄機,才能深刻理解I/O,才能看清I/O相關問題的(of)本質。

2. I/O 的(of)定義

I/O 的(of)全稱是(yes)Input/Output。雖常談及I/O,但想必你也一(one)時(hour)不(No)能給出(out)一(one)個(indivual)完整的(of)定義。搜索了(Got it)谷歌,發現也盡是(yes)些冗長的(of)論述。要(want)想厘清I/O這(this)個(indivual)概念,我(I)們(them)需要(want)從不(No)同的(of)視角去理解它。

2.1. 計算機視角

馮•諾伊曼計算機的(of)基本思想中有提到(arrive)計算機硬件組成應爲(for)五大(big)部分:控制器,運算器,存儲器,輸入和(and)輸出(out)。其中輸入是(yes)指将數據輸入到(arrive)計算機的(of)設備,比如鍵盤鼠标;輸出(out)是(yes)指從計算機中獲取數據的(of)設備,比如顯示器;以(by)及既是(yes)輸入又是(yes)輸出(out)設備,硬盤,網卡等。

用(use)戶通過操作(do)系統才能完成對計算機的(of)操作(do)。計算機啓動時(hour),第一(one)個(indivual)啓動的(of)程序是(yes)操作(do)系統的(of)内核,它将負責計算機的(of)資源管理和(and)進程的(of)調度。換句話說:操作(do)系統負責從輸入設備讀取數據并将數據寫入到(arrive)輸出(out)設備。

所以(by)I/O之于(At)計算機,有兩層意思:

  1. I/O設備

  2. 對I/O設備的(of)數據讀寫

對于(At)一(one)次I/O操作(do),必然涉及2個(indivual)參與方,一(one)個(indivual)輸入端,一(one)個(indivual)輸出(out)端,而又根據參與雙方的(of)設備類型,我(I)們(them)又可以(by)分爲(for)磁盤I/O,網絡I/O(一(one)次網絡的(of)請求響應,網卡)等。

2.2. 程序視角

應用(use)程序作(do)爲(for)一(one)個(indivual)文件保存在(exist)磁盤中,隻有加載到(arrive)内存到(arrive)成爲(for)一(one)個(indivual)進程才能運行。應用(use)程序運行在(exist)計算機内存中,必然會涉及到(arrive)數據交換,比如讀寫磁盤文件,訪問數據庫,調用(use)遠程API等等。但我(I)們(them)編寫的(of)程序并不(No)能像操作(do)系統内核一(one)樣直接進行I/O操作(do)。

因爲(for)爲(for)了(Got it)确保操作(do)系統的(of)安全穩定運行,操作(do)系統啓動後,将會開啓保護模式:将内存分爲(for)内核空間(内核對應進程所在(exist)内存空間)和(and)用(use)戶空間,進行内存隔離。我(I)們(them)構建的(of)程序将運行在(exist)用(use)戶空間,用(use)戶空間無法操作(do)内核空間,也就意味着用(use)戶空間的(of)程序不(No)能直接訪問由内核管理的(of)I/O,比如:硬盤、網卡等。

但操作(do)系統向外提供API,其由各種類型的(of)系統調用(use)(System Call)組成,以(by)提供安全的(of)訪問控制。所以(by)應用(use)程序要(want)想訪問内核管理的(of)I/O,必須通過調用(use)内核提供的(of)系統調用(use)(system call)進行間接訪問。

所以(by)I/O之于(At)應用(use)程序來(Come)說,強調的(of)通過向内核發起系統調用(use)完成對I/O的(of)間接訪問。換句話說應用(use)程序發起的(of)一(one)次IO操作(do)實際包含兩個(indivual)階段:

  1. IO調用(use)階段:應用(use)程序進程向内核發起系統調用(use)

  2. IO執行階段:内核執行IO操作(do)并返回

    1. 準備數據階段:内核等待I/O設備準備好數據

    2. 拷貝數據階段:将數據從内核緩沖區拷貝到(arrive)用(use)戶空間緩沖區

怎麽理解準備數據階段呢?對于(At)寫請求:等待系統調用(use)的(of)完整請求數據,并寫入内核緩沖區;對于(At)讀請求:等待系統調用(use)的(of)完整請求數據;(若請求數據不(No)存在(exist)于(At)内核緩沖區)則将外圍設備的(of)數據讀入到(arrive)内核緩沖區。

而應用(use)程序進程在(exist)發起IO調用(use)至内核執行IO返回之前,應用(use)程序進程/線程所處狀态,就是(yes)我(I)們(them)下面要(want)讨論的(of)第二個(indivual)話題阻塞IO與非阻塞IO。

3. IO 模型之阻塞I/O(BIO)

應用(use)程序中進程在(exist)發起IO調用(use)後至内核執行IO操作(do)返回結果之前,若發起系統調用(use)的(of)線程一(one)直處于(At)等待狀态,則此次IO操作(do)爲(for)阻塞IO。阻塞IO簡稱BIO,Blocking IO。其處理流程如下圖所示:

從上(superior)圖可知當用(use)戶進程發起IO系統調用(use)後,内核從準備數據到(arrive)拷貝數據到(arrive)用(use)戶空間的(of)兩個(indivual)階段期間用(use)戶調用(use)線程選擇阻塞等待數據返回。

因此BIO帶來(Come)了(Got it)一(one)個(indivual)問題:如果内核數據需要(want)耗時(hour)很久才能準備好,那麽用(use)戶進程将被阻塞,浪費性能。爲(for)了(Got it)提升應用(use)的(of)性能,雖然可以(by)通過多線程來(Come)提升性能,但線程的(of)創建依然會借助系統調用(use),同時(hour)多線程會導緻頻繁的(of)線程上(superior)下文的(of)切換,同樣會影響性能。所以(by)要(want)想解決BIO帶來(Come)的(of)問題,我(I)們(them)就得看到(arrive)問題的(of)本質,那就是(yes)阻塞二字。

4. IO 模型之非阻塞I/O(NIO)

那解決方案自然也容易想到(arrive),将阻塞變爲(for)非阻塞,那就是(yes)用(use)戶進程在(exist)發起系統調用(use)時(hour)指定爲(for)非阻塞,内核接收到(arrive)請求後,就會立即返回,然後用(use)戶進程通過輪詢的(of)方式來(Come)拉取處理結果。也就是(yes)如下圖所示:

應用(use)程序中進程在(exist)發起IO調用(use)後至内核執行IO操作(do)返回結果之前,若發起系統調用(use)的(of)線程不(No)會等待而是(yes)立即返回,則此次IO操作(do)爲(for)非阻塞IO模型。非阻塞IO簡稱NIO,Non-Blocking IO。

然而,非阻塞IO雖然相對于(At)阻塞IO大(big)幅提升了(Got it)性能,但依舊不(No)是(yes)完美的(of)解決方案,其依然存在(exist)性能問題,也就是(yes)頻繁的(of)輪詢導緻頻繁的(of)系統調用(use),會耗費大(big)量的(of)CPU資源。比如當并發很高時(hour),假設有1000個(indivual)并發,那麽單位時(hour)間循環内将會有1000次系統調用(use)去輪詢執行結果,而實際上(superior)可能隻有2個(indivual)請求結果執行完畢,這(this)就會有998次無效的(of)系統調用(use),造成嚴重的(of)性能浪費。有問題就要(want)解決,那NIO問題的(of)本質就是(yes)頻繁輪詢導緻的(of)無效系統調用(use)

5. IO模型之IO多路複用(use)

解決NIO的(of)思路就是(yes)降解無效的(of)系統調用(use),如何降解呢?我(I)們(them)一(one)起來(Come)看看以(by)下幾種IO多路複用(use)的(of)解決思路。

5.1. IO多路複用(use)之select/poll

Select是(yes)内核提供的(of)系統調用(use),它支持一(one)次查詢多個(indivual)系統調用(use)的(of)可用(use)狀态,當任意一(one)個(indivual)結果狀态可用(use)時(hour)就會返回,用(use)戶進程再發起一(one)次系統調用(use)進行數據讀取。換句話說,就是(yes)NIO中N次的(of)系統調用(use),借助Select,隻需要(want)發起一(one)次系統調用(use)就夠了(Got it)。其IO流程如下所示:

但是(yes),select有一(one)個(indivual)限制,就是(yes)存在(exist)連接數限制,針對于(At)此,又提出(out)了(Got it)poll。其與select相比,主要(want)是(yes)解決了(Got it)連接限制。

select/epoll 雖然解決了(Got it)NIO重複無效系統調用(use)用(use)的(of)問題,但同時(hour)又引入了(Got it)新的(of)問題。問題是(yes):

  1. 用(use)戶空間和(and)内核空間之間,大(big)量的(of)數據拷貝

  2. 内核循環遍曆IO狀态,浪費CPU時(hour)間

換句話說,select/poll雖然減少了(Got it)用(use)戶進程的(of)發起的(of)系統調用(use),但内核的(of)工作(do)量隻增不(No)減。在(exist)高并發的(of)情況下,内核的(of)性能問題依舊。所以(by)select/poll的(of)問題本質是(yes):内核存在(exist)無效的(of)循環遍曆。

5.2. IO多路複用(use)之epoll

針對select/pool引入的(of)問題,我(I)們(them)把解決問題的(of)思路轉回到(arrive)内核上(superior),如何減少内核重複無效的(of)循環遍曆呢?變主動爲(for)被動,基于(At)事件驅動來(Come)實現。其流程圖如下所示:

epoll相較于(At)select/poll,多了(Got it)兩次系統調用(use),其中epollcreate建立與内核的(of)連接,epollctl注冊事件,epoll_wait阻塞用(use)戶進程,等待IO事件。

epoll,已經大(big)大(big)優化了(Got it)IO的(of)執行效率,但在(exist)IO執行的(of)第一(one)階段:數據準備階段都還是(yes)被阻塞的(of)。所以(by)這(this)是(yes)一(one)個(indivual)可以(by)繼續優化的(of)點。

6. IO 模型之信号驅動IO(SIGIO)

信号驅動IO與BIO和(and)NIO最大(big)的(of)區别就在(exist)于(At),在(exist)IO執行的(of)數據準備階段,不(No)會阻塞用(use)戶進程。如下圖所示:當用(use)戶進程需要(want)等待數據的(of)時(hour)候,會向内核發送一(one)個(indivual)信号,告訴内核我(I)要(want)什麽數據,然後用(use)戶進程就繼續做别的(of)事情去了(Got it),而當内核中的(of)數據準備好之後,内核立馬發給用(use)戶進程一(one)個(indivual)信号,說”數據準備好了(Got it),快來(Come)查收“,用(use)戶進程收到(arrive)信号之後,立馬調用(use)recvfrom,去查收數據。

乍一(one)看,信号驅動式I/O模型有種異步操作(do)的(of)感覺,但是(yes)在(exist)IO執行的(of)第二階段,也就是(yes)将數據從内核空間複制到(arrive)用(use)戶空間這(this)個(indivual)階段,用(use)戶進程還是(yes)被阻塞的(of)。

綜上(superior),你會發現,不(No)管是(yes)BIO還是(yes)NIO還是(yes)SIGIO,它們(them)最終都會被阻塞在(exist)IO執行的(of)第二階段。那如果能将IO執行的(of)第二階段變成非阻塞,那就完美了(Got it)。

7. IO 模型之異步IO(AIO)

異步IO真正實現了(Got it)IO全流程的(of)非阻塞。用(use)戶進程發出(out)系統調用(use)後立即返回,内核等待數據準備完成,然後将數據拷貝到(arrive)用(use)戶進程緩沖區,然後發送信号告訴用(use)戶進程IO操作(do)執行完畢(與SIGIO相比,一(one)個(indivual)是(yes)發送信号告訴用(use)戶進程數據準備完畢,一(one)個(indivual)是(yes)IO執行完畢)。其流程如下:

所以(by),之所以(by)稱爲(for)異步IO,取決于(At)IO執行的(of)第二階段是(yes)否阻塞。因此前面講的(of)BIO,NIO和(and)SIGIO均爲(for)同步IO。

8. 總結

梳理完這(this)些IO模型後,之前一(one)直處于(At)懵懂狀态的(of)阻塞,非阻塞,同步異步IO,終于(At)算是(yes)有個(indivual)概念了(Got it)。同時(hour)也糾正了(Got it)自己一(one)直以(by)來(Come)的(of)誤解,所以(by)一(one)路走來(Come),愈發覺得返璞歸真的(of)重要(want)性,隻有如此,才能在(exist)快速更叠的(of)技術演進中,以(by)不(No)變應萬變。

本文綜合多方資料寫就,難免纰漏,但隻有寫下來(Come),才能得以(by)指正。所以(by),煩請各位看官不(No)吝賜教。

參考資料:

  1. 程序員應該這(this)樣理解IO

  2. IO複用(use)模型同步,異步,阻塞,非阻塞及實例詳解

  3. 服務器網絡編程之 IO 模型

  4. http://www.c-jump.com/CIS77/CPU/VonNeumann/lecture.html

  5. 同步I/O(阻塞I/O,非阻塞I/O),異步I/O

  6. 馬士兵:權威講解nio,epoll,多路複用(use)

  7. Linux 内核詳解以(by)及内核緩沖區技術