1. 程式人生 > 實用技巧 >五種IO模型

五種IO模型

目錄

1.概念說明

1.1 使用者空間與核心空間

現在作業系統都是採用虛擬儲存器,那麼對32位作業系統而言,它的定址空間(虛擬儲存空間)為4G(2的32次方)。作業系統的核心是核心,獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權

。為了保證使用者程序不能直接操作核心(kernel),保證核心的安全,作業系統將虛擬空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。針對linux作業系統而言,將最高的1G位元組(從虛擬地址0xC0000000到0xFFFFFFFF),供核心使用,稱為核心空間,而將較低的3G位元組(從虛擬地址0x00000000到0xBFFFFFFF),供各個程序使用,稱為使用者空間。

1.2 程序切換

為了控制程序的執行,核心必須有能力掛起正在CPU上執行的程序,並恢復以前掛起的某個程序的執行。這種行為被稱為程序切換。因此可以說,任何程序都是在作業系統核心的支援下執行的,是與核心緊密相關的。

從一個程序的執行轉到另一個程序上執行,這個過程中經過下面這些變化:

  1. 儲存處理機上下文,包括程式計數器和其他暫存器。
  2. 更新PCB資訊。
  3. 把程序的PCB移入相應的佇列,如就緒、在某事件阻塞等佇列。
  4. 選擇另一個程序執行,並更新其PCB。
  5. 更新記憶體管理的資料結構。
  6. 恢復處理機上下文。

1.3 程序的阻塞

正在執行的程序,由於期待的某些事件未發生,如請求系統資源失敗、等待某種操作的完成、新資料尚未到達或無新工作做等,則由系統自動執行阻塞原語(Block),使自己由執行狀態變為阻塞狀態。可見,程序的阻塞是程序自身的一種主動行為,也因此只有處於執行態的程序(獲得CPU),才可能將其轉為阻塞狀態。當程序進入阻塞狀態,是不佔用CPU資源的

1.4 檔案描述符fd

檔案描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔案的引用的抽象化概念

檔案描述符在形式上是一個非負整數。實際上,它是一個索引值,指向核心為每一個程序所維護的該程序開啟檔案的記錄表。當程式開啟一個現有檔案或者建立一個新檔案時,核心向程序返回一個檔案描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔案描述符展開。但是檔案描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。

1.5 快取 IO 和 直接IO

快取IO:資料從磁碟先通過DMA copy到核心空間,再從核心空間通過cpu copy到使用者空間。
直接IO:資料從磁碟通過DMA copy到使用者空間。

快取IO

快取 IO 又被稱作標準 IO,大多數檔案系統的預設 IO 操作都是快取 IO。在 Linux 的快取 IO 機制中,作業系統會將 IO 的資料快取在檔案系統的頁快取( page cache )中,也就是說,資料會先磁碟被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間

  • 讀操作:
    作業系統檢查核心的緩衝區有沒有需要的資料,如果已經快取了,那麼就直接從快取中返回;否則從磁碟中讀
    取,然後快取在作業系統的快取中。
  • 寫操作:
    將資料從使用者空間複製到核心空間的快取中。這時對使用者程式來說寫操作就已經完成,至於什麼時候再寫到磁
    盤中由作業系統決定,除非顯示地呼叫了sync同步命令。

快取I/O的優點:

  1. 在一定程度上分離了核心空間和使用者空間,保護系統本身的執行安全;
  2. 可以減少讀盤的次數,從而提高效能。

快取I/O的缺點:

在快取 I/O 機制中,DMA 方式可以將資料直接從磁碟讀到頁快取中,或者將資料從頁快取直接寫回到磁碟上,而不能直接在應用程式地址空間和磁碟之間進行資料傳輸,這樣,資料在傳輸過程中需要在應用程式地址空間(使用者空間)和快取(核心空間)之間進行多次資料拷貝操作,這些資料拷貝操作所帶來的CPU以及記憶體開銷是非常大的。

直接IO

直接IO就是應用程式直接訪問磁碟資料,而不經過核心緩衝區,也就是繞過核心緩衝區,自己管理I/O快取區,這樣做的目的是減少一次從核心緩衝區到使用者程式快取的資料複製。

引入核心緩衝區的目的在於提高磁碟檔案的訪問效能,因為當程序需要讀取磁碟檔案時,如果檔案內容已經在核心緩衝區中,那麼就不需要再次訪問磁碟;而當程序需要向檔案中寫入資料時,實際上只是寫到了核心緩衝區便告訴程序已經寫成功,而真正寫入磁碟是通過一定的策略進行延遲的。

