linux IO多路復(fù)用筆記

什么是IO

io是數(shù)據(jù)的接收和發(fā)送操作,linux進程無法直接操作io設(shè)備,需要通過系統(tǒng)調(diào)用請求內(nèi)核來完成io操作,內(nèi)核為每個設(shè)備維護一個緩沖區(qū)。用戶進程發(fā)送操作的一個完整io包括兩部分:用戶空間將數(shù)據(jù)發(fā)送到內(nèi)核,內(nèi)核將數(shù)據(jù)發(fā)送到io設(shè)備。用戶進程接收操作的一個完整io也是包括兩部分:內(nèi)核從io設(shè)備中接收數(shù)據(jù)到緩沖區(qū),從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)到進程空間

5種io模型

阻塞io:進程發(fā)起io操作后,進程被阻塞,轉(zhuǎn)到內(nèi)核空間處理,整個io處理完后返回進程。特點:需要為每一個io請求分配一個進程或線程來處理

非阻塞io:進程發(fā)起IO系統(tǒng)調(diào)用后,如果內(nèi)核緩沖區(qū)沒有數(shù)據(jù),需要到IO設(shè)備中讀取,進程返回一個錯誤而不會被阻塞;進程發(fā)起IO系統(tǒng)調(diào)用后,如果內(nèi)核緩沖區(qū)有數(shù)據(jù),內(nèi)核就會把數(shù)據(jù)返回進程。需要進程主動去輪詢。

io多路復(fù)用:?linuxIO多路復(fù)用技術(shù)提供一個單進程、單線程監(jiān)聽多個IO讀寫時間的機制。其基本原理是各個IO將句柄設(shè)置為非阻塞IO,然后將各個IO句柄注冊到linux提供的IO復(fù)用函數(shù)上(select,poll或者epoll),如果某個句柄的IO數(shù)據(jù)就緒,則函數(shù)返回通知io ready。調(diào)用者進行后續(xù)的read write操作。多路復(fù)用函數(shù)幫我們進行了多個非阻塞IO數(shù)據(jù)是否就緒的輪詢操作,只不過IO多路復(fù)用函數(shù)的輪詢更有效率,因為函數(shù)一次性傳遞文件描述符到內(nèi)核態(tài),在內(nèi)核態(tài)中進行輪詢(epoll則是進行等待邊緣事件的觸發(fā)),不必反復(fù)進行用戶態(tài)和內(nèi)核態(tài)的切換。io多路復(fù)用的特點:內(nèi)核輪詢多個io在內(nèi)核緩沖區(qū)是否ready,適合高并發(fā)網(wǎng)絡(luò)服務(wù)應(yīng)用。高并發(fā)網(wǎng)絡(luò)服務(wù)應(yīng)用如果采用阻塞io怎么實現(xiàn)?需要為每個socket連接啟動一個線程,頻繁的線程切換會導(dǎo)致性能低下。如果采用非阻塞io怎么實現(xiàn)?需要進程輪詢每個io,如果有一萬個socket連接,確定哪個連接ready需要進行1萬次從用戶態(tài)到內(nèi)核態(tài)的切換,性能低下。

信號驅(qū)動io:進程發(fā)起io操作,會向內(nèi)核注冊一個信號處理函數(shù),然后進程返回不阻塞,當(dāng)內(nèi)核數(shù)據(jù)就緒時發(fā)送一個信號給進程,進程在信號處理函數(shù)中調(diào)用io函數(shù)處理。特點:回調(diào)機制,開發(fā)難度大

異步io:進程發(fā)起一個io操作后,進程返回。內(nèi)核把整個io處理完后(包括將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶態(tài))通知進程,進程只需要在指定的數(shù)組中引用數(shù)據(jù)即可

同步io和異步io

同步IO:用戶進程發(fā)出IO調(diào)用,去獲取IO設(shè)備數(shù)據(jù),雙方的數(shù)據(jù)要經(jīng)過內(nèi)核緩沖區(qū)同步,完全準(zhǔn)備好后,再復(fù)制返回到用戶進程。而復(fù)制返回到用戶進程會導(dǎo)致請求進程阻塞,直到I/O操作完成。

異步IO:用戶進程發(fā)出IO調(diào)用,去獲取IO設(shè)備數(shù)據(jù),并不需要同步,內(nèi)核直接復(fù)制到進程,整個過程不導(dǎo)致請求進程阻塞。

阻塞io、非阻塞io、io多路復(fù)用、信號驅(qū)動io都是同步io

同步IO最終需要應(yīng)用程序調(diào)用系統(tǒng)調(diào)用從內(nèi)核來讀取數(shù)據(jù)、異步IO由系統(tǒng)來負(fù)責(zé)將數(shù)據(jù)從內(nèi)核讀取到應(yīng)用程序,應(yīng)用程序直接使用。

