操作系统IO模式(理解)
我通過后面的文章總結如下:
?
IO多路復用三種模式:
select:
(1)他會遍歷所有文件描述符,不管是否活躍
(2) 它僅僅返回說有數據了,不會告訴你哪個sock有數據,你還需要自己遍歷一遍文件描述符
(3)最多支持1024個連接,資源有限,有效限制。
(4)線程不安全。執行一個sock,當另一個sock來了會扔掉第一個sock
poll:
?(1)在select改進了連接數限制,資源不限制。
(2) 還是不知道哪個sock有數據,你還得自己遍歷一下。
?(3)遍歷所有的,不管是否活躍
? ?(4) 線程不安全。
epoll:
(1) 線程安全了。
(2)告訴進程哪個sock有數據。
? (3)只是遍歷活躍的文件描述符
(4)連接數隨便,不設限制。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
%%%關于如何理解IO多路復用三種模式,select,poll,epool加黑體的是我畫出的重點。
作者:碼農的荒島求生
鏈接:https://www.zhihu.com/question/32163005/answer/1644076216
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
?
I/O多路復用三劍客
本質上select、poll、epoll都是阻塞式I/O,也就是我們常說的同步I/O。
select:初出茅廬
在select這種I/O多路復用機制下,我們需要把想監控的文件描述集合通過函數參數的形式告訴select,然后select會將這些文件描述符集合拷貝到內核中,我們知道數據拷貝是有性能損耗的,因此為了減少這種數據拷貝帶來的性能損耗,Linux內核對集合的大小做了限制,并規定用戶監控的文件描述集合不能超過1024個,同時當select返回后我們僅僅能知道有些文件描述符可以讀寫了,但是我們不知道是哪一個,因此程序員必須再遍歷一邊找到具體是哪個文件描述符可以讀寫了。
因此,總結下來select有這樣幾個特點:
- 我能照看的文件描述符數量有限,不能超過1024個
- 用戶給我的文件描述符需要拷貝的內核中
- 我只能告訴你有文件描述符滿足要求了,但是我不知道是哪個,你自己一個一個去找吧(遍歷)
因此我們可以看到,select機制的特性在高性能網絡服務器動輒幾萬幾十萬并發鏈接的場景下無疑是低效的。
poll:小有所成
poll和select是非常相似的,poll相對于select的優化僅僅在于解決了文件描述符不能超過1024個的限制,select和poll都會隨著監控的文件描述增加而出現性能下降,因此不適合高并發場景。
epoll:獨步天下
在select面臨的三個問題中,文件描述數量限制已經在poll中解決了,剩下的兩個問題呢?
針對第一個epoll使用的策略是各個擊破與共享內存。
實際上文件描述符集合變化的頻率比較低,select和poll頻繁的拷貝整個集合,內核都快要煩死了,epoll通過引入epoll_ctl很體貼的做到了只操作那些有變化的文件描述符,同時epoll和內核還成為了好朋友,共享了同一塊內存,這塊內存中保存的就是那些已經可讀或者可寫的的文件描述符集合,這樣就減少了內核和程序的內存拷貝開銷。
針對第二點,epoll使用的策略是“當小弟”。
在select和poll機制下,進程要親自下場去各個文件描述符上等待,任何一個文件描述可讀或者可寫就喚醒進程,但是進程被喚醒后也是一臉懵逼并不知道到底是哪個文件描述符可讀或可寫,還要再從頭到尾檢查一遍。
但epoll就懂事多了,主動找到進程要當小弟替大哥出頭。
?
?
參考https://www.zhihu.com/question/32163005/answer/76577586
?
要弄清問題 先要知道問題的出現原因
原因:
由于進程的執行過程是線性的(也就是順序執行),當我們調用低速系統I/O(read,write,accept等等),進程可能阻塞,此時進程就阻塞
在這個調用上,不能執行其他操作.阻塞很正常. 接下來考慮這么一個問題:一個服務器進程和一個客戶端進程通信,服務器端read(sockfd1,bud,bufsize),此時客戶端進程沒有發送數據,那么read(阻塞調用)將阻塞直到客戶端調用write(sockfd,but,size)發來數據. 在一個客戶和服務器通信時這沒什么問題,當多個客戶與服務器通信時,若服務器阻塞于其中一個客戶sockfd1,當另一客戶的數據到達套接字sockfd2時,服務器不能處理,仍然阻塞在read(sockfd1,...)上;此時問題就出現了,不能及時處理另一個客戶的服務,咋么辦?I/O多路復用來解決!I/O多路復用:繼續上面的問題,有多個客戶連接,sockfd1,sockfd2,sockfd3..sockfdn同時監聽這n個客戶,當其中有一個發來消息時就從select的阻塞中返回,然后就調用read讀取收到消息的sockfd,然后又循環回select阻塞;這樣就不會因為阻塞在其中一個上而不能處理另一個客戶的消息
Q:
那這樣子,在讀取socket1的數據時,如果其它socket有數據來,那么也要等到socket1讀取完了才能繼續讀取其它socket的數據吧。那不是也阻塞住了嗎?而且讀取到的數據也要開啟線程處理吧,那這和多線程IO有什么區別呢?
A:
1.CPU本來就是線性的 不論什么都需要順序處理 并行只能是多核CPU
http://2.io多路復用本來就是用來解決對多個I/O監聽時,一個I/O阻塞影響其他I/O的問題,跟多線程沒關系.
3.跟多線程相比較,線程切換需要切換到內核進行線程切換,需要消耗時間和資源. 而I/O多路復用不需要切換線/進程,效率相對較高,特別是對高并發的應用nginx就是用I/O多路復用,故而性能極佳.但多線程編程邏輯和處理上比I/O多路復用簡單.而I/O多路復用處理起來較為復雜.
?
?
?
參考:https://www.zhihu.com/question/32163005/answer/55772739
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
如果你把每一個航線當成一個Sock(I/O 流), 空管當成你的服務端Sock管理代碼的話.
第一種方法就是最傳統的多進程并發模型 (每進來一個新的I/O流會分配一個新的進程管理。)
第二種方法就是I/O多路復用 (單個線程,通過記錄跟蹤每個I/O流(sock)的狀態,來同時管理多個I/O流 。)
其實“I/O多路復用”這個坑爹翻譯可能是這個概念在中文里面如此難理解的原因。所謂的I/O多路復用在英文中其實叫 I/O multiplexing. 如果你搜索multiplexing啥意思,基本上都會出這個圖:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
于是大部分人都直接聯想到"一根網線,多個sock復用" 這個概念,包括上面的幾個回答, 其實不管你用多進程還是I/O多路復用, 網線都只有一根好伐。多個Sock復用一根網線這個功能是在內核+驅動層實現的。
重要的事情再說一遍: I/O multiplexing 這里面的 multiplexing 指的其實是在單個線程通過記錄跟蹤每一個Sock(I/O流)的狀態(對應空管塔里面的Fight progress strip槽)來同時管理多個I/O流. 發明它的原因,是盡量多的提高服務器的吞吐能力。
?
是不是聽起來好拗口,看個圖就懂了.
?
在同一個線程里面, 通過撥開關的方式,來同時傳輸多個I/O流, (學過EE的人現在可以站出來義正嚴辭說這個叫“時分復用”了)。
?
什么,你還沒有搞懂“一個請求到來了,nginx使用epoll接收請求的過程是怎樣的”, 多看看這個圖就了解了。提醒下,ngnix會有很多鏈接進來, epoll會把他們都監視起來,然后像撥開關一樣,誰有數據就撥向誰,然后調用相應的代碼處理。
------------------------------------------
了解這個基本的概念以后,其他的就很好解釋了。
select, poll, epoll 都是I/O多路復用的具體的實現,之所以有這三個鬼存在,其實是他們出現是有先后順序的。
I/O多路復用這個概念被提出來以后, select是第一個實現 (1983 左右在BSD里面實現的)。
select 被實現以后,很快就暴露出了很多問題。
- select 會修改傳入的參數數組,這個對于一個需要調用很多次的函數,是非常不友好的。
- select 如果任何一個sock(I/O stream)出現了數據,select 僅僅會返回,但是并不會告訴你是那個sock上有數據,于是你只能自己一個一個的找,10幾個sock可能還好,要是幾萬的sock每次都找一遍,這個無謂的開銷就頗有海天盛筵的豪氣了。
- select 只能監視1024個鏈接, 這個跟草榴沒啥關系哦,linux 定義在頭文件中的,參見FD_SETSIZE。
- select 不是線程安全的,如果你把一個sock加入到select, 然后突然另外一個線程發現,尼瑪,這個sock不用,要收回。對不起,這個select 不支持的,如果你喪心病狂的竟然關掉這個sock, select的標準行為是。。呃。。不可預測的, 這個可是寫在文檔中的哦.
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
霸不霸氣
于是14年以后(1997年)一幫人又實現了poll, poll 修復了select的很多問題,比如
- poll 去掉了1024個鏈接的限制,于是要多少鏈接呢, 主人你開心就好。
- poll 從設計上來說,不再修改傳入數組,不過這個要看你的平臺了,所以行走江湖,還是小心為妙。
其實拖14年那么久也不是效率問題, 而是那個時代的硬件實在太弱,一臺服務器處理1千多個鏈接簡直就是神一樣的存在了,select很長段時間已經滿足需求。
但是poll仍然不是線程安全的, 這就意味著,不管服務器有多強悍,你也只能在一個線程里面處理一組I/O流。你當然可以那多進程來配合了,不過然后你就有了多進程的各種問題。
于是5年以后, 在2002, 大神 Davide Libenzi 實現了epoll.
epoll 可以說是I/O 多路復用最新的一個實現,epoll 修復了poll 和select絕大部分問題, 比如:
- epoll 現在是線程安全的。
- epoll 現在不僅告訴你sock組里面數據,還會告訴你具體哪個sock有數據,你不用自己去找了。
?
epoll 當年的patch,現在還在,下面鏈接可以看得到:
/dev/epoll Home Page
貼一張霸氣的圖,看看當年神一樣的性能(測試代碼都是死鏈了, 如果有人可以刨墳找出來,可以研究下細節怎么測的).
橫軸Dead connections 就是鏈接數的意思,叫這個名字只是它的測試工具叫deadcon. 縱軸是每秒處理請求的數量,你可以看到,epoll每秒處理請求的數量基本不會隨著鏈接變多而下降的。poll 和/dev/poll 就很慘了。
?
可是epoll 有個致命的缺點。。只有linux支持。比如BSD上面對應的實現是kqueue。
其實有些國內知名廠商把epoll從安卓里面裁掉這種腦殘的事情我會主動告訴你嘛。什么,你說沒人用安卓做服務器,尼瑪你是看不起p2p軟件了啦。
而ngnix 的設計原則里面, 它會使用目標平臺上面最高效的I/O多路復用模型咯,所以才會有這個設置。一般情況下,如果可能的話,盡量都用epoll/kqueue吧。
詳細的在這里:
Connection processing methods
PS: 上面所有這些比較分析,都建立在大并發下面,如果你的并發數太少,用哪個,其實都沒有區別。 如果像是在歐朋數據中心里面的轉碼服務器那種動不動就是幾萬幾十萬的并發,不用epoll我可以直接去撞墻了。
?
?
?
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
?
轉載自https://blog.csdn.net/d12345678a/article/details/53944791?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-1&spm=1001.2101.3001.4242
?
%%%%%%%%%這個講的清楚%%%%%%%%%%%%
?
目前IO模型主要經歷了以下五種:
1)阻塞IO
2)非阻塞IO
3)IO復用(select和poll)
4)信號驅動IO(sigio)
5)異步IO(aio_)
?
內核空間和用戶空間:
由于操作系統都包括內核空間和用戶空間(或者說內核態和用戶態),內核空間主要存放的是內核代碼和數據,是供系統進程使用的空間。而用戶空間主要存放的是用戶代碼和數據,是供用戶進程使用的空間。目前Linux系統簡化了分段機制,使得虛擬地址與線性地址總是保持一致,因此,Linux系統的虛擬地址也是0~4G。Linux系統將這4G空間分為了兩個部分:將最高的1G空間(從虛擬地址0xC0000000到0xFFFFFFFF)供內核使用,即為“內核空間”,而將較低的3G空間(從虛擬地址 0x00000000到0xBFFFFFFF)供用戶進程使用,即為“用戶空間”。同時由于每個用戶進程都可以通過系統調用進入到內核空間,因此Linux的內核空間可以認為是被所有用戶進程所共享的,因此對于一個具體用戶進程來說,它可以訪問的虛擬內存地址就是0~4G。另外Linux系統分為了四種特權級:0~3,主要是用來保護資源。0級特權最高,而3級則為最低,系統進程主要運行在0級,用戶進程主要運行在3級。
?
一般來說,IO操作都分為兩個階段,就拿套接口的輸入操作來說,它的兩個階段主要是:
1)等待網絡數據到來,當分組到來時,將其拷貝到內核空間的臨時緩沖區中
2)將內核空間臨時緩沖區中的數據拷貝到用戶空間緩沖區中
?
1、阻塞IO
默認情況下,所有套接口都是阻塞的。
假如recvfrom函數是一個系統調用:
說明:任何一個系統調用都會產生一個由用戶態到內核態切換,再從內核態到用戶態切換的過程,而進程上下文切換是通過系統中斷程序來實現的,需要保存當前進程的上下文狀態,這是一個極其費力的過程。
?
2、非阻塞IO
當我們把套接口設置成非阻塞時,就是由用戶進程不停地詢問內核某種操作是否準備就緒,這就是我們常說的“輪詢”。這同樣是一件比較浪費CPU的方式。
?
3、IO復用
我們常用到的IO復用,主要是select和poll。這里同樣是會阻塞進程的,但是這里進程是阻塞在select或者poll這兩個系統調用上,而不是阻塞在真正的IO操作上。
另外還有一點不同于阻塞IO的就是,盡管看起來與阻塞IO相比,這里阻塞了兩次,但是第一次阻塞在select上時,select可以監控多個套接口上是否已有IO操作準備就緒的,而不是像阻塞IO那種,一次性只能監控一個套接口。
?
4、信號驅動IO
信號驅動IO就是說我們可以通過sigaction系統調用注冊一個信號處理程序,然后主程序可以繼續向下執行,當我們所監控的套接口有IO操作準備就緒時,由內核通知觸發前面注冊的信號處理程序執行,然后將我們所需要的數據從內核空間拷貝到用戶空間。
?
5、異步IO
異步IO與信號驅動IO最主要的區別就是信號驅動IO是由內核通知我們何時可以進行IO操作了,而異步IO則是由內核告訴我們IO操作何時完成了。具體來說就是,信號驅動IO當內核通知觸發信號處理程序時,信號處理程序還需要阻塞在從內核空間緩沖區拷貝數據到用戶空間緩沖區這個階段,而異步IO直接是在第二個階段完成后內核直接通知可以進程后續操作了。
?
綜上所述,我們發現 前四種IO模型的主要區別是在第一階段,因為它們的第二階段都是在阻塞等待數據由內核空間拷貝到用戶空間;而異步IO很明顯與前面四種有所不同,它在第一階段和第二階段都不會阻塞。具體參考如下:
?
最后,總結下同步IO與異步IO的區別:
1)同步IO操作會引起進程阻塞直到IO操作完成。
2)異步IO操作不引起進程阻塞。
因此,由上面定義可以看出,阻塞IO、非阻塞IO、IO復用、信號驅動IO都是屬于同步IO,而異步IO模型才與異步IO定義所匹配
總結
以上是生活随笔為你收集整理的操作系统IO模式(理解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 认识System Center之一
- 下一篇: javaWeb的线下服装店管理平台、基于