linuxio模型
① Linux系統I/O模型及select、poll、epoll原理和應用
理解Linux的IO模型之前,首先要了解一些基本概念,才能理解這些IO模型設計的依據
操作系統使用虛擬內存來映射物理內存,對於32位的操作系統來說,虛擬地址空間為4G(2^32)。操作系統的核心是內核,為了保護用戶進程不能直接操作內核,保證內核安全,操作系統將虛擬地址空間劃分為內核空間和用戶空間。內核可以訪問全部的地址空間,擁有訪問底層硬體設備的許可權,普通的應用程序需要訪問硬體設備必須通過 系統調用 來實現。
對於Linux系統來說,將虛擬內存的最高1G位元組的空間作為內核空間僅供內核使用,低3G位元組的空間供用戶進程使用,稱為用戶空間。
又被稱為標准I/O,大多數文件系統的默認I/O都是緩存I/O。在Linux系統的緩存I/O機制中,操作系統會將I/O的數據緩存在頁緩存(內存)中,也就是數據先被拷貝到內核的緩沖區(內核地址空間),然後才會從內核緩沖區拷貝到應用程序的緩沖區(用戶地址空間)。
這種方式很明顯的缺點就是數據傳輸過程中需要再應用程序地址空間和內核空間進行多次數據拷貝操作,這些操作帶來的CPU以及內存的開銷是非常大的。
由於Linux系統採用的緩存I/O模式,對於一次I/O訪問,以讀操作舉例,數據先會被拷貝到內核緩沖區,然後才會從內核緩沖區拷貝到應用程序的緩存區,當一個read系統調用發生的時候,會經歷兩個階段:
正是因為這兩個狀態,Linux系統才產生了多種不同的網路I/O模式的方案
Linux系統默認情況下所有socke都是blocking的,一個讀操作流程如下:
以UDP socket為例,當用戶進程調用了recvfrom系統調用,如果數據還沒准備好,應用進程被阻塞,內核直到數據到來且將數據從內核緩沖區拷貝到了應用進程緩沖區,然後向用戶進程返回結果,用戶進程才解除block狀態,重新運行起來。
阻塞模行下只是阻塞了當前的應用進程,其他進程還可以執行,不消耗CPU時間,CPU的利用率較高。
Linux可以設置socket為非阻塞的,非阻塞模式下執行一個讀操作流程如下:
當用戶進程發出recvfrom系統調用時,如果kernel中的數據還沒准備好,recvfrom會立即返回一個error結果,不會阻塞用戶進程,用戶進程收到error時知道數據還沒准備好,過一會再調用recvfrom,直到kernel中的數據准備好了,內核就立即將數據拷貝到用戶內存然後返回ok,這個過程需要用戶進程去輪詢內核數據是否准備好。
非阻塞模型下由於要處理更多的系統調用,因此CPU利用率比較低。
應用進程使用sigaction系統調用,內核立即返回,等到kernel數據准備好時會給用戶進程發送一個信號,告訴用戶進程可以進行IO操作了,然後用戶進程再調用IO系統調用如recvfrom,將數據從內核緩沖區拷貝到應用進程。流程如下:
相比於輪詢的方式,不需要多次系統調用輪詢,信號驅動IO的CPU利用率更高。
非同步IO模型與其他模型最大的區別是,非同步IO在系統調用返回的時候所有操作都已經完成,應用進程既不需要等待數據准備,也不需要在數據到來後等待數據從內核緩沖區拷貝到用戶緩沖區,流程如下:
在數據拷貝完成後,kernel會給用戶進程發送一個信號告訴其read操作完成了。
是用select、poll等待數據,可以等待多個socket中的任一個變為可讀,這一過程會被阻塞,當某個套接字數據到來時返回,之後再用recvfrom系統調用把數據從內核緩存區復制到用戶進程,流程如下:
流程類似阻塞IO,甚至比阻塞IO更差,多使用了一個系統調用,但是IO多路復用最大的特點是讓單個進程能同時處理多個IO事件的能力,又被稱為事件驅動IO,相比於多線程模型,IO復用模型不需要線程的創建、切換、銷毀,系統開銷更小,適合高並發的場景。
select是IO多路復用模型的一種實現,當select函數返回後可以通過輪詢fdset來找到就緒的socket。
優點是幾乎所有平台都支持,缺點在於能夠監聽的fd數量有限,Linux系統上一般為1024,是寫死在宏定義中的,要修改需要重新編譯內核。而且每次都要把所有的fd在用戶空間和內核空間拷貝,這個操作是比較耗時的。
poll和select基本相同,不同的是poll沒有最大fd數量限制(實際也會受到物理資源的限制,因為系統的fd數量是有限的),而且提供了更多的時間類型。
總結:select和poll都需要在返回後通過輪詢的方式檢查就緒的socket,事實上同時連的大量socket在一個時刻只有很少的處於就緒狀態,因此隨著監視的描述符數量的變多,其性能也會逐漸下降。
epoll是select和poll的改進版本,更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。
epoll_create()用來創建一個epoll句柄。
epoll_ctl() 用於向內核注冊新的描述符或者是改變某個文件描述符的狀態。已注冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 准備好的描述符加入到一個就緒鏈表中管理。
epoll_wait() 可以從就緒鏈表中得到事件完成的描述符,因此進程不需要通過輪詢來獲得事件完成的描述符。
當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序可以不立即處理該事件,下次調用epoll_wait還會再次通知該事件,支持block和nonblocking socket。
當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序需要立即處理該事件,如果不立即處理,下次調用epoll_wait不會再次通知該事件。
ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用nonblocking socket,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
【segmentfault】 Linux IO模式及 select、poll、epoll詳解
【GitHub】 CyC2018/CS-Notes
② 請比較Linux與Windows在網路編程方面的特點
找了一段,大致涉及到了您的問題:
一、socket的模式
socket一般有兩種模式:同步和非同步(windows網路編程技術中也可叫鎖定和非鎖定,Linux網路編程叫阻塞和非阻塞)。
二、socket的類型
socket一般有三種類型,基於TCP的流式套接字,基於UDP的數據報套接字和原始套接字。
三、socket的IO模型
socket
的IO模型是編程中使用socket兩種模式的策略,它們適用的場合不同,在不同的操作系統上支持的模型也不同,例如windows從NT版本才開始支持
完成埠模型。Linux和Windows所支持的模型也有區別,當然也有相同的地方,可能叫法不一樣,但大致思路是一樣的,下面分別介紹windows
和Linux的IO模型
1、 Windows下的套接字IO模型:
A、 Select(選擇)模型
用於同步socket的狀態檢測模型,又叫(Linux)多路復用,可以同時檢測多個socket的狀態
B、 WSAAsyncSelect(非同步選擇)模型
用於非同步socket的非同步事件設置,它是基於Windows消息的模型,必須先打開一個窗口,然後把窗口和socket的消息綁定,這樣,在socket有消息通知時,操作系統便通知窗口,然後在窗口進行處理。
C、 WSAEventSelect(非同步事件)模型
用
於非同步socket的非同步事件,它是基於網路事件的模型,先使用CreateEvent創建一個事件,然後使用WSAEventSelect進行事件綁
定,然後可以使用WaitForMultipleObject(Event)進行事件監聽,可以同時監聽多個事件,不光是socket的,比如可以監聽使
用CreateWaitableTimer創建的Timer等。
D、 重疊IO模型
用
於非同步socket,在創建socket時需要在創建函數WSASocket中使用WSA_FLAG_OVERLAPPED標志,然後在投遞IO請求的時
候將一個Overlapped結構體指針賦給投遞函數,可以使用WSAWaitForMultipleObject來監聽事件,然後使用
WSAGetOverlappedResult來獲取IO的狀態,也可以在Overlapped結構體中使用完成常式來處理,即在投遞函數中把完成常式賦
給投遞函數。
E、 完成埠模型
它
是迄今為止最復雜的一種IO模型,當應用程序需要管理眾多的套接字並且希望隨著系統內安裝的CPU數目的增多,應用程序的性能也可以線性增加,就可以使用
這種模型,它的原理是每個CPU可以單獨負責一個線程的執行,避免線程的頻繁切換。使用這種模型往往可以達到最佳的系統性能。
首
先需要使用CreateIOCompletePort來創建完成埠,然後將IO句柄和此埠綁定,綁定也是使用此函數,當然也可以一次完成。接著是創建
工作者線程,工作者線程會使用GetQueuedCompletionStatus進入完成埠維護的線程池,當有完成事件時,會激活一個線程。
2、 Linux下的IO模型
A、阻塞IO
B、非阻塞IO
C、IO多路復用(選擇)
D、信號驅動
用於非同步socket,首先設定信號處理函數,然後使用fcntl函數設定socket的擁有者,像windows下使用WSAAsncSelect設定socket的窗口一樣。使用這種模型,當內核操作可以被操作的時候通知我們的應用程序
E、非同步IO
當內核在所有操作完成後才會通知應用程序
四、socket的一些使用上的優化
A、緩沖區的優化,可以考慮讓應用程序使用比較小的緩沖區,但同時使用多個WSARecv
B、使用socket選項SO_SNDBUF和SO_RCVBUF設置socket緩沖區大小,如果設為0,操作體系統會使用應用程序的緩沖區,這樣避免了從系統緩沖區向用戶區復制的開銷
五、注意這些IO模型有些不光是針對socket的,其他的IO操作也可以使用,最常用使用的是WriteFile,ReadFile等函數。
其它查考網址:
http://blog.163.com/tianle_han/blog/static/6617826200821522743948/
http://blog.csdn.net/yibulianhua/article/details/5374317
③ Linux非同步IO
Linux中最常用的IO模型是同步IO,在這個模型中,當請求發出之後,應用程序就會阻塞,直到請求滿足條件為止。這是一種很好的解決方案,調用應用程序在等待IO完成的時候不需要佔用CPU,但是在很多場景中,IO請求可能需要和CPU消耗交疊,以充分利用CPU和IO提高吞吐率。
下圖描繪了非同步IO的時序,應用程序發起IO操作後,直接開始執行,並不等待IO結束,它要麼過一段時間來查詢之前的IO請求完成情況,要麼IO請求完成了會自動被調用與IO完成綁定的回調函數。
Linux的AIO有多種實現,其中一種實現是在用戶空間的glibc庫中實現的,本質上是借用了多線程模型,用開啟的新的線程以同步的方式做IO,新的AIO輔助線程與發起AIO的線程以pthread_cond_signal()的形式進行線程間的同步,glibc的AIO主要包含以下函數:
1、aio_read()
aio_read()函數請求對一個有效的文件描述符進行非同步讀操作。這個文件描述符可以代表一個文件、套接字,甚至管道,aio_read()函數原型如下:
aio_read()函數在請求進行排隊之後就會立即返回(盡管讀操作並未完成),如果執行成功就返回0,如果出現錯誤就返回-1。參數aiocb(AIO I/O Control Block)結構體包含了傳輸的所有信息,以及為AIO操作準備的用戶空間緩沖區。在產生IO完成通知時,aiocb結構就被用來唯一標識所完成的IO操作。
2.aio_write()
aio_write()函數用來請求一個非同步寫操作。函數原型如下:
aio_write()函數會立即返回,並且它的請求以及被排隊(成功時返回值為0,失敗時返回值為-1)
3.aio_error()
aio_error()函數被用來確定請求的狀態,其原型如下:
該函數的返回:
4.aio_return()
非同步IO和同步阻塞IO方式之間有一個區別就是不能立即訪問函數的返回狀態,因為非同步IO沒有阻塞在read()調用上。在標準的同步阻塞read()調用中,返回狀態是在該函數返回時提供的。
但是在非同步IO中,我們要用aio_return()函數,原型如下:
只有在aio_error()調用確定請求已經完成(可能成功、也可能發生了錯誤)之後,才會調用這個函數,aio_return()的返回值就等價於同步情況中read()或者write系統調用的返回值。
5.aio_suspend()
用戶可以用該函數阻塞調用進程,直到非同步請求完成為止,調用者提供了一個aiocb引用列表,其中任何一個完成都會導致aio_suspend()返回。函數原型如下:
6.aio_cancel()
該函數允許用戶取消對某個文件描述符執行的一個或所以IO請求。
要取消一個請求,用戶需要提供文件描述符和aiocb指針,如果這個請求被成功取消了,那麼這個函數就會返回AIO_CANCELED。如果請求完成了,就會返回AIO_NOTCANCELED。
7.lio_listio()
lio_listio()函數可用於同時發起多個傳輸。這個函數非常重要,它使得用戶可以在一個系統調用中啟動大量的IO操作,原型如下:
mode參數可以是LIO_WAIT或者是LIO_NOWAIT。LIO_WAIT會阻塞這個調用,直到所有的IO都返回為止,若是LIO_NOWAIT模型,在IO操作完成排隊之後,該函數就會返回。list是一個aiocb的列表,最大元素的個數是由nent定義的。如果list的元素為null,lio_listio()會將其忽略。