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

IO模型

什麼是IO

在Linux世界裡,一切皆檔案。檔案就是一串二進位制流,不管是socket、FIFO、管道還是終端,對我們來說一切都是檔案,一切都是流。在資訊交換的過程中,我們都是對這些流進行資料的收發操作,簡稱為I/O操作(Input and Output)。

計算機裡的所有流都是通過檔案描述符(File Descriptor,簡稱fd)來表示。一個fd就是一個整數,所以對這個整數的操作,就是對這個檔案(流)的操作。我們建立一個socket,通過系統呼叫會返回一個fd,對socket的操作就會轉化為對這個fd的操作。

IO互動

Linux作業系統將使用者空間和核心空間進行了隔離,使用者空間也稱為使用者態,核心空間也稱為核心態。通常使用者程序中的一個完整IO分為兩個階段:

  • 使用者空間和核心空間之間的互動
    使用者空間用來存放的是使用者程式的程式碼和資料,應用程式執行在使用者空間。核心空間用來存放的是核心程式碼和資料,作業系統和驅動程式執行在核心空間。兩者不能簡單地使用指標傳遞資料,因為Linux使用的虛擬記憶體機制,使用者空間中的程式必須通過系統呼叫請求核心空間中的kernel來協助完成IO動作。

  • 核心空間和裝置空間之間的互動
    裝置空間用來緩衝裝置中將被讀寫操作的資料。以網絡卡裝置為例,當發起一次網路請求時,將核心空間中的資料複製到網絡卡,然後再將請求傳送出去。當接收一次網球請求時,等待網路資料到達網絡卡,然後核心將網絡卡中的資料讀取到核心緩衝區。由於IO裝置一般速度比較慢,需要等待,所以核心會為每一個IO裝置維護一個緩衝區。

從廣義的角度來講,只要是比核心態慢的互動,都可以看作是IO操作。如:記憶體IO、網路IO和磁碟IO。通常我們說的IO指的是網路IO和磁碟IO。

IO模型

阻塞IO(Blocking I/O,BIO)

Application發起一個IO請求,Kernel等待IO裝置資料ready,然後將ready的資料從核心空間copy到使用者空間,最後返回給Application。在這整個過程當中Application一直是被block住的。

非阻塞IO(Nonblocking I/O)

Application發起一個IO請求,Kernel立刻返回EWOULDBLOCK(用於非阻塞模式,不需要重新讀或者寫)。Application不停的輪詢Kernel,直到IO裝置資料ready被Kernel讀取到核心空間,再從核心空間copy到使用者空間,最後返回給Application為止。在這整個過程當中Application是可以執行其它操作的,是一種非阻塞狀態。這種方式會讓Application由於輪詢導致CPU過高。

IO多路複用(I/O Multiplexing,NIO)

在IO多路複用中,Application會通過select取輪詢一個fd集合中的狀態,只有當fd有讀、寫或者異常事件時,才真正呼叫recvfrom實際的IO讀寫操作(IO裝置資料ready被Kernel讀取到核心空間,再從核心空間copy到使用者空間,最後返回給Application)。

IO多路複用是通過一個執行緒就可以管理多個fd,只有當fd真正有讀、寫或者異常事件發生才會佔用資源來進行實際的操作。因此,IO多路複用比較適合連線數比較多的情況。IO多路複用非阻塞IO的效率高是因為在非阻塞IO中,不斷地詢問socket狀態時通過使用者執行緒去進行的,而在IO多路複用中,輪詢每個socket狀態是核心在進行的,這個效率要比使用者執行緒要高的多。

不過要注意的是,IO多路複用是通過輪詢的方式來檢測是否有事件到達,並且對到達的事件逐一進行響應。因此對於IO多路複用來說,一旦事件響應體很大,那麼就會導致後續的事件遲遲得不到處理,並且會影響新事件的輪詢。

訊號驅動IO(Signal—Driven I/O)

Application發起一個IO請求,Kernel會給對應的socket註冊一個訊號函式,然後Application繼續執行其它操作,當Kernel中資料就緒時會發送一個訊號給Application,Application接收到訊號之後,便在訊號函式中呼叫IO讀寫操作來進行實際的IO請求操作。這個一般用於UDP中,對TCP套介面幾乎是沒用的,原因是該訊號產生得過於頻繁,並且該訊號的出現並沒有告訴我們發生了什麼事情。

非同步IO(Asynchronous I/O,AIO)

非同步IO模型才是最理想的IO模型,在非同步IO模型中,當Application發起aio_read操作之後,立刻就可以開始去做其它的事。當Kernel收到一個aio_read請求之後會立刻返回,說明read請求已經成功發起了,因此不會對Application產生任何block。然後,Kernel會等待IO資料準備完成,然後將資料從核心空間copy到使用者空間,當這一切都完成之後,Kernel會給Application傳送一個訊號,告訴它read操作完成了。也就說Application完全不需要關心實際的整個IO操作是如何進行的,只需要先發起一個請求,當接收Kernel返回的成功訊號時表示IO操作已經完成,可以直接去使用資料了。

也就說在非同步IO模型中,IO操作的兩個階段都不會阻塞Application,這兩個階段都是由Kernel自動完成,然後傳送一個訊號告知Application操作已完成。Application中不需要再次呼叫IO函式(recvfrom)進行具體的讀寫。這點是和訊號驅動IO模型有所不同的,在訊號驅動IO模型中,當Application接收到訊號表示資料已經就緒,然後需要Application呼叫IO函式(recvfrom)進行實際的讀寫操作;而在非同步IO模型中,收到訊號表示IO操作已經完成,不需要再在Application中呼叫IO函式(recvfrom)進行實際的讀寫操作。

很少有Linux系統支援非同步IO模型,Windows的IOCP就是該模型

IO模型對比

結論

  • 阻塞程度:Blocking I/O>Nonblocking I/O>I/O Multiplexing>Signal-Driven I/O >Asynchronous I/O,效率是由低到高的
  • 對於Blocking I/O、Nonblocking I/O、I/O Multiplexing和Signal-Driven I/O 而言,第一階段的處理方式不同,第二階段的處理是相同的(阻塞的呼叫recvfrom