Epoll是Linux IO多路復(fù)用的管理機(jī)制账月。作為現(xiàn)在Linux平臺(tái)高性能網(wǎng)絡(luò)IO必要的組件综膀。內(nèi)核的實(shí)現(xiàn)可以參照:fs/eventpoll.c .
為什么需要自己實(shí)現(xiàn)epoll呢澳迫?現(xiàn)在自己打算做一個(gè)用戶態(tài)的協(xié)議棧。采用單線程的模式剧劝。https://github.com/wangbojing/NtyTcp橄登,至于為什么要實(shí)現(xiàn)用戶態(tài)協(xié)議棧?可以自行百度C10M的問(wèn)題讥此。
由于協(xié)議棧做到了用戶態(tài)故需要自己實(shí)現(xiàn)高性能網(wǎng)絡(luò)IO的管理拢锹。所以epoll就自己實(shí)現(xiàn)一下。代碼:https://github.com/wangbojing/NtyTcp/blob/master/src/nty_epoll_rb.c
在實(shí)現(xiàn)epoll之前萄喳,先得好好理解內(nèi)核epoll的運(yùn)行原理卒稳。內(nèi)核的epoll可以從四方面來(lái)理解。
1.?Epoll的數(shù)據(jù)結(jié)構(gòu)他巨,rbtree對(duì)的存儲(chǔ)充坑,ready隊(duì)列存儲(chǔ)就緒io。
2.?Epoll的線程安全染突,SMP的運(yùn)行捻爷,以及防止死鎖。
3.?Epoll內(nèi)核回調(diào)份企。
4.?Epoll的LT(水平觸發(fā))與ET(邊沿觸發(fā))
下面從這四個(gè)方面來(lái)實(shí)現(xiàn)epoll也榄。
一、Epoll數(shù)據(jù)結(jié)構(gòu)
Epoll主要由兩個(gè)結(jié)構(gòu)體:eventpoll與epitem司志。Epitem是每一個(gè)IO所對(duì)應(yīng)的的事件甜紫。比如?epoll_ctl EPOLL_CTL_ADD操作的時(shí)候,就需要?jiǎng)?chuàng)建一個(gè)epitem骂远。Eventpoll是每一個(gè)epoll所對(duì)應(yīng)的的囚霸。比如epoll_create?就是創(chuàng)建一個(gè)eventpoll。
Epitem的定義
Eventpoll的定義
數(shù)據(jù)結(jié)構(gòu)如下圖所示吧史。
List?用來(lái)存儲(chǔ)準(zhǔn)備就緒的IO邮辽。對(duì)于數(shù)據(jù)結(jié)構(gòu)主要討論兩方面:insert與remove唠雕。同樣如此,對(duì)于list我們也討論insert與remove吨述。何時(shí)將數(shù)據(jù)插入到list中呢岩睁?當(dāng)內(nèi)核IO準(zhǔn)備就緒的時(shí)候,則會(huì)執(zhí)行epoll_event_callback的回調(diào)函數(shù)揣云,將epitem添加到list中捕儒。
那何時(shí)刪除list中的數(shù)據(jù)呢?當(dāng)epoll_wait激活重新運(yùn)行的時(shí)候邓夕,將list的epitem逐一copy到events參數(shù)中刘莹。
Rbtree用來(lái)存儲(chǔ)所有io的數(shù)據(jù),方便快速通io_fd查找焚刚。也從insert與remove來(lái)討論点弯。
對(duì)于rbtree何時(shí)添加:當(dāng)App執(zhí)行epoll_ctl EPOLL_CTL_ADD操作,將epitem添加到rbtree中矿咕。何時(shí)刪除呢抢肛?當(dāng)App執(zhí)行epoll_ctl EPOLL_CTL_DEL操作,將epitem添加到rbtree中碳柱。
List與rbtree的操作又如何做到線程安全捡絮,SMP,防止死鎖呢莲镣?
二福稳、Epoll鎖機(jī)制
Epoll?從以下幾個(gè)方面是需要加鎖保護(hù)的。List的操作瑞侮,rbtree的操作的圆,epoll_wait的等待。
List使用最小粒度的鎖spinlock区岗,便于在SMP下添加操作的時(shí)候略板,能夠快速操作list。
List添加
346行:獲取spinlock慈缔。
347行:epitem?的rdy置為1叮称,代表epitem已經(jīng)在就緒隊(duì)列中,后續(xù)再觸發(fā)相同事件就只需更改event藐鹤。
348行:添加到list中瓤檐。
349行:將eventpoll的rdnum域?加1。
350行:釋放spinlock
List刪除
301行:獲取spinlock
304行:判讀rdnum與maxevents的大小娱节,避免event溢出挠蛉。
307行:循環(huán)遍歷list,判斷添加list不能為空
309行:獲取list首個(gè)結(jié)點(diǎn)
310行:移除list首個(gè)結(jié)點(diǎn)肄满。
311行:將epitem的rdy域置為0瓦灶,標(biāo)識(shí)epitem不再就緒隊(duì)列中。
313行:copy epitem的event到用戶空間的events皱蹦。
316行:copy數(shù)量加1
317行:eventpoll中rdnum減一。
避免SMP體系下汇陆,多核競(jìng)爭(zhēng)。此處采用自旋鎖带饱,不適合采用睡眠鎖毡代。
Rbtree的添加
149行:獲取互斥鎖。
153行:查找sockid的epitem是否存在勺疼。存在則不能添加教寂,不存在則可以添加。
160行:分配epitem执庐。
167行:sockid賦值
168行:將設(shè)置的event添加到epitem的event域酪耕。
170行:將epitem添加到rbrtree中。
173行:釋放互斥鎖耕肩。
Rbtree刪除:
177行:獲取互斥鎖因妇。
181行:刪除sockid的結(jié)點(diǎn)问潭,如果不存在猿诸,則rbtree返回-1。
188行:釋放epitem
190行:釋放互斥鎖狡忙。
Epoll_wait的掛起梳虽。
采用pthread_cond_wait,具體實(shí)現(xiàn)可以參照灾茁。
https://github.com/wangbojing/NtyTcp/blob/master/src/nty_epoll_rb.c
三窜觉、Epoll回調(diào)
Epoll?的回調(diào)函數(shù)何時(shí)執(zhí)行,此部分需要與Tcp的協(xié)議棧一起來(lái)闡述北专。Tcp協(xié)議棧的時(shí)序圖如下圖所示禀挫,epoll從協(xié)議棧回調(diào)的部分從下圖的編號(hào)1,2,3,4拓颓。具體Tcp協(xié)議棧的實(shí)現(xiàn)语婴,后續(xù)從另外的文章中表述出來(lái)。下面分別對(duì)四個(gè)步驟詳細(xì)描述
編號(hào)1:是tcp三次握手驶睦,對(duì)端反饋ack后砰左,socket進(jìn)入rcvd狀態(tài)。需要將監(jiān)聽(tīng)socket的event置為EPOLLIN场航,此時(shí)標(biāo)識(shí)可以進(jìn)入到accept讀取socket數(shù)據(jù)缠导。
編號(hào)2:在established狀態(tài),收到數(shù)據(jù)以后溉痢,需要將socket的event置為EPOLLIN狀態(tài)僻造。
編號(hào)3:在established狀態(tài)憋他,收到fin時(shí),此時(shí)socket進(jìn)入到close_wait髓削。需要socket的event置為EPOLLIN举瑰。讀取斷開(kāi)信息。
編號(hào)4:檢測(cè)socket的send狀態(tài)蔬螟,如果對(duì)端cwnd>0是可以此迅,發(fā)送的數(shù)據(jù)。故需要將socket置為EPOLLOUT旧巾。
所以在此四處添加EPOLL的回調(diào)函數(shù)耸序,即可使得epoll正常接收到io事件。
四鲁猩、LT與ET
LT(水平觸發(fā))與ET(邊沿觸發(fā))是電子信號(hào)里面的概念坎怪。不清楚可以man epoll查看的。如下圖所示:
比如:event = EPOLLIN | EPOLLLT廓握,將event設(shè)置為EPOLLIN與水平觸發(fā)搅窿。只要event為EPOLLIN時(shí)就能不斷調(diào)用epoll回調(diào)函數(shù)。
比如: event = EPOLLIN | EPOLLET隙券,event如果從EPOLLOUT變化為EPOLLIN的時(shí)候男应,就會(huì)觸發(fā)。在此情形下娱仔,變化只發(fā)生一次沐飘,故只調(diào)用一次epoll回調(diào)函數(shù)。關(guān)于水平觸發(fā)與邊沿觸發(fā)放在epoll回調(diào)函數(shù)執(zhí)行的時(shí)候牲迫,如果為EPOLLET(邊沿觸發(fā))耐朴,與之前的event對(duì)比,如果發(fā)生改變則調(diào)用epoll回調(diào)函數(shù)盹憎,如果為EPOLLLT(水平觸發(fā))筛峭,則查看event是否為EPOLLIN,即可調(diào)用epoll回調(diào)函數(shù)陪每。