然而,對於一些較複雜的應用,比如資料庫伺服器,它們為了充分提高效能,希望繞過核心緩衝區,由自己在使用者態空間實現並管理I/O緩衝區,包括快取機制和寫延遲機制等,以支援獨特的查詢機制,比如資料庫可以根據更加合理的策略來提高查詢快取命中率。另一方面,繞過核心緩衝區也可以減少系統記憶體的開銷,因為核心緩衝區本身就在使用系統記憶體。

應用程式直接訪問磁碟資料,不經過作業系統核心資料緩衝區,這樣做的目的是減少一次從核心緩衝區到使用者程式快取的資料複製。這種方式通常是在對資料的快取管理由應用程式實現的資料庫管理系統中。

直接I/O的缺點:

如果訪問的資料不在應用程式快取中,那麼每次資料都會直接從磁碟進行載入,這種直接載入會非常緩慢。通常直接I/O跟非同步I/O結合使用會得到較好的效能。

訪問步驟:

Linux提供了對這種需求的支援,即在open()系統呼叫中增加引數選項O_DIRECT,用它開啟的檔案便可以繞過核心緩衝區的直接訪問,這樣便有效避免了CPU和記憶體的多餘時間開銷。

順便提一下,與O_DIRECT類似的一個選項是O_SYNC,後者只對寫資料有效,它將寫入核心緩衝區的資料立即寫入磁碟,將機器故障時資料的丟失減少到最小,但是它仍然要經過核心緩衝區。

2.Linux IO模型

網路IO的本質是socket的讀取,socket在linux系統被抽象為流,IO可以理解為對流的操作。剛才說了,對於一次IO訪問(以read舉例),資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間。所以說,當一個read操作發生時,它會經歷兩個階段:

  1. 第一階段:等待資料準備 (Waiting for the data to be ready)。
  2. 第二階段:將資料從核心拷貝到程序中 (Copying the data from the kernel to the process)。

對於socket流而言,

  1. 第一步:通常涉及等待網路上的資料分組到達,然後被複制到核心的某個緩衝區。
  2. 第二步:把資料從核心緩衝區複製到應用程序緩衝區。

網路應用需要處理的無非就是兩大類問題,網路IO,資料計算。相對於後者,網路IO的延遲,給應用帶來的效能瓶頸大於後者。

網路IO的模型大致有如下幾種:

  • 同步模型(synchronous IO)
  • 阻塞IO(bloking IO)
  • 非阻塞IO(non-blocking IO)
  • 多路複用IO(multiplexing IO)
  • 訊號驅動式IO(signal-driven IO):不常用
  • 非同步IO(asynchronous IO)

常見的IO模型有阻塞、非阻塞、IO多路複用,非同步。以一個生動形象的例子來說明這四個概念。週末我和女友去逛街,中午餓了,我們準備去吃飯。週末人多,吃飯需要排隊,我和女友有以下幾種方案。

2.1 同步阻塞 IO(blocking IO)

2.1.1 場景描述

我和女友點完餐後,不知道什麼時候能做好,只好坐在餐廳裡面等,直到做好,然後吃完才離開。女友本想還和我一起逛街的,但是不知道飯能什麼時候做好,只好和我一起在餐廳等,而不能去逛街,直到吃完飯才能去逛街,中間等待做飯的時間浪費掉了。

這就是典型的阻塞

2.1.2 網路模型

同步阻塞 IO 模型是最常用的一個模型,也是最簡單的模型。在linux中,預設情況下所有的socket都是blocking。它符合人們最常見的思考邏輯。阻塞就是程序 "被" 休息, CPU處理其它程序去了

在這個IO模型中,使用者空間的應用程式執行一個系統呼叫(recvform),這會導致應用程式阻塞,什麼也不幹,直到資料準備好,並且將資料從核心複製到使用者程序,最後程序再處理資料,在等待資料到處理資料的兩個階段,整個程序都被阻塞。不能處理別的網路IO。呼叫應用程式處於一種不再消費 CPU 而只是簡單等待響應的狀態,因此從處理的角度來看,這是非常有效的。在呼叫recv()/recvfrom()函式時,發生在核心中等待資料和複製資料的過程,大致如下圖:

2.1.3 流程描述

