什么是IO
io是數(shù)據(jù)的接收和發(fā)送操作,linux進程無法直接操作io設備揍诽,需要通過系統(tǒng)調用請求內核來完成io操作诀蓉,內核為每個設備維護一個緩沖區(qū)。用戶進程發(fā)送操作的一個完整io包括兩部分:用戶空間將數(shù)據(jù)發(fā)送到內核暑脆,內核將數(shù)據(jù)發(fā)送到io設備渠啤。用戶進程接收操作的一個完整io也是包括兩部分:內核從io設備中接收數(shù)據(jù)到緩沖區(qū),從內核緩沖區(qū)復制數(shù)據(jù)到進程空間
5種io模型
阻塞io:進程發(fā)起io操作后添吗,進程被阻塞沥曹,轉到內核空間處理,整個io處理完后返回進程。特點:需要為每一個io請求分配一個進程或線程來處理
非阻塞io:進程發(fā)起IO系統(tǒng)調用后妓美,如果內核緩沖區(qū)沒有數(shù)據(jù)僵腺,需要到IO設備中讀取,進程返回一個錯誤而不會被阻塞壶栋;進程發(fā)起IO系統(tǒng)調用后辰如,如果內核緩沖區(qū)有數(shù)據(jù),內核就會把數(shù)據(jù)返回進程贵试。需要進程主動去輪詢琉兜。
io多路復用:?linuxIO多路復用技術提供一個單進程、單線程監(jiān)聽多個IO讀寫時間的機制锡移。其基本原理是各個IO將句柄設置為非阻塞IO,然后將各個IO句柄注冊到linux提供的IO復用函數(shù)上(select,poll或者epoll),如果某個句柄的IO數(shù)據(jù)就緒,則函數(shù)返回通知io ready呕童。調用者進行后續(xù)的read write操作漆际。多路復用函數(shù)幫我們進行了多個非阻塞IO數(shù)據(jù)是否就緒的輪詢操作,只不過IO多路復用函數(shù)的輪詢更有效率,因為函數(shù)一次性傳遞文件描述符到內核態(tài),在內核態(tài)中進行輪詢(epoll則是進行等待邊緣事件的觸發(fā)),不必反復進行用戶態(tài)和內核態(tài)的切換淆珊。io多路復用的特點:內核輪詢多個io在內核緩沖區(qū)是否ready,適合高并發(fā)網(wǎng)絡服務應用奸汇。高并發(fā)網(wǎng)絡服務應用如果采用阻塞io怎么實現(xiàn)施符?需要為每個socket連接啟動一個線程,頻繁的線程切換會導致性能低下擂找。如果采用非阻塞io怎么實現(xiàn)戳吝?需要進程輪詢每個io,如果有一萬個socket連接贯涎,確定哪個連接ready需要進行1萬次從用戶態(tài)到內核態(tài)的切換听哭,性能低下。
信號驅動io:進程發(fā)起io操作塘雳,會向內核注冊一個信號處理函數(shù)陆盘,然后進程返回不阻塞,當內核數(shù)據(jù)就緒時發(fā)送一個信號給進程败明,進程在信號處理函數(shù)中調用io函數(shù)處理隘马。特點:回調機制,開發(fā)難度大
異步io:進程發(fā)起一個io操作后妻顶,進程返回酸员。內核把整個io處理完后(包括將數(shù)據(jù)從內核緩沖區(qū)復制到用戶態(tài))通知進程,進程只需要在指定的數(shù)組中引用數(shù)據(jù)即可
同步io和異步io
同步IO:用戶進程發(fā)出IO調用讳嘱,去獲取IO設備數(shù)據(jù)幔嗦,雙方的數(shù)據(jù)要經(jīng)過內核緩沖區(qū)同步,完全準備好后沥潭,再復制返回到用戶進程邀泉。而復制返回到用戶進程會導致請求進程阻塞,直到I/O操作完成叛氨。
異步IO:用戶進程發(fā)出IO調用呼渣,去獲取IO設備數(shù)據(jù)棘伴,并不需要同步,內核直接復制到進程屁置,整個過程不導致請求進程阻塞焊夸。
阻塞io、非阻塞io蓝角、io多路復用阱穗、信號驅動io都是同步io
同步IO最終需要應用程序調用系統(tǒng)調用從內核來讀取數(shù)據(jù)、異步IO由系統(tǒng)來負責將數(shù)據(jù)從內核讀取到應用程序使鹅,應用程序直接使用揪阶。
select
1 可以一次性從用戶態(tài)向內核態(tài)傳遞多個fd
2 內核把當前進程掛到相應io設備的等待隊列中
3 內核逐個io判斷是否就緒,如果有就緒的就返回(select返回)患朱,如果全部未就緒鲁僚,調用schedule_timeout將進程睡眠
4 當設備驅動發(fā)生自身資源可讀寫后,喚醒其隊列上的睡眠進程裁厅,進程返回(select返回)
5 如果超過schedule_timeout還沒人喚醒冰沙,進程喚醒重新遍歷fd重復上邊流程
優(yōu)點:
1 一次性傳遞多個fd
2 在內核中遍歷,不必反復進行用戶態(tài)和內核態(tài)的切換
3 具有喚醒機制
缺點:
總結一句話就是:一堆fd從用戶態(tài)拷貝到內核態(tài)执虹,內核態(tài)遍歷一堆fd拓挥,內核態(tài)返回后用戶態(tài)需要遍歷一堆fd,這一堆的個數(shù)還限制的比較小
1 每次調用select袋励,都需要把fd集合從用戶態(tài)拷貝到內核態(tài)返回時還要從內核態(tài)拷貝到用戶態(tài)侥啤,這個開銷在fd很多時會很大
2?每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
3?select返回后茬故,用戶不得不自己再遍歷一遍fd集合盖灸,以找到哪些fd的IO操作可用
4?再次調用select時,fd數(shù)組需要重新被初始化
5?select支持的文件描述符數(shù)量太小了均牢,內核中采用位圖存儲糠雨,默認是1024
poll
1 通過一個pollfd數(shù)組向內核傳遞需要關注的事件,故沒有描述符個數(shù)的限制徘跪,其他的沒有區(qū)別
epoll
epoll三大關鍵要素:mmap甘邀、紅黑樹、鏈表
mmap:epoll通過mmap將用戶空間的一塊地址和內核空間的一塊地址映射到相同的一塊物理內存地址垮庐,減少用戶態(tài)和內核態(tài)之間的數(shù)據(jù)交換松邪,內核可以直接看到監(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集合從用戶空間賦值到內核空間)
在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。當調用epoll_ctrl添加fd和事件時褂傀,該事件都會和相應的設備驅動程序建立回調關系忍啤,當相應的事件發(fā)生后,調用ep_poll_callback回調函數(shù)仙辟,這個函數(shù)把這個事件添加到rdllist雙向鏈表中同波。
op可以指定操作類型:EPOLL_CTL_ADD(往事件表中注冊fd上的事件)、EPOLL_CTL_MOD(修改fd上的注冊事件)叠国、EPOLL_CTL_DEL(刪除fd上的注冊事件)
?event:指定fd關注的事件
3?int epoll_wait (int epfd,struct epoll_event* events,int maxevents,int timeout );
(1)?epoll_wait調用ep_poll未檩,當rdllist為空(無就緒fd)時掛起當前進程,直到rdlist不空時進程才被喚醒粟焊。
(2)?文件fd狀態(tài)改變冤狡,導致相應fd上的回調函數(shù)ep_poll_callback()被調用。
(3)?ep_poll_callback將相應fd對應epitem加入rdlist吆玖,導致rdlist不空筒溃,進程被喚醒,epoll_wait得以繼續(xù)執(zhí)行沾乘。
(4)?ep_events_transfer函數(shù)將rdlist中的epitem拷貝到txlist中,并將rdlist清空浑测。
(5)?ep_send_events函數(shù)翅阵,它掃描txlist中的每個epitem,調用其關聯(lián)fd對用的poll方法迁央。此時對poll的調用僅僅是取得fd上較新的events(防止之前events被更新)掷匠,之后將取得的events和相應的fd發(fā)送到用戶空間(封裝在struct?epoll_event,從epoll_wait返回)岖圈。
ET模式和LT模式
epoll_wait返回的兩個時機:
時機一:fd狀態(tài)改變讹语,ep_poll_callback被調用,加入rdllist蜂科。對于讀操作:一是buffer由不可讀狀態(tài)變?yōu)榭勺x的時候顽决。二是有新數(shù)據(jù)到達,buffer中待讀的內容變多的時候导匣。對于寫操作:一是buffer由不可寫變?yōu)榭蓪懙臅r候才菠。二是舊數(shù)據(jù)發(fā)送走,buffer中可寫的空間變大的時候贡定。
時機二:fd的events中有相應的事件位置1時赋访。對于讀操作:buffer中有數(shù)據(jù)可讀,buffer不為空的時候fd的events的可讀位就置1。對于寫操作:buffer中有空間可寫蚓耽,buffer不滿的時候fd的可寫位就置1渠牲。
對于ET模式,只采用上述時機一步悠。LT模式采用上述時機一和時機二嘱兼。
ET模式注意事項
ET模式下,讀操作如果一次沒有讀盡buffer中的數(shù)據(jù)贤徒,將得不到讀就緒的通知芹壕,造成buffer中已有的數(shù)據(jù)無機會讀出,除非有新的數(shù)據(jù)再次到達接奈。因此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的調用。
https://blog.csdn.net/daaikuaichuan/article/details/88777274
高并發(fā)服務器模型epoll+線程池
這種架構特點如下:
1 基于I/O多路復用的思想腌巾,通過單線程I/O多路復用遂填,可以達到高效并發(fā),同時避免了多線程I/O來回切換的各種開銷澈蝙。
2 由于業(yè)務多跟數(shù)據(jù)庫打交道會造成阻塞吓坚,基于線程池的多工作者線程,可以充分發(fā)揮和利用多線程的優(yōu)勢灯荧。
創(chuàng)建一個epoll實例;
while(server?running)
{
????epoll等待事件;
????if(新連接到達且是有效連接)
????{
????????accept此連接;
????????將此連接設置為non-blocking;
????????為此連接設置event(EPOLLIN?|?EPOLLET?...);
????????將此連接加入epoll監(jiān)聽隊列;
????????從線程池取一個空閑工作者線程并處理此連接;
????}
????else?if(讀請求)
????{
????????從線程池取一個空閑工作者線程并處理讀請求;
????}
????else?if(寫請求)
????{
????????從線程池取一個空閑工作者線程并處理寫請求;
????}
????else
????????其他事件;?????
}
golang net庫網(wǎng)絡實現(xiàn)分析
golang中的網(wǎng)絡io全部是非阻塞io
網(wǎng)絡io的read操作如下:
1 golang協(xié)程通過系統(tǒng)調用來讀取數(shù)據(jù)
2 如果數(shù)據(jù)沒準備好礁击,調用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)設置為可運行
4 讀io的協(xié)程繼續(xù)運行撕贞,再次通過系統(tǒng)調用來讀取數(shù)據(jù)更耻,最終返回
底層本質是:非阻塞io+epoll+線程池+任務隊列的模型,epoll返回哪個任務的io 已經(jīng)ready捏膨,線程池中的線程取io ready的任務繼續(xù)執(zhí)行秧均,goalng通過協(xié)程將這些全部封裝好了食侮,通過協(xié)程實現(xiàn)了非阻塞的io可以采用阻塞的方式編程