select

1 可以一次性從用戶態(tài)向內(nèi)核態(tài)傳遞多個fd

2 內(nèi)核把當(dāng)前進程掛到相應(yīng)io設(shè)備的等待隊列中

3 內(nèi)核逐個io判斷是否就緒,如果有就緒的就返回(select返回),如果全部未就緒,調(diào)用schedule_timeout將進程睡眠

4 當(dāng)設(shè)備驅(qū)動發(fā)生自身資源可讀寫后,喚醒其隊列上的睡眠進程,進程返回(select返回)

5 如果超過schedule_timeout還沒人喚醒,進程喚醒重新遍歷fd重復(fù)上邊流程

優(yōu)點:

1 一次性傳遞多個fd

2 在內(nèi)核中遍歷,不必反復(fù)進行用戶態(tài)和內(nèi)核態(tài)的切換

3 具有喚醒機制

缺點:

總結(jié)一句話就是:一堆fd從用戶態(tài)拷貝到內(nèi)核態(tài),內(nèi)核態(tài)遍歷一堆fd,內(nèi)核態(tài)返回后用戶態(tài)需要遍歷一堆fd,這一堆的個數(shù)還限制的比較小

1 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài)返回時還要從內(nèi)核態(tài)拷貝到用戶態(tài),這個開銷在fd很多時會很大

2?每次調(diào)用select都需要在內(nèi)核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

3?select返回后,用戶不得不自己再遍歷一遍fd集合,以找到哪些fd的IO操作可用

4?再次調(diào)用select時,fd數(shù)組需要重新被初始化

5?select支持的文件描述符數(shù)量太小了,內(nèi)核中采用位圖存儲,默認(rèn)是1024

poll

1 通過一個pollfd數(shù)組向內(nèi)核傳遞需要關(guān)注的事件,故沒有描述符個數(shù)的限制,其他的沒有區(qū)別

epoll

epoll三大關(guān)鍵要素:mmap、紅黑樹、鏈表

mmap:epoll通過mmap將用戶空間的一塊地址和內(nèi)核空間的一塊地址映射到相同的一塊物理內(nèi)存地址,減少用戶態(tài)和內(nèi)核態(tài)之間的數(shù)據(jù)交換,內(nèi)核可以直接看到監(jiān)聽的句柄

紅黑樹:epoll采用紅黑樹來存儲監(jiān)聽的套接字。epoll_ctl添加或者刪除一個套接字時,都在紅黑樹上處理,紅黑樹的插入和刪除性能比較好

雙向鏈表:epoll的rdllist采用的就是雙向鏈表

epoll主要函數(shù)

1?int? epoll_create (int size );

采用epoll_create()建立epoll描述符來記錄需要監(jiān)控的fd(select每次都把fd集合從用戶空間賦值到內(nèi)核空間)

在epoll早期的實現(xiàn)中,對于監(jiān)控文件描述符的組織并不是使用紅黑樹,而是hash表。這里的size實際上已經(jīng)沒有意義。

2?int? epoll_ctl (int epfd,int op,int fd,struct epoll_event *event);

epoll_ctrl用來添加或刪除待監(jiān)控的fd。當(dāng)調(diào)用epoll_ctrl添加fd和事件時,該事件都會和相應(yīng)的設(shè)備驅(qū)動程序建立回調(diào)關(guān)系,當(dāng)相應(yīng)的事件發(fā)生后,調(diào)用ep_poll_callback回調(diào)函數(shù),這個函數(shù)把這個事件添加到rdllist雙向鏈表中。

op可以指定操作類型:EPOLL_CTL_ADD(往事件表中注冊fd上的事件)、EPOLL_CTL_MOD(修改fd上的注冊事件)、EPOLL_CTL_DEL(刪除fd上的注冊事件)

?event:指定fd關(guān)注的事件

3?int epoll_wait (int epfd,struct epoll_event* events,int maxevents,int timeout );

(1)?epoll_wait調(diào)用ep_poll,當(dāng)rdllist為空(無就緒fd)時掛起當(dāng)前進程,直到rdlist不空時進程才被喚醒。

(2)?文件fd狀態(tài)改變,導(dǎo)致相應(yīng)fd上的回調(diào)函數(shù)ep_poll_callback()被調(diào)用。

(3)?ep_poll_callback將相應(yīng)fd對應(yīng)epitem加入rdlist,導(dǎo)致rdlist不空,進程被喚醒,epoll_wait得以繼續(xù)執(zhí)行。

(4)?ep_events_transfer函數(shù)將rdlist中的epitem拷貝到txlist中,并將rdlist清空。