當用戶程序呼叫了recv()/recvfrom()這個系統呼叫,kernel就開始了IO的第一個階段:準備資料(對於網路IO來說,很多時候資料在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的資料到來)。這個過程需要等待,也就是說資料被拷貝到作業系統核心的緩衝區中是需要一個過程的。而在使用者程序這邊,整個程序會被阻塞(當然,是程序自己選擇的阻塞)。第二個階段:當kernel一直等到資料準備好了,它就會將資料從kernel中拷貝到使用者記憶體,然後kernel返回結果,使用者程序才解除block的狀態,重新執行起來。

所以,blocking IO的特點就是在IO執行的兩個階段都被block了。

優點:
  1. 能夠及時返回資料,無延遲;
  2. 對核心開發者來說這是省事了;
缺點:
  1. 對使用者來說處於等待就要付出效能的代價了;

2.2 同步非阻塞 IO(nonblocking IO)

2.2.1 場景描述

我女友不甘心白白在這等,又想去逛商場,又擔心飯好了。所以我們逛一會,回來詢問服務員飯好了沒有,來來回回好多次,飯都還沒吃都快累死了啦。

這就是非阻塞。需要不斷的詢問,是否準備好了。

2.2.2 網路模型

同步非阻塞就是 “每隔一會兒瞄一眼進度條” 的輪詢(polling)方式。在這種模型中,裝置是以非阻塞的形式開啟的。這意味著 IO 操作不會立即完成,read 操作可能會返回一個錯誤程式碼,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK)。

在網路IO時候,非阻塞IO也會進行recvform系統呼叫,檢查資料是否準備好,與阻塞IO不一樣,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以程序不斷地有機會 '被' CPU光顧"。

也就是說非阻塞的recvform系統呼叫呼叫之後,程序並沒有被阻塞,核心馬上返回給程序,如果資料還沒準備好,此時會返回一個error。程序在返回之後,可以乾點別的事情,然後再發起recvform系統呼叫。重複上面的過程,迴圈往復的進行recvform系統呼叫。這個過程通常被稱之為輪詢。輪詢檢查核心資料,直到資料準備好,再拷貝資料到程序,進行資料處理。需要注意,拷貝資料整個過程,程序仍然是屬於阻塞的狀態

在linux下,可以通過設定socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程如圖所示:

2.2.3 流程描述

當用戶程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回一個error。從使用者程序角度講,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者程序判斷結果是一個error時,它就知道資料還沒有準備好,於是它可以再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。

所以,nonblocking IO的特點是使用者程序需要不斷的主動詢問kernel資料好了沒有。

同步非阻塞方式相比同步阻塞方式:
優點:

能夠在等待任務完成的時間裡幹其他活了(包括提交其他任務,也就是 “後臺” 可以有多個任務在同時執行)。

缺點:

任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體資料吞吐量的降低。

2.3 IO 多路複用( IO multiplexing)

2.3.1 場景描述

與第二個方案差不多,餐廳安裝了電子螢幕用來顯示點餐的狀態,這樣我和女友逛街一會,回來就不用去詢問服務員了,直接看電子螢幕就可以了。這樣每個人的餐是否好了,都直接看電子螢幕就可以了。

這就是典型的IO多路複用。

2.3.2 網路模型

由於同步非阻塞方式需要不斷主動輪詢,輪詢佔據了很大一部分過程,輪詢會消耗大量的CPU時間,而 “後臺” 可能有多個任務在同時進行,人們就想到了迴圈查詢多個任務的完成狀態,只要有任何一個任務完成,就去處理它。如果輪詢不是程序的使用者態,而是有人幫忙就好了。那麼這就是所謂的 “IO 多路複用”。UNIX/Linux 下的 select、poll、epoll 就是幹這個的(epoll 比 poll、select 效率高,做的事情是一樣的)。

IO多路複用有兩個特別的系統呼叫select、poll、epoll函式

  • select呼叫是核心級別的,select輪詢相對非阻塞的輪詢的區別:

    前者可以等待多個socket,能實現同時對多個IO埠進行監聽,當其中任何一個socket的資料準好了,就能返回進行可讀,然後程序再進行recvform系統呼叫,將資料由核心拷貝到使用者程序,當然這個過程是阻塞的。

  • select或poll呼叫之後,會阻塞程序,與blocking IO阻塞不同在於:

    此時的select不是等到socket資料全部到達再處理, 而是有了一部分資料就會呼叫使用者程序來處理。如何知道有一部分資料到達了呢?監視的事情交給了核心,核心負責資料到達的處理。也可以理解為"非阻塞"吧

