系統中有哪5種IO模型?什麼是 select/poll/epoll?同步非同步阻塞非阻塞有啥區別?

本文地址http://yangjianyong.cn/?p=84轉載無需經過作者本人授權

先解開第一個疑惑:有哪五種I/O模型

- Blocking I/O【阻塞I/O】

- NonBlocking I/O【非阻塞I/O】

- Multiplexing I/O【I/O多路複用】

- Asynchronous IO【非同步I/O】

- Signal Driven IO【訊號驅動I/O】

這些IO模型具體是怎麼工作的,往下看。


先來一波前提的講解

整個計算機系統涉及到了硬體基礎跟作業系統這兩部分,而我們開發的軟體則是執行在系統之上的。

先來看下現代計算機系統的硬體組成部分:匯流排、I/O裝置、主存、處理器

匯流排 是一組電子管道,這個管道貫穿了整個計算機系統,主要的工作就是攜帶資訊位元組在各個部件之間傳遞。

I/O裝置 就是系統與外部世界的連線通道。例如滑鼠/鍵盤/顯示器等等。

主存 則是一個臨時的儲存裝置,用來存放程式以及程式要處理的資料。

處理器就是中央處理單元【CPU】 就是用來解釋儲存在主存中的指令的引擎。講的明白點,就是把要計算的資料以及計算過程丟給處理器,然後讓它計算得到我們想要的結果。

作業系統 就是我們熟知的 windows/linux/unix 作業系統。作業系統是位於硬體跟應用程式之間的一層軟體,該軟體(作業系統)提供了兩個基本的功能:一個是防止硬體被失控的應用程式濫用;一個就是嚮應用程式提供了一種簡單一致的機制來控制不同的硬體裝置。而怎麼實現這兩種功能的呢,是通過三個基本的抽象概念來是實現的,分別是:程序、虛擬記憶體以及檔案。而這個程序,就是我們今天要講的主角,至於虛擬記憶體跟檔案在這裡就先不講了。對於程序使用的學習,是作為程式設計師的必須課之一。

核心空間跟使用者空間 上面已經提到了,在硬體之上,有一層作業系統,更上層的是應用程式。作業系統可以直接控制硬體,可以訪問到受保護的記憶體空間。而應用程式的程序則不可以直接操作硬體。為了保護作業系統的核心執行,作業系統將虛擬空間劃分為兩部分:核心空間、使用者空間。至於這兩個概念的詳細介紹這裡就先不寫了。在這裡只需要知道程序是執行在一個叫虛擬記憶體空間上的,這個虛擬記憶體空間有一部分是核心使用的,叫核心空間,有一部分是程序使用的,叫使用者空間。而程序需要操作硬體,只能通過呼叫作業系統提供的核心函式來實現,例如 linux 中的 read 操作和 write 操作。


開始進入主題

什麼是程序? 前面的講解我們已經知道了,作業系統是位於硬體跟應用程式程式之間的一層軟體。而我們開發的各種應用軟體,比如 Photoshop/微信/QQ 等等,都是執行在這個作業系統上面的。那麼我們的各種應用軟體是怎麼來使用硬體的以及怎麼保證各個應用軟體之間的資料不會錯亂呢?程序 登場了

程序 就是作業系統提供的一種抽象概念,這個概念給各種應用軟體提供了一種假象,讓不同的應用軟體看上去好像是單獨的佔用硬體資源,讓硬體單獨的處理我這個正在執行的軟體。也就是說一個程序代表著一個軟體正在執行的過程。但是我們的電腦上,肯定不止開了一個軟體,肯定一次性開了好幾個,那麼作業系統就會建立起很多個不同的程序來處理不同的軟體。但是 CPU 一次只能處理一種程式,當有不同的程式需要同一個 CPU 處理怎麼辦?就是我們這篇文章的重點登場了:五種I/O模型來實現併發的處理不同的程式

上面那麼講解程序的概念,一開始新手肯定理解起來很吃力。舉個例子來說明吧:

我們小時候肯定玩過積木,而剛購買過來的積木裡面有一些已經畫好的圖形教程,教我們怎麼搭建出不同的造型,我們照著這個圖形來搭建積木,搭建出一個造型