(5)?ep_send_events函數(shù),它掃描txlist中的每個epitem,調(diào)用其關(guān)聯(lián)fd對用的poll方法。此時對poll的調(diào)用僅僅是取得fd上較新的events(防止之前events被更新),之后將取得的events和相應(yīng)的fd發(fā)送到用戶空間(封裝在struct?epoll_event,從epoll_wait返回)。

ET模式和LT模式

epoll_wait返回的兩個時機:

時機一:fd狀態(tài)改變,ep_poll_callback被調(diào)用,加入rdllist。對于讀操作:一是buffer由不可讀狀態(tài)變?yōu)榭勺x的時候。二是有新數(shù)據(jù)到達(dá),buffer中待讀的內(nèi)容變多的時候。對于寫操作:一是buffer由不可寫變?yōu)榭蓪懙臅r候。二是舊數(shù)據(jù)發(fā)送走,buffer中可寫的空間變大的時候。

時機二:fd的events中有相應(yīng)的事件位置1時。對于讀操作:buffer中有數(shù)據(jù)可讀,buffer不為空的時候fd的events的可讀位就置1。對于寫操作:buffer中有空間可寫,buffer不滿的時候fd的可寫位就置1。

對于ET模式,只采用上述時機一。LT模式采用上述時機一和時機二。


ET模式注意事項

ET模式下,讀操作如果一次沒有讀盡buffer中的數(shù)據(jù),將得不到讀就緒的通知,造成buffer中已有的數(shù)據(jù)無機會讀出,除非有新的數(shù)據(jù)再次到達(dá)。因此ET模式下需要注意:1,采用非阻塞io? 2,循環(huán)讀寫,保證讀完、寫滿。

ET模式和LT模式如何保證數(shù)據(jù)可以被讀完?ET模式由用戶程序來循環(huán),LT模式通過多次epoll_wait返回來循環(huán)。

建議采用ET模式+非阻塞io+循環(huán)讀寫,相比LT模式,省去了多次epoll_wait的調(diào)用。

https://blog.csdn.net/daaikuaichuan/article/details/88777274

高并發(fā)服務(wù)器模型epoll+線程池


這種架構(gòu)特點如下:

1 基于I/O多路復(fù)用的思想,通過單線程I/O多路復(fù)用,可以達(dá)到高效并發(fā),同時避免了多線程I/O來回切換的各種開銷。

2 由于業(yè)務(wù)多跟數(shù)據(jù)庫打交道會造成阻塞,基于線程池的多工作者線程,可以充分發(fā)揮和利用多線程的優(yōu)勢。

創(chuàng)建一個epoll實例;

while(server?running)

{

????epoll等待事件;

????if(新連接到達(dá)且是有效連接)

????{

????????accept此連接;

????????將此連接設(shè)置為non-blocking;

????????為此連接設(shè)置event(EPOLLIN?|?EPOLLET?...);

????????將此連接加入epoll監(jiān)聽隊列;

????????從線程池取一個空閑工作者線程并處理此連接;

????}

????else?if(讀請求)

????{

????????從線程池取一個空閑工作者線程并處理讀請求;

????}

????else?if(寫請求)

????{

????????從線程池取一個空閑工作者線程并處理寫請求;

????}

????else

????????其他事件;?????

}

golang net庫網(wǎng)絡(luò)實現(xiàn)分析

golang中的網(wǎng)絡(luò)io全部是非阻塞io

網(wǎng)絡(luò)io的read操作如下:

1 golang協(xié)程通過系統(tǒng)調(diào)用來讀取數(shù)據(jù)

2 如果數(shù)據(jù)沒準(zhǔn)備好,調(diào)用waitRead----->runtime_pollWait----->poll_runtime_pollWait------>netpollblock------>gopark------->park_m--------->schedule? 進行協(xié)程的切換。這樣就通過非阻塞io加協(xié)程切換模擬出了阻塞io,可以采用阻塞io的簡單開發(fā)方式

3 golang中起一個線程定期執(zhí)行sysmon函數(shù),sysmon---->netpoll----->epollwait? 返回ready的協(xié)程列表,sysmon----->injectglist------>casgstatus將ready的協(xié)程的狀態(tài)設(shè)置為可運行

4 讀io的協(xié)程繼續(xù)運行,再次通過系統(tǒng)調(diào)用來讀取數(shù)據(jù),最終返回

底層本質(zhì)是:非阻塞io+epoll+線程池+任務(wù)隊列的模型,epoll返回哪個任務(wù)的io 已經(jīng)ready,線程池中的線程取io ready的任務(wù)繼續(xù)執(zhí)行,goalng通過協(xié)程將這些全部封裝好了,通過協(xié)程實現(xiàn)了非阻塞的io可以采用阻塞的方式編程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容