Epoll是Linux IO多路複用的管理機制。做爲如今Linux平臺高性能網絡IO必要的組件。內核的實現能夠參照:fs/eventpoll.c .
git
爲何須要本身實現epoll呢?如今本身打算作一個用戶態的協議棧。採用單線程的模式。github.com/wangbojing/…,至於爲何要實現用戶態協議棧?能夠自行百度C10M的問題。github
因爲協議棧作到了用戶態故須要本身實現高性能網絡IO的管理。因此epoll就本身實現一下。代碼:github.com/wangbojing/…安全
在實現epoll以前,先得好好理解內核epoll的運行原理。內核的epoll能夠從四方面來理解。網絡
1. Epoll的數據結構,rbtree對<fd, event>的存儲,ready隊列存儲就緒io。數據結構
2. Epoll的線程安全,SMP的運行,以及防止死鎖。socket
3. Epoll內核回調。tcp
4. Epoll的LT(水平觸發)與ET(邊沿觸發)函數
下面從這四個方面來實現epoll。性能
Epoll主要由兩個結構體:eventpoll與epitem。Epitem是每個IO所對應的的事件。好比 epoll_ctl EPOLL_CTL_ADD操做的時候,就須要建立一個epitem。Eventpoll是每個epoll所對應的的。好比epoll_create 就是建立一個eventpoll。線程
Epitem的定義
Eventpoll的定義
數據結構以下圖所示。
List 用來存儲準備就緒的IO。對於數據結構主要討論兩方面:insert與remove。一樣如此,對於list咱們也討論insert與remove。什麼時候將數據插入到list中呢?當內核IO準備就緒的時候,則會執行epoll_event_callback的回調函數,將epitem添加到list中。
那什麼時候刪除list中的數據呢?當epoll_wait激活從新運行的時候,將list的epitem逐一copy到events參數中。
Rbtree用來存儲全部io的數據,方便快速通io_fd查找。也從insert與remove來討論。
對於rbtree什麼時候添加:當App執行epoll_ctl EPOLL_CTL_ADD操做,將epitem添加到rbtree中。什麼時候刪除呢?當App執行epoll_ctl EPOLL_CTL_DEL操做,將epitem添加到rbtree中。
List與rbtree的操做又如何作到線程安全,SMP,防止死鎖呢?
Epoll 從如下幾個方面是須要加鎖保護的。List的操做,rbtree的操做,epoll_wait的等待。
List使用最小粒度的鎖spinlock,便於在SMP下添加操做的時候,可以快速操做list。
List添加
346行:獲取spinlock。
347行:epitem 的rdy置爲1,表明epitem已經在就緒隊列中,後續再觸發相同事件就只需更改event。
348行:添加到list中。
349行:將eventpoll的rdnum域 加1。
350行:釋放spinlock
List刪除
301行:獲取spinlock
304行:判讀rdnum與maxevents的大小,避免event溢出。
307行:循環遍歷list,判斷添加list不能爲空
309行:獲取list首個結點
310行:移除list首個結點。
311行:將epitem的rdy域置爲0,標識epitem再也不就緒隊列中。
313行:copy epitem的event到用戶空間的events。
316行:copy數量加1
317行:eventpoll中rdnum減一。
避免SMP體系下,多核競爭。此處採用自旋鎖,不適合採用睡眠鎖。
Rbtree的添加
149行:獲取互斥鎖。
153行:查找sockid的epitem是否存在。存在則不能添加,不存在則能夠添加。
160行:分配epitem。
167行:sockid賦值
168行:將設置的event添加到epitem的event域。
170行:將epitem添加到rbrtree中。
173行:釋放互斥鎖。
Rbtree刪除:
177行:獲取互斥鎖。
181行:刪除sockid的結點,若是不存在,則rbtree返回-1。
188行:釋放epitem
190行:釋放互斥鎖。
Epoll_wait的掛起。
採用pthread_cond_wait,具體實現能夠參照。
Epoll 的回調函數什麼時候執行,此部分須要與Tcp的協議棧一塊兒來闡述。Tcp協議棧的時序圖以下圖所示,epoll從協議棧回調的部分從下圖的編號1,2,3,4。具體Tcp協議棧的實現,後續從另外的文章中表述出來。下面分別對四個步驟詳細描述
編號1:是tcp三次握手,對端反饋ack後,socket進入rcvd狀態。須要將監聽socket的event置爲EPOLLIN,此時標識能夠進入到accept讀取socket數據。
編號2:在established狀態,收到數據之後,須要將socket的event置爲EPOLLIN狀態。
編號3:在established狀態,收到fin時,此時socket進入到close_wait。須要socket的event置爲EPOLLIN。讀取斷開信息。
編號4:檢測socket的send狀態,若是對端cwnd>0是能夠,發送的數據。故須要將socket置爲EPOLLOUT。
因此在此四處添加EPOLL的回調函數,便可使得epoll正常接收到io事件。
LT(水平觸發)與ET(邊沿觸發)是電子信號裏面的概念。不清楚能夠man epoll查看的。以下圖所示:
好比:event = EPOLLIN | EPOLLLT,將event設置爲EPOLLIN與水平觸發。只要event爲EPOLLIN時就能不斷調用epoll回調函數。
好比: event = EPOLLIN | EPOLLET,event若是從EPOLLOUT變化爲EPOLLIN的時候,就會觸發。在此情形下,變化只發生一次,故只調用一次epoll回調函數。關於水平觸發與邊沿觸發放在epoll回調函數執行的時候,若是爲EPOLLET(邊沿觸發),與以前的event對比,若是發生改變則調用epoll回調函數,若是爲EPOLLLT(水平觸發),則查看event是否爲EPOLLIN,便可調用epoll回調函數。