在上面的例子中,我們人就是處理器,也就是CPU;而那些圖形教程就是程式,也可以說是演算法;剩下的那些各種各樣的積木就是要輸入的資料。那麼程序就是我們人學習了圖形教程,然後使用了各種各樣的積木,搭建出不同造型的整個過程的總和

下面講解五種 I/O 模型

1.Blocking I/O【阻塞I/O】

Blocking I/O 也叫 BIO,也就是 阻塞IO。從圖中可以看出當程序了呼叫了 recvfrom 的系統呼叫,kernel 就開始了進行 IO 操作了,這個操作就是準備資料。而這個準備資料是需要一個過程的,核心需要獲取資料,需要等待資料被拷貝到核心空間,然後再將資料從核心空間拷貝到使用者空間。這其中涉及到兩個過程,一個就是作業系統核心需要等待資料被拷貝到核心空間,另一個過程就是將資料從核心空間拷貝到使用者空間。在這個 阻塞IO 模型中,在資料還沒到達使用者空間之前,這兩個過程都是阻塞的,一直處於 Blocking 的狀態。這就是 阻塞IO,也叫 BIO

用生活的例子來理解阻塞IO:一個客戶就是一種軟體,奶茶店就是作業系統,製作奶茶的機器就是CPU

客戶到奶茶店,客戶跟奶茶店的店員說要買奶茶【進行了系統呼叫】,客戶啥事也不幹就瞎等著【阻塞狀態】,而店員接到這個客戶要求後,就開始使用機器製作奶茶【核心準備資料】,等到製作好了之後【核心準備好了資料】,再把奶茶放到前臺【將資料從核心空間拷貝到使用者空間】,並且通知客戶自己過來取奶茶,客戶拿了之後走人【進行read操作】

客戶到奶茶店說要買奶茶、機器製作奶茶、店員把奶茶拿到前臺、客戶取走的整個過程就是一個程序

2.NonBlocking I/O【非阻塞I/O】

NonBlocking I/O 也叫 NIO,也就是 非阻塞IO。同樣從圖中可以看出當程序呼叫了 recvfrom 的系統呼叫,kernel 一樣就開始了 IO 操作。但是同 BIO 不同的是,BOI 會把核心準備資料的過程給掛起【阻塞】,直到核心準備好了資料才返回。而 NIO 則是直接先返回一個 error 告知程序資料還沒準備好。同樣的在程序端,也不用一直傻傻的等待,當得到一個數據還沒準備好的錯誤時,會再次傳送系統呼叫,直到核心準備好了資料,再從核心空間將資料拷貝到使用者空間。NIO 最大的特點就是,程序需要不斷的詢問 kernel 資料準備好了沒。在我看來這是非常傻的操作,可能我理解的不夠透徹~~

舉例:客戶到奶茶店,客戶跟店員說要買奶茶【進行系統呼叫】,這時候店員直接跟客戶說稍等一下還沒做好【直接返回error】,然後一邊開始使用機器製作奶茶【核心準備資料】。而客戶這邊的做法則是重複的詢問店員奶茶是否做好了,直到店員把奶茶做好為止。同樣的,這期間客戶除了一直詢問之外,啥事也沒幹【阻塞狀態】。如果沒做好,店員一樣是回答還沒有,如果做好了,則是把奶茶拿到前臺【拷貝到使用者空間】並且告知客戶,讓客戶過來拿走【read操作】

跟阻塞IO唯一不同的是,在資料還沒準備好之前,程序會一直重複的詢問

3.Multiplexing I/O【I/O多路複用】

)

Multiplexing I/O,就是 IO多路複用。在這裡可能童鞋們就會發現這種IO方式跟前面的已經不同了,直接將 IO多路複用 可能大家不知道是什麼,如果提到 select/poll/epoll,在這裡肯定大家都有所耳聞。IO多路複用也叫 Event Driver IO【事件驅動IO】。至於什麼是 select/poll/epoll,等會再講解