I/O複用模型會用到select、poll、epoll函式,這幾個函式也會使程序阻塞,但是和阻塞I/O所不同的,這兩個函式可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫時(注意不是全部資料可讀或可寫),才真正呼叫I/O操作函式。

對於多路複用,也就是輪詢多個socket。多路複用既然可以處理多個IO,也就帶來了新的問題,多個IO之間的順序變得不確定了,當然也可以針對不同的編號。具體流程,如下圖所示:

2.3.3 流程描述

IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。select/epoll的好處就在於單個process就可以同時處理多個網路連線的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序。

當用戶程序呼叫了select,那麼整個程序會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。

多路複用的特點是通過一種機制一個程序能同時等待IO檔案描述符,核心監視這些檔案描述符(套接字描述符),其中的任意一個進入讀就緒狀態,select, poll,epoll函式就可以返回。對於監視的方式,又可以分為 select, poll, epoll三種方式。

上面的圖和blocking IO的圖其實並沒有太大的不同,事實上,還更差一些。因為這裡需要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。但是,用select的優勢在於它可以同時處理多個connection

所以,如果處理的連線數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。(select/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。)

在IO multiplexing Model中,實際中,對於每一個socket,一般都設定成為non-blocking,但是,如上圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket IO給block。所以IO多路複用是阻塞在select,epoll這樣的系統呼叫之上,而沒有阻塞在真正的I/O系統呼叫如recvfrom之上。

在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,I/O多路複用的最大優勢是系統開銷小,系統不需要建立新的額外程序或者執行緒,也不需要維護這些程序和執行緒的執行,降底了系統的維護工作量,節省了系統資源,I/O多路複用的主要應用場景如下:

伺服器需要同時處理多個處於監聽狀態或者多個連線狀態的套接字。

伺服器需要同時處理多種網路協議的套接字。

瞭解了前面三種IO模式,在使用者程序進行系統呼叫的時候,他們在等待資料到來的時候,處理的方式不一樣,直接等待,輪詢,select或poll輪詢,兩個階段過程:

第一階段:等待資料準備 (Waiting for the data to be ready)。

第二階段:將資料從核心拷貝到程序中 (Copying the data from the kernel to the process)。

第一個階段有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。

第二個階段都是阻塞的。

從整個IO過程來看,他們都是順序執行的,因此可以歸為同步模型(synchronous)。都是程序主動等待且向核心檢查狀態。【此句很重要!!!】

高併發的程式一般使用同步非阻塞方式 而非 多執行緒 + 同步阻塞方式

要理解這一點,首先要扯到併發和並行的區別。比如去某部門辦事需要依次去幾個視窗,辦事大廳裡的人數就是併發數,而視窗個數就是並行度。也就是說併發數是指同時進行的任務數(如同時服務的 HTTP 請求),而並行數是可以同時工作的物理資源數量(如 CPU 核數)。通過合理排程任務的不同階段,併發數可以遠遠大於並行度,這就是區區幾個 CPU 可以支援上萬個使用者併發請求的奧祕。在這種高併發的情況下,為每個任務(使用者請求)建立一個程序或執行緒的開銷非常大。而同步非阻塞方式可以把多個 IO 請求丟到後臺去,這就可以在一個程序裡服務大量的併發 IO 請求。

注意:IO多路複用是同步阻塞模型還是非同步阻塞模型,在此給大家分析下:

同步與非同步的根本性區別,同步是需要主動等待訊息通知,而非同步則是被動接收訊息通知,通過回撥、通知、狀態等方式來被動獲取訊息

IO多路複用在阻塞到select階段時,使用者程序是主動等待並呼叫select函式獲取資料就緒狀態訊息,並且其程序狀態為阻塞

所以,把IO多路複用歸為同步阻塞模式。(看下面)

https://github.com/CyC2018/CS-Notes/issues/194

多路複用是同步的,阻塞/非阻塞取決於呼叫時的引數設定。

所有I/O多路複用操作都是同步的,涵蓋select/poll
阻塞/非阻塞是相對於同步I/O來說的,與非同步I/O無關。
select/poll/epoll本身是同步的,可以阻塞也可以不阻塞。

關於Select是否阻塞:

在使用int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)函式時,可以設定timeval決定該系統呼叫是否阻塞。

關於Poll是否阻塞:

在使用int poll(struct pollfd *fds, nfds_t nfds, int timeout)函式獲取資訊時,可以通過指定timeout的值來決定是否阻塞(當timeout<0時,會無限期阻塞;當timeout=0時,會立即返回)。

關於Epoll是否阻塞:

