高性能IO模型分析-I/O模型簡介(一)

I/O模型簡介


同步IO和異步IO,阻塞IO和非阻塞IO分別是什麼?有什麼區別和聯繫?讀完本文,可窺一二

本文討論的背景是Linux環境下的network IO。

一、基礎概念

再進行介紹之前先簡單瞭解一些概念

  • 用戶空間和內核空間

  • 進程切換

  • 進程阻塞

  • 文件描述符

  • 緩存IO

用戶空間和內核空間

現在操作系統都是採用虛擬存儲器,那麼對32位操作系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)。操作系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。爲了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心繫統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間

進程切換

爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復以前掛起的某個進程的執行。這種行爲被稱爲進程切換。因此可以說,任何進程都是在操作系統內核的支持下運行的,是與內核緊密相關的。 進程切換很消耗資源

進程阻塞

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

文件描述符

文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。有時也叫它文件句柄

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

緩存 I/O

緩存 I/O 又被稱作標準 I/O,大多數文件系統的默認 I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操作系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操作系統內核的緩衝區中,然後纔會從操作系統內核的緩衝區拷貝到應用程序的地址空間

數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的。

二、I/O執行的兩大階段

在Linux中,對於一次I/O讀取的操作,數據並不會直接拷貝到程序的程序緩衝區。通常包括兩個不同階段:

  1. 等待數據準備好,到達內核空間 (Waiting for the data to be ready) ;
  2. 從內核向進程複製數據 (Copying the data from the kernel to the process)

對於一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所有等待分組到達時,它被複制到內核中的某個緩衝區。第二步就是把數據從內核緩衝區複製到應用程序緩衝區。

看下面的內容過程中,可以不斷回來“細品”這兩個步驟

三、五大模型及對比

正式上述I/O的兩個階段,linux系統產生了下面五種網絡模式的方案:

  • 同步阻塞IO(blocking IO)
  • 同步非阻塞IO(nonblocking IO)
  • IO多路複用(IO multiplexing)
  • 信號驅動IO(signal driven IO)
  • 異步IO(asynchronous IO)

Linux的五大I/O模型

1、同步阻塞IO(blocking IO)

同步阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操作時被阻塞

同步阻塞IO同步IO時序圖

用戶線程通過系統調用read發起IO讀操作,由用戶空間轉到內核空間。內核等到數據包到達後,然後將接收的數據拷貝到用戶空間,完成read操作。

用戶線程使用同步阻塞IO模型的僞代碼描述爲:

{
	...
	read(socket, buffer);
	process(buffer);
	...
}

即用戶需要等待read將socket中的數據讀取到buffer後,才繼續處理接收的數據。整個IO請求的過程中,用戶線程是被阻塞的,這導致用戶在發起IO請求時,不能做任何事情,對CPU的資源利用率不夠

2、同步非阻塞IO(nonblocking IO)

同步非阻塞IO是在同步阻塞IO的基礎上,將socket設置爲NONBLOCK。這樣做用戶線程可以在發起IO請求後可以立即返回。
NIONIO時序圖由於socket是非阻塞的方式,因此用戶線程發起IO請求時立即返回。但並未讀取到任何數據,用戶線程需要不斷地發起IO請求,直到數據到達後,才真正讀取到數據,繼續執行。

用戶線程使用同步非阻塞IO模型的僞代碼描述爲:

{
	while(read(socket, buffer) != SUCCESS){
		// 輪詢等待
	}
	process(buffer);
}

即用戶需要不斷地調用read,嘗試讀取socket中的數據,直到讀取成功後,才繼續處理接收的數據。整個IO請求的過程中,雖然用戶線程每次發起IO請求後可以立即返回,但是爲了等到數據,仍需要不斷地輪詢、重複請求,消耗了大量的CPU的資源。一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性

3、IO多路複用(IO multiplexing)

IO多路複用模型是建立在內核提供的多路分離函數select基礎之上的,使用select函數可以避免同步非阻塞IO模型中輪詢等待的問題。
IO多路複用IO多路複用時序圖用戶首先將需要進行IO操作的socket添加到select中,然後阻塞地等待select系統調用返回。當數據到達時,socket被激活,select函數返回。用戶線程正式發起read請求,讀取數據並繼續執行。

從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以後最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以註冊多個socket,然後不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

用戶線程使用select函數的僞代碼描述爲:

{
    select(socket);
    while(1){
        sockets = select();
        for(socket in sockets) {
            if(can_read(socket)) {
                read(socket, buffer);
				process(buffer);
            }else if(can_write(socket)){
                write(socket, buffer);
                process(buffer);
            }else{
                // ....
            }
        }
    }
}

4、信號驅動IO(signal driven IO)

使用信號,讓內核在文件描述符就緒的時候使用 SIGIO 信號來通知我們。我們將這種模式稱爲信號驅動 I/O 模式。
信號驅動IO允許Socket使用信號驅動 I/O ,還要註冊一個 SIGIO 的處理函數,這時的系統調用將會立即返回。然後我們的程序可以繼續做其他的事情,當數據就緒時,進程收到系統發送一個 SIGIO 信號,可以在信號處理函數中調用IO操作函數處理數據

由於信號驅動IO在實際中並不常用,在此不做具體分析。

5、異步IO(asynchronous IO)

Linux下的異步IO其實用得很少 ,著名的高性能網絡框架netty 5.0版本被廢棄的原因便是:使用異步IO提升效率,增加了複雜性,卻並且沒有顯示出明顯的性能優勢。

第一階段:當在異步 I/O 模型下時,用戶進程如果想進行 I/O 操作,只需進行系統調用,告知內核要進行 I/O 操作,此時內核會馬上返回, 用戶進程 就可以去處理其他的邏輯了 。

第二階段:當內核完成所有的 I/O 操作和數據拷貝後,內核將通知我們的程序,此時數據已經在用戶空間了,可以對數據進行處理了
AIO

五大IO模型的對比

IO模型對比前四種I/O模型都是同步I/O操作,他們的區別在於第一階段,而他們的第二階段是一樣的。

在數據從內核複製到應用緩衝區期間(用戶空間),進程阻塞於系統調用。相反,異步I/O模型在這等待數據和接收數據的這兩個階段裏面都是非阻塞的,可以處理其他的邏輯,用戶進程將整個IO操作交由內核完成,內核完成後會發送通知。在此期間,用戶進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝數據。

同步、異步、阻塞與非阻塞的重點概念區分

同步和異步的概念描述的是用戶線程與內核的交互方式:同步是指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。

阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成

同步和異步阻塞和非阻塞這兩對兒概念,是兩個維度的概念,大家不要混淆。

  • 同步才區分阻塞和非阻塞
  • 異步則一定是非阻塞的,不存在直接的異步非阻塞方式

上述文章內容主要講述了操作系統級別的I/O模型相關知識,下一章就講述一下應用級別的I/O模式都有哪些?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章