在看kafka的生產(chǎn)者基于NIO構(gòu)建網(wǎng)絡(luò)通信層NetworkClient的時(shí)候,發(fā)覺自己對(duì)網(wǎng)絡(luò)通信的相關(guān)知識(shí)(同步馆衔,異步瘟判,阻塞怨绣,非阻塞角溃,?Reactor拷获,Proactor,Linux的IO模型减细,IO的多路復(fù)用等知識(shí))有點(diǎn)模糊匆瓜,熟悉喃也好像不怎么熟悉,了解喃也好像不怎么了解未蝌。那么就在這里重新梳理一遍驮吱。
同步和異步針對(duì)應(yīng)用程序,關(guān)注的是程序之間的協(xié)作關(guān)系萧吠;阻塞和非阻塞關(guān)注的是進(jìn)程/線程之間的執(zhí)行狀態(tài)(正在執(zhí)行或者是掛起)左冬。
同步 異步
同步:執(zhí)行一個(gè)操作之后,等待結(jié)果后纸型,然后才繼續(xù)執(zhí)行后續(xù)的操作拇砰。
異步:執(zhí)行一個(gè)操作之后,可以去執(zhí)行其他的操作狰腌,然后等待通知后除破,再回來執(zhí)行剛剛那個(gè)操作后續(xù)的操作。
阻塞 非阻塞
阻塞:進(jìn)程給CPU一個(gè)任務(wù)后琼腔,一直等待CPU處理完成瑰枫,然后才執(zhí)行后面的操作。
非阻塞:進(jìn)程給CPU一個(gè)任務(wù)后丹莲,繼續(xù)處理后面的操作光坝,間隔一段時(shí)間在來詢問之前的任務(wù)是否完成,完成了甥材,就繼續(xù)執(zhí)行后續(xù)的操作教馆。這個(gè)過程叫輪詢。
阻塞和非阻塞是指進(jìn)程/線程訪問的數(shù)據(jù)如果尚未就緒擂达,進(jìn)程/線程是否需要等待土铺。
同步和異步是指訪問數(shù)據(jù)的機(jī)制:同步一般指主動(dòng)請(qǐng)求并等待I/O操作完畢的方式,當(dāng)數(shù)據(jù)就緒后進(jìn)程/線程在讀寫的時(shí)候必須阻塞(等待數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū))板鬓。異步則指主動(dòng)請(qǐng)求I/O數(shù)據(jù)后悲敷,繼續(xù)處理其他任務(wù)。隨后等待I/O操作完畢的通知(callback)俭令,這樣可以使進(jìn)程/線程在讀寫時(shí)不阻塞后德。
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取,socket在Linux系統(tǒng)被抽象為流抄腔,IO可以理解為對(duì)流的操作瓢湃。對(duì)于一次IO訪問理张,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后再?gòu)牟僮飨到y(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間绵患。
同步模型
阻塞IO
最常用的IO模型就是阻塞IO模型雾叭,在缺省條件下,所有的socket操作都是阻塞的落蝙,以socket讀為例(底層是調(diào)用recvfrom方法):
在用戶空間調(diào)用recvfrom织狐,直到數(shù)據(jù)包全部達(dá)到并且從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程的緩沖區(qū)或者中間發(fā)生異常返回的這段期間進(jìn)程/線程一直等待。進(jìn)程/線程從調(diào)用recvfrom開始到recvfrom返回整段時(shí)間內(nèi)都被阻塞----阻塞IO模型筏勒。
非阻塞IO
非阻塞的recvfrom調(diào)用移迫,從應(yīng)用到內(nèi)核時(shí),如果緩沖區(qū)沒有數(shù)據(jù)管行,進(jìn)程不會(huì)被阻塞厨埋,內(nèi)核會(huì)馬上返回一個(gè)EWOULDBLOCK錯(cuò)誤。這樣進(jìn)程可以做點(diǎn)其他的事情捐顷,然后進(jìn)程再發(fā)起recvfrom調(diào)用踏拜,如果緩沖區(qū)還沒有數(shù)據(jù)待错,又會(huì)返回EWOULDBLOCK錯(cuò)誤言询。就這樣循環(huán)往復(fù)的進(jìn)行recvform系統(tǒng)調(diào)用环戈,這種就叫做輪詢。輪詢檢查到內(nèi)核的緩沖區(qū)有數(shù)據(jù)準(zhǔn)備好了逗柴,再拷貝到應(yīng)用的緩沖區(qū)蛹头,進(jìn)程進(jìn)行數(shù)據(jù)處理。數(shù)據(jù)從內(nèi)核緩沖區(qū)到應(yīng)用的緩沖區(qū)這個(gè)時(shí)間段戏溺,進(jìn)程屬于阻塞狀態(tài)渣蜗。
多路復(fù)用IO
Linux提供了select,poll旷祸,epoll耕拷。應(yīng)用系統(tǒng)調(diào)用阻塞于select調(diào)用,等待socket變?yōu)榭勺x托享,就返回這個(gè)可讀的條件骚烧。進(jìn)程再調(diào)用recvfrom把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用緩沖區(qū)(這個(gè)過程是阻塞的)
前面的非阻塞IO需要用戶應(yīng)用不停的去輪詢,并且一個(gè)進(jìn)程/線程只能關(guān)注一個(gè)socket闰围。多路復(fù)用IO不需要用戶應(yīng)用去不停的輪詢赃绊,而是讓Linux下的select,poll羡榴,epoll去幫忙輪詢多個(gè)任務(wù)的完成情況碧查。select調(diào)用是內(nèi)核級(jí)別,select輪詢相對(duì)非阻塞IO的輪詢的區(qū)別在于---select輪詢可以等待多個(gè)socket,能實(shí)現(xiàn)同時(shí)對(duì)多個(gè)IO端口進(jìn)行監(jiān)聽忠售。當(dāng)其中任何一個(gè)socket的數(shù)據(jù)準(zhǔn)好了传惠,就能返回進(jìn)行可讀,然后進(jìn)程再進(jìn)行recvform系統(tǒng)調(diào)用稻扬,將數(shù)據(jù)由內(nèi)核拷貝到用戶進(jìn)程卦方,當(dāng)然這個(gè)過程是阻塞的。調(diào)用select后腐螟,會(huì)阻塞進(jìn)程愿汰,于阻塞IO不同(等待所有數(shù)據(jù)都到達(dá))困后,select不是等到socket數(shù)據(jù)全部到達(dá)再處理, 而是有了一部分?jǐn)?shù)據(jù)(網(wǎng)絡(luò)上的數(shù)據(jù)是分組到達(dá)的乐纸,如何知道有一部分?jǐn)?shù)據(jù)到達(dá)了呢?監(jiān)視的事情交給了內(nèi)核摇予,內(nèi)核負(fù)責(zé)數(shù)據(jù)到達(dá)的處理)就會(huì)通知用戶應(yīng)用讀準(zhǔn)備好了汽绢,然后進(jìn)程調(diào)用recvform來處理。
1侧戴,對(duì)多個(gè)socket進(jìn)行監(jiān)聽宁昭,只要任何一個(gè)socket數(shù)據(jù)準(zhǔn)備好就返回可讀。
2酗宋,不等一個(gè)socket數(shù)據(jù)全部到達(dá)再處理积仗,而是一部分socket的數(shù)據(jù)到達(dá)了就通知用戶進(jìn)程。這就是我們經(jīng)常在代碼里面寫循環(huán)來處理數(shù)據(jù):
while ((networkReceive = channel.read()) != null){
// 處理讀取到的一部分的socket數(shù)據(jù)
}
信號(hào)驅(qū)動(dòng)IO
首先開啟套接字的信號(hào)驅(qū)動(dòng)式IO功能蜕猫,并且通過sigaction系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù)寂曹,該函數(shù)調(diào)用將立即返回,當(dāng)前進(jìn)程沒有被阻塞回右,繼續(xù)工作隆圆;當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好的時(shí)候,內(nèi)核則為該進(jìn)程產(chǎn)生SIGIO的信號(hào)翔烁,隨既可以在信號(hào)處理函數(shù)中調(diào)用recvfrom讀取數(shù)據(jù)報(bào)渺氧,并且通知主循環(huán);數(shù)據(jù)已經(jīng)準(zhǔn)備好等待處理蹬屹,通知主循環(huán)讀取數(shù)據(jù)報(bào)侣背;(一個(gè)待讀取的通知和待處理的通知);
異步IO
告知內(nèi)核啟動(dòng)讀取事件慨默,并讓內(nèi)核在整個(gè)操作完成后(包括將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用程序緩沖區(qū))通知用戶進(jìn)程贩耐。
文件描述符(fd)
文件描述符(file descriptor,簡(jiǎn)稱fd)业筏,是一個(gè)非負(fù)整數(shù)憔杨。當(dāng)進(jìn)程打開一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符蒜胖。在Linux系統(tǒng)中消别,內(nèi)核將所有的外部設(shè)備都當(dāng)做一個(gè)文件來進(jìn)行操作抛蚤,而對(duì)于一個(gè)文件的讀寫操作會(huì)調(diào)用內(nèi)核提供的系統(tǒng)命令,返回一個(gè)fd寻狂。對(duì)于一個(gè)socket的讀寫也會(huì)有相應(yīng)的描述符---socketfd(socket描述符)岁经,指向內(nèi)核中的一個(gè)結(jié)構(gòu)體(文件路徑,數(shù)據(jù)區(qū)域等屬性)蛇券。
Linux的IO多路復(fù)用模型
基本思想:先構(gòu)造一張有關(guān)文件描述符的表缀壤,然后調(diào)用一個(gè)函數(shù),這個(gè)函數(shù)要到這些文件描述符中一個(gè)已經(jīng)準(zhǔn)備好進(jìn)行IO操作后才返回纠亚。在返回時(shí)塘慕,此函數(shù)告訴進(jìn)程哪一個(gè)文件描述符已經(jīng)準(zhǔn)備好可以進(jìn)行IO操作。
IO多路復(fù)用通過把多個(gè)IO阻塞復(fù)用到同一個(gè)select的阻塞上蒂胞,從而使得系統(tǒng)在單線程的情況下图呢,可以同時(shí)處理多個(gè) client 請(qǐng)求,與傳統(tǒng)的多線程相比骗随,IO多路復(fù)用的最大優(yōu)勢(shì)是系統(tǒng)開銷小蛤织,系統(tǒng)不需要?jiǎng)?chuàng)建新的額外的進(jìn)程或線程,也不需要維護(hù)這些進(jìn)程和線程的運(yùn)行鸿染,節(jié)省了系統(tǒng)資源指蚜。
目前支持I/O多路復(fù)用的系統(tǒng)有select,pselect涨椒,poll摊鸡,epoll,I/O多路復(fù)用就是通過一種機(jī)制丢烘,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符柱宦,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作播瞳。但select掸刊,pselect,poll赢乓,epoll本質(zhì)上都是同步I/O忧侧,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說這個(gè)讀寫過程是阻塞的牌芋,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫蚓炬,異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。用戶進(jìn)程之間從用戶緩沖區(qū)中讀取數(shù)據(jù)躺屁。真正的IO阻塞是數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū)中肯夏,這正是上面recvfrom方法做的事情。所以從消息機(jī)制來看都是同步IO。
select
select函數(shù)的參數(shù)會(huì)告訴內(nèi)核:
1驯击,關(guān)心的文件描述符有哪些烁兰。
2,對(duì)于每一個(gè)文件描述符關(guān)心的條件是什么:是否可以讀一個(gè)文件描述符徊都,是否可以寫一個(gè)文件描述符沪斟,文件描述符的異常情況。
3暇矫,進(jìn)程希望等待多長(zhǎng)的時(shí)間(在沒有文件描述符準(zhǔn)備的時(shí)候主之,好久返回):可以永遠(yuǎn)等待,等待一個(gè)固定的時(shí)間李根,完全不等待槽奕。
在select函數(shù)返回時(shí),內(nèi)核會(huì)告訴我們:
1朱巨,已經(jīng)準(zhǔn)備好的文件描述符的數(shù)量史翘。
2枉长,哪一個(gè)文件描述符已準(zhǔn)備好讀冀续,寫或者異常條件。
select函數(shù)監(jiān)視的文件描述符分3類必峰,分別是writefds洪唐、readfds、和exceptfds吼蚁。這三個(gè)指針是指向描述符集的凭需。這三個(gè)描述符集代表了我們關(guān)心的可讀,可寫或處于異常條件的各個(gè)描述符肝匆。每個(gè)描述符集存放在一個(gè)fd_set數(shù)據(jù)類型中粒蜈,fd_set用了一個(gè)32位矢量來表示fd,Linux中FD_SETSIZE設(shè)置默認(rèn)是1024旗国。調(diào)用后select函數(shù)會(huì)阻塞枯怖,直到有描述符就緒(有數(shù)據(jù)可讀、可寫)能曾,或者超時(shí)(timeout指定等待時(shí)間)函數(shù)返回度硝。當(dāng)select函數(shù)返回后,可以通過遍歷fd_set寿冕,來找到就緒的描述符蕊程。
select的缺點(diǎn):
1,在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制驼唱。由FD_SETSIZE設(shè)置藻茂,在Linux上默認(rèn)為1024。
2,當(dāng)socket比較多的時(shí)候辨赐,每次的select都要遍歷FD_SETSIZE個(gè)socket岗钩。不是遍歷的socket是否是活躍的,都要遍歷一遍肖油。浪費(fèi)很多CPU時(shí)間兼吓。
3,需要維護(hù)一個(gè)用來存放大量fd的數(shù)據(jù)結(jié)構(gòu)(操作socket森枪,其實(shí)就是對(duì)文件描述符的操作)视搏,這樣在從內(nèi)核空間復(fù)制到用戶空間開銷很大。
poll
poll函數(shù)類似select县袱,與select不同的是浑娜,poll不是為每個(gè)條件構(gòu)造一個(gè)描述符集(writefds、readfds式散、和exceptfds)筋遭,而是構(gòu)造一個(gè)fd結(jié)構(gòu)數(shù)組,數(shù)組中每一個(gè)元素指定一個(gè)描述符編號(hào)和關(guān)心的事件暴拄。查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài)漓滔。如果設(shè)備就緒則在設(shè)置數(shù)組中描述符編號(hào)對(duì)應(yīng)的描述符發(fā)生了什么事件(revent),如果遍歷完所有 fd 后沒有發(fā)現(xiàn)就緒設(shè)備乖篷,則掛起當(dāng)前進(jìn)程响驴。直到設(shè)備就緒或者主動(dòng)超時(shí),被喚醒后它又要再次遍歷fd撕蔼。這個(gè)過程經(jīng)歷了多次無謂的遍歷豁鲤。
poll沒有FD_SETSIZE限制。因?yàn)椴捎脭?shù)組方式存儲(chǔ)要檢查的fd鲸沮。
poll的缺點(diǎn):
1琳骡,fd結(jié)構(gòu)數(shù)組(數(shù)據(jù)量大)被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間之間,而不管這樣的復(fù)制是不是有意義讼溺。
2楣号,如果報(bào)告了fd后,沒有被處理肾胯,那么下次poll時(shí)會(huì)再次報(bào)告該fd竖席。
select和poll最后都需要遍歷文件描述符來獲取已經(jīng)就緒的socket。因?yàn)槲覀冎皇侵烙惺录l(fā)生敬肚,但是并不知道是那幾個(gè)流毕荐,所以只能無差別的輪詢一遍所有的流。其實(shí)在同時(shí)連接大量客戶端在一時(shí)刻可能只有很少的處于就緒狀態(tài)艳馒。但是也要全部遍歷憎亚,這樣隨著監(jiān)視的描述符數(shù)量的增長(zhǎng)员寇,其效率也會(huì)線性下降。
epoll
在內(nèi)核里第美,一切都是文件蝶锋。epoll會(huì)向內(nèi)核注冊(cè)一個(gè)文件系統(tǒng),用于存儲(chǔ)被監(jiān)控的socket描述符什往。epoll在被內(nèi)核初始化的時(shí)(操作系統(tǒng)啟動(dòng)時(shí))扳缕,會(huì)開辟屬于epoll自己的內(nèi)核高速緩存區(qū)。
epoll的相關(guān)操作
int epoll_create:創(chuàng)建一個(gè)epoll對(duì)象别威,epollfd = epoll_create()躯舔。返回一個(gè)文件描述符,這個(gè)文件描述符指向內(nèi)核中的事件表省古,這個(gè)事件表存放的是用戶關(guān)心的文件描述符粥庄。相當(dāng)于使用一個(gè)文件描述符來管理用戶的所有的文件描述符。調(diào)用epoll_create方法會(huì)向epoll向內(nèi)核注冊(cè)的文件系統(tǒng)里面創(chuàng)建一個(gè)file節(jié)點(diǎn)豺妓。并且在屬于epoll的高速緩存區(qū)中建立一個(gè)紅黑樹用于存儲(chǔ)epoll_ctl傳來的socket描述符惜互。最后還會(huì)建立一個(gè)就緒的鏈表,用于存儲(chǔ)準(zhǔn)備就緒的事件琳拭。(文件系統(tǒng)和緩存區(qū)直接采用mmap來添加映射關(guān)系)
int epoll_ctl:操作epoll_create建立的epoll训堆,可將新建的socket描述符加入到epoll讓其監(jiān)控,或者移除正在被監(jiān)控的某個(gè)socket描述符臀栈。返回值:若成功蔫慧,返回0,若出錯(cuò)返回-1权薯。epoll_ctl把socket描述符添加到紅黑樹上,還會(huì)給內(nèi)核的中斷處理程序注冊(cè)一個(gè)回調(diào)函數(shù)睡扬,這樣內(nèi)核會(huì)在此socket描述符中斷到了后盟蚣,就把它放到就緒的鏈表中。(中斷:網(wǎng)卡上收到了數(shù)據(jù)卖怜,從CPU發(fā)送一個(gè)中斷請(qǐng)求屎开,CPU會(huì)從當(dāng)前正在執(zhí)行的動(dòng)作中抽出身來獲取數(shù)據(jù),然后通過驅(qū)動(dòng)程序通知操作系統(tǒng)马靠,操作系統(tǒng)通知epoll在內(nèi)核的中斷處理程序上注冊(cè)的回調(diào)函數(shù)把處理此socket描述符-->放到就緒鏈表中奄抽。)
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout):等待所監(jiān)聽文件描述符上有事件發(fā)生。返回值:若成功甩鳄,返回就緒的文件描述符個(gè)數(shù)逞度,若出錯(cuò),返回-1妙啃,時(shí)間超時(shí)返回0档泽。epoll_wait直接監(jiān)控就緒鏈表里面有沒有數(shù)據(jù)即可俊戳,有數(shù)據(jù)就返回,沒有數(shù)據(jù)就sleep馆匿,如果過了timeout不管有無數(shù)據(jù)都直接返回抑胎,就不用去掃描所有的socket描述符(fd)。如果我們即使監(jiān)控?cái)?shù)百萬的fd渐北。那么在就緒鏈表里面也會(huì)只有很少的fd就緒阿逃,這樣epoll_wait從內(nèi)核的緩沖區(qū)復(fù)制在用戶的緩沖區(qū)中是很少的數(shù)據(jù),這也就是epoll_wait高效所在之一赃蛛。
struct epoll_event *events:
struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
疑問:epoll_wait()方法的返回值的是fd就緒的個(gè)數(shù)盆昙,那么應(yīng)用系統(tǒng)又是怎么知道具體是哪些fd就緒的?epoll不會(huì)去遍歷整個(gè)fd焊虏。
解答:通過查看源碼發(fā)現(xiàn)在epoll_wait方法中淡喜,會(huì)從通過就緒鏈表拿出就緒的事件,然后向用戶空間發(fā)送data信息诵闭。if(__put_user(revents,&events[eventcnt.].events) || __put_user(epi->event.data,&events[eventcnt].data)):event.data就是epoll_data炼团。epoll_data里面包含fd,fd就是可讀或者可寫的文件描述符。應(yīng)用程序就知道哪個(gè)fd可讀可寫了疏尿。
epoll的執(zhí)行過程:
1瘟芝,執(zhí)行epoll_create創(chuàng)建紅黑樹和就緒鏈表。2褥琐,執(zhí)行epoll_ctl時(shí)锌俱,向紅黑樹增加socket描述符,并向內(nèi)核注冊(cè)回調(diào)函數(shù)敌呈,當(dāng)中斷事件發(fā)生后通過回調(diào)函數(shù)想就緒鏈表中寫入數(shù)據(jù)贸宏。3,當(dāng)事件(可讀可寫)發(fā)生epoll_wait被觸發(fā)磕洪,直接向用戶緩存區(qū)返回準(zhǔn)備就緒的fd吭练。
關(guān)鍵:1,紅黑樹高速的查找析显、插入鲫咽、刪除。2谷异,就緒鏈表只保存準(zhǔn)備就緒的描述符分尸,減少?gòu)膬?nèi)核緩沖區(qū)到用戶緩沖區(qū)的復(fù)制大小。
epoll對(duì)文件描述符有兩種模式:LT(level trigger)和ET(edge trigger)LT是默認(rèn)模式歹嘹。
LT模式:采用LT模式的文件描述符箩绍,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理此事件荞下,當(dāng)下一次調(diào)用epoll_wait時(shí)伶选,epoll_wait還會(huì)將此事件通告應(yīng)用程序史飞。
ET模式:當(dāng)調(diào)用epoll_ctl,向參數(shù)event注冊(cè)EPOLLET事件時(shí)仰税,epoll將以ET模式來操作該文件描述符构资,ET模式是epoll的高效工作模式。對(duì)于采用ET模式的文件描述符陨簇,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此通知應(yīng)用程序后吐绵,應(yīng)用程序必須立即處理該事件,因?yàn)楹罄m(xù)的epoll_wait調(diào)用將不在向應(yīng)用程序通知這一事件河绽。ET模式降低了同一epoll事件被觸發(fā)的次數(shù)己单,效率比LT模式高。
總結(jié):在select/poll中耙饰,進(jìn)程調(diào)用select方法纹笼,將所監(jiān)控的描述符從用戶緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū)中,將數(shù)以萬計(jì)的socket描述符從用戶緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū)里面苟跪,這是非常低效的廷痘。內(nèi)核對(duì)所有監(jiān)視的文件描述符進(jìn)行全部掃描。epoll事先通過epoll_ctl()把文件描述符增加到紅黑樹上件已,一旦某個(gè)文件描述符就緒時(shí)笋额,內(nèi)核會(huì)調(diào)用epoll_ctl在內(nèi)核上注冊(cè)的回調(diào)函數(shù)來將此描述符添加到就緒鏈表中,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)就將就緒鏈表里的fd從內(nèi)核緩沖區(qū)復(fù)制到用戶的緩沖區(qū)篷扩,這次的copy是很少量的兄猩。
Java的IO模型可以參考:談一談 Java IO 模型 | Matt's Blog?里面說的比較好
參考資料:
阻塞和非阻塞,同步和異步 總結(jié) - banananana - 博客園
關(guān)于同步鉴未、異步與阻塞枢冤、非阻塞的理解 - Anker's Blog - 博客園
Unix 網(wǎng)絡(luò) IO 模型及 Linux 的 IO 多路復(fù)用模型 | Matt's Blog