在使用epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)函式來獲取是否有發生變化/事件的檔案描述符時,可以通過指定timeout來指定該呼叫是否阻塞(當timeout=-1時,會無限期阻塞;當timeout=0時,會立即返回)。

2.4 訊號驅動式IO(signal-driven IO)

訊號驅動式I/O:首先我們允許Socket進行訊號驅動IO,並安裝一個訊號處理函式,程序繼續執行並不阻塞。當資料準備好時,程序會收到一個SIGIO訊號,可以在訊號處理函式中呼叫I/O操作函式處理資料。過程如下圖所示:

2.5 非同步非阻塞 IO(asynchronous IO)

2.5.1 場景描述

女友不想逛街,又餐廳太吵了,回家好好休息一下。於是我們叫外賣,打個電話點餐,然後我和女友可以在家好好休息一下,飯好了送貨員送到家裡來。

這就是典型的非同步,只需要打個電話說一下,然後可以做自己的事情,飯好了就送來了。

2.5.2 網路模型

相對於同步IO,非同步IO不是順序執行。使用者程序進行aio_read系統呼叫之後,無論核心資料是否準備好,都會直接返回給使用者程序,然後使用者態程序可以去做別的事情。等到socket資料準備好了,核心直接複製資料給程序,然後從核心向程序傳送通知IO兩個階段,程序都是非阻塞的

Linux提供了AIO庫函式實現非同步,但是用的很少。目前有很多開源的非同步IO庫,例如libevent、libev、libuv。非同步過程如下圖所示:

2.5.3 流程描述

使用者程序發起aio_read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對使用者程序產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者程序傳送一個signal或執行一個基於執行緒的回撥函式來完成這次 IO 處理過程,告訴它read操作完成了。

在 Linux 中,通知的方式是 “訊號”:

如果這個程序正在使用者態忙著做別的事(例如在計算兩個矩陣的乘積),那就強行打斷之,呼叫事先註冊的訊號處理函式,這個函式可以決定何時以及如何處理這個非同步任務。由於訊號處理函式是突然闖進來的,因此跟中斷處理程式一樣,有很多事情是不能做的,因此保險起見,一般是把事件 “登記” 一下放進佇列,然後返回該程序原來在做的事

如果這個程序正在核心態忙著做別的事,例如以同步阻塞方式讀寫磁碟,那就只好把這個通知掛起來了,等到核心態的事情忙完了,快要回到使用者態的時候,再觸發訊號通知

如果這個程序現在被掛起了,例如無事可做 sleep 了,那就把這個程序喚醒,下次有 CPU 空閒的時候,就會排程到這個程序,觸發訊號通知。

非同步 API 說來輕巧,做來難,這主要是對 API 的實現者而言的。Linux 的非同步 IO(AIO)支援是 2.6.22 才引入的,還有很多系統呼叫不支援非同步 IO。Linux 的非同步 IO 最初是為資料庫設計的,因此通過非同步 IO 的讀寫操作不會被快取或緩衝,這就無法利用作業系統的快取與緩衝機制

很多人把 Linux 的 O_NONBLOCK 認為是非同步方式,但事實上這是前面講的同步非阻塞方式。需要指出的是,雖然 Linux 上的 IO API 略顯粗糙,但每種程式設計框架都有封裝好的非同步 IO 實現。作業系統少做事,把更多的自由留給使用者,正是 UNIX 的設計哲學,也是 Linux 上程式設計框架百花齊放的一個原因。

從前面 IO 模型的分類中,我們可以看出 AIO非同步非阻塞 的動機:

同步阻塞模型需要在 IO 操作開始時阻塞應用程式。這意味著不可能同時重疊進行處理和 IO 操作。

同步非阻塞模型允許處理和 IO 操作重疊進行,但是這需要應用程式根據重現的規則來檢查 IO 操作的狀態。

這樣就剩下非同步非阻塞 IO 了,它允許處理和 IO 操作重疊進行,包括 IO 操作完成的通知。

IO多路複用除了需要阻塞之外,select 函式所提供的功能(非同步阻塞 IO)與 AIO 類似。不過,它是對通知事件進行阻塞,而不是對 IO 呼叫進行阻塞

3.五種IO模型總結

3.1 blocking和non-blocking區別

呼叫blocking IO會一直block住對應的程序直到操作完成,而non-blocking IO在kernel還準備資料的情況下會立刻返回。

3.2 synchronous IO和asynchronous IO區別

在說明synchronous IO和asynchronous IO的區別之前,需要先給出兩者的定義。POSIX的定義是這樣子的:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