先來看下 IO多路複用 是怎麼工作的。從圖中可以看到,作業系統提供了一個叫 select 的系統呼叫,當一個使用者程序呼叫了 select,則該使用者程序所負責的所有 socket 同樣被 select 所負責。對於核心,則監聽著所有 select 負責的socket。當呼叫了 select 之後,select 會不斷的輪詢所有的 socket ,當某個 socket 有資料到達了,select 就會返回,告知使用者程序將資料拷貝從核心空間拷貝到使用者空間

舉例:跟上面的兩種方式有很大的不同。假如有很多客戶來買奶茶,每個客戶用一個卡片將自己的姓名跟要買的奶茶型號寫在上面,然後統一交給店員。每一次店員做好奶茶後,要在一堆卡片中,根據做好的奶茶型號找到對應的姓名,通知客戶過來取奶茶。在這種方式上,每一次店員奶茶做好後,需要花大量的時間在一堆卡片中尋找客戶。同樣的,客戶在等待奶茶做好之前,啥事都沒幹只有瞎等著【阻塞狀態】

4.Asynchronous IO【非同步I/O】

Asynchronous IO 也叫 AIO,也就是 非同步IO 。其實 非同步IO 是最好理解的。非同步IO 就是當用戶程序發起了一個系統呼叫之後,核心直接返回 error,而使用者程序接收到返回後,不再等待直接幹其她事情去了。剩下的資料準備工作核心就自己去幹了,等資料準備好了,不再通知使用者程序來拷貝資料,而是自己順手將資料從核心空間拷貝到使用者空間。當所有事情都做完之後,只需要給使用者程序傳送一個訊號【Signal】,告知到使用者程序直接讀取資料就行了【程序read操作】

舉例:客戶到奶茶店後,直接跟店員說要買哪種奶茶,然後留一個電話號碼給店員就回去了【不再是阻塞狀態了】,店員在奶茶做好後,再打電話通知客戶過來取

在AIO這個方式上,最大最大的區別在於,當程序發起系統呼叫,程序不再傻乎乎的等著了,而是去處理其他事情去了

5.Signal Driven IO【訊號驅動I/O】

Signal Driven IO 也叫 SIGIO,也就是訊號驅動IO訊號驅動IO在實際的開發中是很少用到的,因為無用的訊號太多了。在該模型中,從圖中可以看出當程序呼叫了系統呼叫後,該模型會給對用的 socket 請求建立一個訊號處理器,程序並不會被掛起。當核心準備好資料時,核心就會產生一個SIGIO訊號給之前建立好的訊號處理器,然後訊號處理器就可以進行讀寫操作。由於該模型很少使用,在這裡不再詳細講述


第二個問題:同步非同步阻塞非阻塞有啥區別

首先,在開始之前,重要的事情說三遍

**此處要講解的概念跟上面五種IO模型中提到的同步非同步阻塞非阻塞沒有絕對的關聯關係**

**此處要講解的概念跟上面五種IO模型中提到的同步非同步阻塞非阻塞沒有絕對的關聯關係**

**此處要講解的概念跟上面五種IO模型中提到的同步非同步阻塞非阻塞沒有絕對的關聯關係**

在這裡要理解同步非同步阻塞非阻塞的區別,先清空一下腦袋,不要將這裡的概念跟IO模型中提到的字眼聯絡到一塊,否則它會影響你對概念的理解!!!

先來看下什麼是同步非同步

顧名思義,字面上的意思就可以看出了。所謂的同步,就是不同的個體之間在完成同一個任務時,過程的 協調性是一致 的,在完成這個任務的過程中,其中的任何一個步驟要是出現問題,那麼剩下的步驟必須得等到出現問題的步驟恢復正常才能繼續執行任務。具體一點,比如說軍訓走正步的時候,每個人的步伐是一致的,如果走到一半有一個人摔倒了,那麼其他人必須停下來等這個站起來後才能繼續走正步;做早操的時候,每個童鞋的動作是一致的,如果有一個人鞋帶掉了在綁鞋帶,那麼其他人就要等著直到這個人恢復做早操才能繼續。而非同步也是同樣的道理,就是不同個體之間在完成同一個任務時,過程的協調性不一致。也就說在走正步的時候,沒有必要每個人都保持同樣的步調,只要保證最終每個人把走正步這個任務走完就行

