參考視頻:https://www.bilibili.com/video/BV12i4y1G7UK?from=search&seid=12243469803670169476
epoll模型是在單個(gè)線程中偵聽多個(gè)套接字fd行為的一種IO多路復(fù)用模型。主要有epoll_create,epoll_ctl,epoll_wait三個(gè)接口微峰。
一沛厨、epoll的使用
1. 創(chuàng)建epoll句柄
int epfd = epoll_create(intsize);
創(chuàng)建一個(gè)epoll的句柄,size用來告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大杜耙。size就是你在這個(gè)epoll fd上能關(guān)注的最大socket fd數(shù)成榜。
2.將被監(jiān)聽的描述符添加到epoll句柄或從epool句柄中刪除或者對監(jiān)聽事件進(jìn)行修改桐智。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
參數(shù):
epfd:由 epoll_create 生成的epoll專用的文件描述符;
op:要進(jìn)行的操作例如注冊事件辩尊,可能的取值EPOLL_CTL_ADD 注冊涛浙、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 刪除
fd:關(guān)聯(lián)的文件描述符;
event:指向epoll_event的指針轿亮;
如果調(diào)用成功返回0,不成功返回-1
第一個(gè)參數(shù)是epoll_create()的返回值疮薇,
第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來表示:
EPOLL_CTL_ADD:? ? ? 注冊新的fd到epfd中我注;
EPOLL_CTL_MOD:? ? ? 修改已經(jīng)注冊的fd的監(jiān)聽事件按咒;
EPOLL_CTL_DEL:? ? ? ? 從epfd中刪除一個(gè)fd;
第三個(gè)參數(shù)是需要監(jiān)聽的fd但骨,
第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事件励七,structepoll_event結(jié)構(gòu)如下:
typedefunionepoll_data{void*ptr;//指向要附加的數(shù)據(jù)結(jié)構(gòu)intfd;//一般設(shè)為監(jiān)視的fd__uint32_t u32;__uint64_t u64;}epoll_data_t;structepoll_event{/* Epoll events
events可以是以下幾個(gè)宏的集合:
? ? ? ? EPOLLIN:? ? ? ? ? ? 觸發(fā)該事件,表示對應(yīng)的文件描述符上有可讀數(shù)據(jù)奔缠。(包括對端SOCKET正常關(guān)閉)掠抬;
? ? ? ? EPOLLOUT:? ? ? ? 觸發(fā)該事件,表示對應(yīng)的文件描述符上可以寫數(shù)據(jù)校哎;
? ? ? ? EPOLLPRI:? ? ? ? ? 表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)两波;
? ? ? ? EPOLLERR:? ? ? ? 表示對應(yīng)的文件描述符發(fā)生錯(cuò)誤;
? ? ? ? EPOLLHUP:? ? ? ? 表示對應(yīng)的文件描述符被掛斷贬蛙;
? ? ? ? EPOLLET:? ? ? ? ? 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式雨女,這是相對于水平觸發(fā)(Level Triggered)來說的。
? ? ? ? EPOLLONESHOT:? 只監(jiān)聽一次事件阳准,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話馏臭,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里野蝇。
*/__uint32_t events;epoll_data_t data;/* User data variable */};
如要監(jiān)聽服務(wù)端套接字的連接,listenfd對對應(yīng)的socket套接字括儒,之前已經(jīng)bind和listen好绕沈。將它加入到epfd指定的epoll對象中
structepoll_eventev;//設(shè)置與要處理的事件相關(guān)的文件描述符ev.data.fd=listenfd;//設(shè)置要處理的事件類型ev.events=EPOLLIN|EPOLLET;//注冊epoll事件epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
3.等待事件觸發(fā),當(dāng)超過timeout還沒有事件觸發(fā)時(shí)帮寻,就超時(shí)乍狐。
int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
函數(shù)聲明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
該函數(shù)用于輪詢I/O事件的發(fā)生;參數(shù):
epfd:由epoll_create 生成的epoll專用的文件描述符固逗;
epoll_event:用于回傳代處理事件的數(shù)組浅蚪,已經(jīng)分配好內(nèi)存;
maxevents:每次能處理的最大事件數(shù)烫罩;
timeout:等待I/O事件發(fā)生的超時(shí)值(單位我也不太清楚)惜傲;-1相當(dāng)于阻塞,0相當(dāng)于非阻塞贝攒。一般用-1即可
返回發(fā)生事件數(shù)盗誊。
二、epoll的原理
本節(jié)會(huì)以示例和圖表來講解epoll的原理和流程。
1.創(chuàng)建epoll對象
如下圖所示哈踱,當(dāng)某個(gè)進(jìn)程調(diào)用epoll_create方法時(shí)荒适,內(nèi)核會(huì)創(chuàng)建一個(gè)eventpoll對象(也就是程序中epfd所代表的對象)。eventpoll對象也是文件系統(tǒng)中的一員开镣,和socket一樣吻贿,它也會(huì)有等待隊(duì)列。
內(nèi)核創(chuàng)建eventpoll對象
創(chuàng)建一個(gè)代表該epoll的eventpoll對象是必須的哑子,因?yàn)閮?nèi)核要維護(hù)“就緒列表”等數(shù)據(jù)舅列,“就緒列表”可以作為eventpoll的成員。
2.維護(hù)監(jiān)視列表
創(chuàng)建epoll對象后卧蜓,可以用epoll_ctl添加或刪除所要監(jiān)聽的socket帐要。以添加socket為例,如下圖弥奸,如果通過epoll_ctl添加sock1榨惠、sock2和sock3的監(jiān)視,內(nèi)核會(huì)將eventpoll添加到這三個(gè)socket的等待隊(duì)列中盛霎。
添加所要監(jiān)聽的socket
當(dāng)socket收到數(shù)據(jù)后赠橙,中斷程序會(huì)操作eventpoll對象,而不是直接操作進(jìn)程愤炸。
3.接收數(shù)據(jù)
當(dāng)socket收到數(shù)據(jù)后期揪,中斷程序會(huì)給eventpoll的“就緒列表”添加socket引用。如下圖展示的是sock2和sock3收到數(shù)據(jù)后规个,中斷程序讓rdlist引用這兩個(gè)socket凤薛。
給就緒列表添加引用
eventpoll對象相當(dāng)于是socket和進(jìn)程之間的中介,socket的數(shù)據(jù)接收并不直接影響進(jìn)程诞仓,而是通過改變eventpoll的就緒列表來改變進(jìn)程狀態(tài)缤苫。
當(dāng)程序執(zhí)行到epoll_wait時(shí),如果rdlist已經(jīng)引用了socket墅拭,那么epoll_wait直接返回活玲,如果rdlist為空,阻塞進(jìn)程谍婉。
4.阻塞和喚醒進(jìn)程
假設(shè)計(jì)算機(jī)中正在運(yùn)行進(jìn)程A和進(jìn)程B舒憾,在某時(shí)刻進(jìn)程A運(yùn)行到了epoll_wait語句。如下圖所示屡萤,內(nèi)核會(huì)將進(jìn)程A放入eventpoll的等待隊(duì)列中珍剑,阻塞進(jìn)程。
epoll_wait阻塞進(jìn)程
當(dāng)socket接收到數(shù)據(jù)死陆,中斷程序一方面修改rdlist招拙,另一方面喚醒eventpoll等待隊(duì)列中的進(jìn)程唧瘾,進(jìn)程A再次進(jìn)入運(yùn)行狀態(tài)(如下圖)。也因?yàn)閞dlist的存在别凤,進(jìn)程A可以知道哪些socket發(fā)生了變化饰序。
epoll喚醒進(jìn)程
三、epoll的實(shí)現(xiàn)細(xì)節(jié)
讀完這篇文章规哪,還有三個(gè)問題需要細(xì)究一下求豫。
eventpoll的數(shù)據(jù)結(jié)構(gòu)是什么樣子?
就緒隊(duì)列應(yīng)該應(yīng)使用什么數(shù)據(jù)結(jié)構(gòu)诉稍?
eventpoll應(yīng)使用什么數(shù)據(jù)結(jié)構(gòu)來管理通過epoll_ctl添加或刪除的socket蝠嘉?
如下圖所示,eventpoll包含了lock杯巨、mtx蚤告、wq(等待隊(duì)列)、rdlist等成員服爷。rdlist和rbr是我們所關(guān)心的杜恰。
epoll原理示意圖
1. 就緒列表的數(shù)據(jù)結(jié)構(gòu)
就緒列表引用著就緒的socket,所以它應(yīng)能夠快速的插入數(shù)據(jù)仍源。
程序可能隨時(shí)調(diào)用epoll_ctl添加監(jiān)視socket心褐,也可能隨時(shí)刪除。當(dāng)刪除時(shí)笼踩,若該socket已經(jīng)存放在就緒列表中逗爹,它也應(yīng)該被移除。
所以就緒列表應(yīng)是一種能夠快速插入和刪除的數(shù)據(jù)結(jié)構(gòu)戳表。雙向鏈表就是這樣一種數(shù)據(jù)結(jié)構(gòu)桶至,epoll使用雙向鏈表來實(shí)現(xiàn)就緒隊(duì)列(對應(yīng)上圖的rdllist)。
2.索引結(jié)構(gòu)
既然epoll將“維護(hù)監(jiān)視隊(duì)列”和“進(jìn)程阻塞”分離匾旭,也意味著需要有個(gè)數(shù)據(jù)結(jié)構(gòu)來保存監(jiān)視的socket。至少要方便的添加和移除圃郊,還要便于搜索价涝,以避免重復(fù)添加。紅黑樹是一種自平衡二叉查找樹持舆,搜索色瘩、插入和刪除時(shí)間復(fù)雜度都是O(log(N)),效率較好逸寓。epoll使用了紅黑樹作為索引結(jié)構(gòu)(對應(yīng)上圖的rbr)居兆,存儲(chǔ)所監(jiān)視的socket fd。
作者:hjx_zju
鏈接:http://www.reibang.com/p/4c50e740ddd5
來源:簡書
著作權(quán)歸作者所有竹伸。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)泥栖,非商業(yè)轉(zhuǎn)載請注明出處簇宽。