An asynchronous I/O operation does not cause the requesting process to be blocked;

兩者的區別就在於synchronous IO做 ”IO operation” 的時候會將process阻塞。按照這個定義,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO。

有人會說,non-blocking IO並沒有被block啊。這裡有個非常“狡猾”的地方,定義中所指的”IO operation”是指真實的IO操作,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,如果kernel的資料沒有準備好,這時候不會block程序。但是,當kernel中資料準備好的時候,recvfrom會將資料從kernel拷貝到使用者記憶體中,這個時候程序是被block了,在這段時間內,程序是被block的。

而asynchronous IO則不一樣,當程序發起IO 操作之後,就直接返回再也不理睬了,直到kernel傳送一個訊號,告訴程序說IO完成。在這整個過程中,程序完全沒有被block。

各個IO Model的比較如圖所示:

通過上面的圖片,可以發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然程序大部分時間都不會被block,但是它仍然要求程序去主動的check,並且當資料準備完成以後,也需要程序主動的再次呼叫recvfrom來將資料拷貝到使用者記憶體。而asynchronous IO則完全不同。它就像是使用者程序將整個IO操作交給了他人(kernel)完成,然後他人做完後發訊號通知。在此期間,使用者程序不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料

IO模型舉例理解

例1:

  • 阻塞IO, 給女神發一條簡訊, 說我來找你了, 然後就默默的一直等著女神下樓, 這個期間除了等待你不會做其他事情, 屬於備胎做法.
  • 非阻塞IO, 給女神發簡訊, 如果不回, 接著再發, 一直髮到女神下樓, 這個期間你除了發簡訊等待不會做其他事情, 屬於專一做法.
  • IO多路複用, 是找一個宿管大媽來幫你監視下樓的女生, 這個期間你可以些其他的事情. 例如可以順便看看其他妹子,玩玩王者榮耀, 上個廁所等等. IO複用又包括 select, poll, epoll 模式. 那麼它們的區別是什麼? 3.1 select大媽 每一個女生下樓, select大媽都不知道這個是不是你的女神, 她需要一個一個詢問, 並且select大媽能力還有限, 最多一次幫你監視1024個妹子 3.2 poll大媽不限制盯著女生的數量, 只要是經過宿舍樓門口的女生, 都會幫你去問是不是你女神 3.3 epoll大媽不限制盯著女生的數量, 並且也不需要一個一個去問. 那麼如何做呢? epoll大媽會為每個進宿舍樓的女生臉上貼上一個大字條,上面寫上女生自己的名字, 只要女生下樓了, epoll大媽就知道這個是不是你女神了, 然後大媽再通知你.

上面這些同步IO有一個共同點就是, 當女神走出宿舍門口的時候, 你已經站在宿舍門口等著女神的, 此時你屬於同步等待狀態。

  • 非同步IO 你告訴女神我來了, 然後你就去王者榮耀了, 一直到女神下樓了, 發現找不見你了,女神再給你打電話通知你, 說我下樓了, 你在哪呢? 這時候你才來到宿舍門口。

例2:

  • 阻塞I/O模型 老李去火車站買票,排隊三天買到一張退票。 耗費:在車站吃喝拉撒睡 3天,其他事一件沒幹。

  • 非阻塞I/O模型 老李去火車站買票,隔12小時去火車站問有沒有退票,三天後買到一張票。耗費:往返車站6次,路上6小時,其他時間做了好多事。

  • I/O複用模型

    1.select/poll 老李去火車站買票,委託黃牛,然後每隔6小時電話黃牛詢問,黃牛三天內買到票,然後老李去火車站交錢領票。

    耗費:往返車站2次,路上2小時,黃牛手續費100元,打電話17次

    2.epoll 老李去火車站買票,委託黃牛,黃牛買到後即通知老李去領,然後老李去火車站交錢領票。

    耗費:往返車站2次,路上2小時,黃牛手續費100元,無需打電話

  • 訊號驅動I/O模型 老李去火車站買票,給售票員留下電話,有票後,售票員電話通知老李,然後老李去火車站交錢領票。 耗費:往返車站2次,路上2小時,免黃牛費100元,無需打電話

  • 非同步I/O模型 老李去火車站買票,給售票員留下電話,有票後,售票員電話通知老李並快遞送票上門。 耗費:往返車站1次,路上1小時,免黃牛費100元,無需打電話

參考資料

https://segmentfault.com/a/1190000003063859

https://github.com/CyC2018/CS-Notes/issues/194