也就說,同步的概念是,一個任務的執行過程涉及到的步驟或者個體,它們是一種前後順序都安排好的依賴關係,當一個步驟或者個體出現問題時,該順序不可調,只能等著;而非同步的概念則相反,沒有嚴格的前後順序和依賴關係。也就說同步非同步表達的是順序和依賴關係

注意:有必要提到的一點是,不同的個體是在完成同一個任務,而不是各自幹不同的事情

再來看下什麼是阻塞非阻塞

同樣的從字面上的意思就可以理解了。所謂阻塞,很簡單的,就是卡住了,就是在執行一個任務的過程中,卡在那邊不動了一直 保持著一種狀態 而導致任務無法繼續執行下去。相反的,非阻塞的就是執行任務的過程中遇到阻礙,不是卡著不動,而是幹其他事情去了。同樣的舉例子,在走正步的過程中,如果遇到了障礙,阻塞的方式就是等在那邊,等到阻礙解除了再繼續走。如果是非阻塞方式,那就是人都跑光了幹其他事情去了,等到障礙解除了再從那個位置繼續走。注意,這裡必須回來繼續走正步完成任務

也就是說,阻塞與非阻塞的概念是,一個任務的執行過程涉及到的步驟或者個體,它們在執行任務的過程中,其中的一個步驟或者個體出現問題時,剩下的是一直等著啥事也不幹,還是跑去幹其他事情去了。也就是說,阻塞非阻塞表達的是等待時的狀態

同樣要注意的:在阻塞非阻塞的概念中,也是基於要完成一個完整的任務過程而言的

講到這裡再重複一遍,對於同步非同步阻塞非阻塞的理解,只需要理解其字面上的意思就行了。很多人被混淆了,是因為在介紹五種IO模型中提到了同樣的字眼,才導致理解困難的


整明白了概念之後,再來看下跟五種IO模型有啥關係

同步非同步阻塞非阻塞組合在一起,那就有四種不同的組合:

- 同步阻塞

- 同步非阻塞

- 非同步阻塞

- 非同步非阻塞

在結合上面講解的五種IO模型中我們就可以看出哪中IO模型屬於組合。先睹為快,直接畫一個表格矩陣簡單明瞭的歸納【訊號驅動IO由於用的極少,就不再說明了】

- 阻塞 非阻塞
同步 BIO/Multiplexing IO NIO
非同步 - AIO

先來看下BIO 從上面的IO模型圖中可以看出,當程序發起了系統呼叫的之後,程序是啥也不幹的,也就是阻塞的狀態的,一直卡在那邊。而核心在把資料準備好了之後並且從核心空間拷貝到使用者空間,這時候程序就獲取到了資料。可以看出,程序發起系統呼叫到結束,在核心沒有準備好資料之前程序是沒有結束的,也就是程序的完成需要依賴於核心操作全部完成,否則該程序的呼叫永遠也完結不了,也就是說程序跟核心是同步的。所以BIO是屬於同步阻塞IO

再來看NIO 同樣的從IO模型圖中可以看出,NIOBIO 唯一不同的是,在等待核心處理好資料之前,程序不是傻傻的啥事不幹,而是一直在輪詢,說明了是非阻塞的。而程序獲取資料也是等到核心處理好資料之後將資料從核心空間拷貝到使用者空間,程序再過來讀取,說明是同步的操作。所以NIO是同步非阻塞IO

再來看Multiplexing I/OIO多路複用中,本質上是和 BIO 是一樣的。不同的是系統提供了一個機制,這種機制可以用來一次處理很多請求。但是對於程序的狀態以及獲取資料結果的過程,都是阻塞的狀態和同步的過程。所以Multiplexing I/O也是同步阻塞IO

