什么是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)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/145074.html
標籤:Java
上一篇:Redis的cluster模式
