天天看點

作業系統中的IO模式1、概念說明2、IO模式3、總結

在進行解釋之前,首先要說明幾個概念:

-使用者空間和核心空間

-程序切換

-程序的阻塞

-檔案描述符

-緩存 i/o

現在作業系統都是采用虛拟存儲器,那麼對32位作業系統而言,它的尋址空間(虛拟存儲空間)為4g(2的32次方)。作業系統的核心是核心(kernel),獨立于普通的應用程式,可以通路受保護的記憶體空間,也有通路底層硬體裝置的所有權限。為了保證使用者程序不能直接操作核心,保證核心的安全,作業系統将虛拟空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。針對linux作業系統而言,将最高的1g位元組(從虛拟位址0xc0000000到0xffffffff)供核心使用,稱為核心空間,而将較低的3g位元組(從虛拟位址0x00000000到0xbfffffff)供各個程序使用,稱為使用者空間。

為了控制程序的執行,核心必須有能力挂起正在cpu上運作的程序,并恢複以前挂起的某個程序的執行。這種行為被稱為程序切換。是以可以說,任何程序都是在作業系統核心的支援下運作的,是與核心緊密相關的。程序切換時很耗費資源的。從一個程序的運作轉到另一個程序上運作,這個過程中經過下面這些變化:

1 儲存處理機上下文,包括程式計數器和其他寄存器。

2 更新pcb資訊。

3 把程序的pcb移入相應的隊列,如就緒、在某事件阻塞等隊列。

4 選擇另一個程序執行,并更新其pcb。

5 更新記憶體管理的資料結構。

6 恢複處理機上下文。

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

檔案描述符(file descriptor)是計算機科學中的一個術語,是一個用于表述指向檔案的引用的抽象化概念。

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

緩存 i/o 又被稱作标準 i/o,大多數檔案系統的預設 i/o 操作都是緩存 i/o。在 linux 的緩存 i/o 機制中,作業系統會将 i/o 的資料緩存在檔案系統的頁緩存( page cache )中,也就是說,資料會先被拷貝到作業系統核心的緩沖區中,然後才會從作業系統核心的緩沖區拷貝到應用程式的位址空間。緩存

i/o 的缺點:資料在傳輸過程中需要在應用程式位址空間和核心進行多次資料拷貝操作,這些資料拷貝操作所帶來的 cpu 以及記憶體開銷是非常大的。

剛才說了對于一次io通路(以read舉例),資料會先被拷貝到作業系統核心的緩沖區中,然後才會從作業系統核心的緩沖區拷貝到應用程式的位址空間。是以說當一個read操作發生時,它會經曆兩個階段:

1  等待資料準備 (waiting for the data to be ready)

2  将資料從核心拷貝到程序中 (copying the data from the kernel to the process)

正式因為這兩個階段,linux系統産生了下面五種io模式的方案。

-阻塞 i/o

-非阻塞 i/o

-i/o 多路複用

-信号驅動 i/o

-異步 i/o

由于信号驅動signal driven io在實際中并不常用,是以隻提剩下的四種io model。

blocking io。當使用者程序調用了recvfrom這個系統調用,kernel就開始了io的第一個階段:準備資料(對于網絡io來說,很多時候資料在一開始還沒有到達。比如,還沒有收到一個完整的udp包。這個時候kernel就要等待足夠的資料到來)。這個過程需要等待,也就是說資料被拷貝到作業系統核心的緩沖區中是需要一個過程的。而在使用者程序這邊,整個程序會被阻塞(當然,是程序自己選擇的阻塞)。當kernel一直等到資料準備好了,進行第二階段,它就會将資料從kernel中拷貝到使用者記憶體,然後kernel傳回結果,使用者程序才解除block的狀态,重新運作起來。

作業系統中的IO模式1、概念說明2、IO模式3、總結

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

nonblocking io。linux下可以通過設定socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

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

call,那麼它馬上就将資料拷貝到了使用者記憶體,然後傳回。

作業系統中的IO模式1、概念說明2、IO模式3、總結

一個非阻塞模式套接字多次調用recv()函數的過程。前三次調用recv()函數時,核心資料還沒有準備好。是以,該函數立即傳回wsaewouldblock錯誤代碼。第四次調用recv()函數時,資料已經準備好,被複制到應用程式的緩沖區中,recv()函數傳回成功訓示,應用程式開始處理資料。

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

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

作業系統中的IO模式1、概念說明2、IO模式3、總結

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

是以如果處理的連接配接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking io的web server性能更好,可能延遲還更大。select/epoll的優勢并不是對于單個連接配接能處理得更快,而是在于能處理更多的連接配接。

linux下的asynchronous io其實用得很少。流程如下:

作業系統中的IO模式1、概念說明2、IO模式3、總結

使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻傳回,是以不會對使用者程序産生任何block。然後,kernel會等待資料準備完成,然後将資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者程序發送一個signal,告訴它read操作完成了。

blocking和non-blocking的差別

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

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的比較如圖所示:

作業系統中的IO模式1、概念說明2、IO模式3、總結

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

節選自

https://segmentfault.com/a/1190000003063859

http://blog.csdn.net/jay900323/article/details/18141217/

繼續閱讀