最後一個AIO 從上面的IO模型圖中就可以很直接明瞭的看出了,當程序發起系統呼叫之後,就不再繼續等待了,而是直接繼續執行,幹其他事情去了,這說明是非阻塞的方式。而在核心準備好資料後,則是將資料拷貝到使用者空間。而這個時候,不同於同步的方式,程序不需要等到資料拷貝到使用者空間才能結束,在AIO模式中,程序相對已經結束了,因為幹其他事情去了。要通知程序過來讀取資料,需要用到回撥的方式通知程序過來讀取資料。所以AIO是非同步非阻塞IO


第三個問題:什麼是 select/poll/epoll

首先說明的是,select/poll/epoll是一種機制 這種機制是來實現IO多路複用的。那麼這種機制是怎麼實現的,就是核心提供了一種方式來實現一個程序可以監聽多個不同的描述符,一旦有描述符資料準備就緒,就通知程序過來讀取資料。而 select/poll/epoll 就是來實現這個方式的三種不同的方法

selectselect實現的過程中,在一個或多個程序管理著不同的描述符,每個描述符都有唯一的標識。當一個或多個程序向核心發起了select的系統呼叫後,核心就開始準備資料,當資料準備好後,要返回給對應程序中對應的描述符時,select 需要遍歷所有程序中所有的描述符,找到對應的發起請求的那個描述符後通知程序來讀取資料。由於每次都要把 select 管理的程序輪詢一遍,時間複雜度就是我們所說的 O(N) 複雜度。在 select 方式中,系統規定了單個程序中能夠開啟的描述符最大上限是 1024個。至於為啥是 1024個,這裡就不再說明了

舉例來理解:還是上面提到的買奶茶的例子。當有不同的客戶【程序】來買奶茶,同時每個客戶還要幫各自的朋友們買【描述符】,不過每個客戶最多隻能幫1024個朋友買【上限1024個描述符】,多的就不行了。這時候這些客戶把自己的身份證號,自己朋友的生份證號,還有排隊編號寫在了一張卡片上交給了店員。等到奶茶做好後,店員就在這一堆卡片中,根據排隊編號找到對應的客戶身份證號,再通知客戶過來取奶茶,再把奶茶送到對應的朋友手中。在這個過程中,每次店員都要把每一張卡片看遍【輪詢遍歷】,這就是所謂的O(N)複雜度,很明顯,這很耗時間

poll的實現過程其實和 select 是一樣的,只不過 poll 方式沒有 select1024 個最大描述符的限制

epollepoll方式中,跟 selectpoll 方式不同的是,當一個或多個程序向核心發起了 epoll 呼叫後,核心這個時候就給該程序的描述符註冊一個回撥函式,在核心準備好資料後,讓對應的描述符的程序自己過來讀取資料,每一次只需要通知一個程序就行了,這個時間複雜度就是 O(1) 複雜度,很明顯,這比 O(N) 複雜度效率高的太多了

舉例來理解:還是同樣的買奶茶的例子。當這些不同的客戶過來買奶茶時,不再是扔一堆卡片跟店員了,而是店員給每一個客戶一個呼叫機【註冊了一個回撥函式】,當呼叫機對應的奶茶做好後,就通知客戶【程序】過來拿奶茶,這時候店員的工作量就已經是最小的了,每一次只需要通知到一個客戶就行,這樣效率就高太多了

以上就是關於 select/poll/epoll 方式的說明,在這裡只提到了怎麼通俗的理解這三種方式,其他的一些技術上的理解就不再說明了


總結

在現代的作業系統中有五種IO模型,其中常用的有 BIO/NIO/Multiplexing IO和AIO 這四種,其中 BIO/Multiplexing IO同步阻塞IONIO同步非阻塞IOAIO非同步非阻塞IO

同步非同步 指的是程序在IO過程中的 協調性 是否一致,同步表示一致,非同步表示不一致;阻塞非阻塞 指的是程序在IO過程中的 狀態 表示,阻塞表示一直在等待,非阻塞表示不等待去執行其他任務

select/poll/epoll 都是實現 Multiplexing IO 的三種不同的方式。最大的區別在於效能上,select/pollO(N) 時間複雜度,epoll 則是超高效率的 O(1) 時間複雜度

歡迎來我的部落格逛一逛 楊建勇的個人部落格http://yangjianyong.cn