epoll機制詳解

      select,poll,epoll都是IO多路複用的機制。I/O多路複用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒,能夠通知程序進行相應的讀寫操作。其中epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對於select和poll來說,epoll更加靈活

一.epoll的相關操作概覽:

1.int epoll_create(int size);

int epoll_create(int size);

 

epoll_create 創建一個epoll對象,一般epollfd = epoll_create()

2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

第一個參數是創建的epoll對象

第二個參數是具體的操作類型,如添加、刪除

epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//添加

epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//刪除

 

 

第三個是需要監聽的fd,第四個是監聽事件,該事件結構如下

struct epoll_event {

  __uint32_t events;  /* Epoll events */

  epoll_data_t data;  /* User data variable */

};

 

 __uint32_t events:

其中events是以下宏的集合

EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);

EPOLLOUT:表示對應的文件描述符可以寫;

EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);

EPOLLERR:表示對應的文件描述符發生錯誤;

EPOLLHUP:表示對應的文件描述符被掛斷;

EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。

EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏

  epoll_data_t data:

data可以存儲對應文件描述符(fd)和任意自定義函數或類(ptr)

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

 

(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

這裏是epoll主要的特點,select函數只是當有可讀描述符後返回但並不知道具體是那個描述符,返回後還需要輪詢,文件描述符數量越多,性能越差。而epoll的epoll_wait函數可以返回具體可操作的文件描述符,存儲在events裏。Maxevents告訴內核這個events有多大。

二.實現機制

來看具體的實現機制

1.某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體

struct eventpoll{

    ....

    struct rb_root  rbr;   //紅黑樹的根節點,存儲着所有添加到epoll中的需要監控的事件


    struct list_head rdlist;    //雙鏈表,存放着將要通過epoll_wait返回給用戶的滿足條件的事件

    ....

};

 rbr是該epoll對象監聽的所有文件對象,rdlist是放進去的有事件發生的文件描述符。

在epoll中,對於每一個事件,都會建立一個epitem結構體

struct epitem{

    struct rb_node  rbn;//紅黑樹節點

    struct list_head    rdllink;//雙向鏈表節點

    struct epoll_filefd  ffd;  //事件句柄信息

    struct eventpoll *ep;    //指向其所屬的eventpoll對象

    struct epoll_event event; //期待發生的事件類型

}

 

雙鏈表與存儲着所有監聽的紅黑樹裏都是存放的epitem結構數據

 

2.epoll_ctl

將epoll_event結構拷貝到內核空間中並且判斷加入的fd是否支持poll結構,並且從epfd->file->privatedata獲取event_poll對象,根據op區分是添加刪除還是修改,

對於修改刪除是先在紅黑樹查找是否有相對應的fd,沒找到就返回錯誤

對於插入,先創建一個與fd對應的epitem結構,並且初始化相關成員,然後指定了調用poll_wait時的回調函數用於數據就緒時喚醒進程,(其內部,初始化設備的等待隊列,將該進程註冊到等待隊列)完成這一步, 我們的epitem就跟這個socket關聯起來了, 當它有狀態變化時,會通過ep_poll_callback()來通知.最後調用加入的fd的file operation->poll函數(最後會調用poll_wait操作)用於完成註冊操作.

最後將epitem結構添加到紅黑樹中

3.epoll_wait

當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不爲空,則把發生的事件複製到用戶態,同時將事件數量返回給用戶

三.ET和LT的區別

首先從epoll的監聽流程看起,當有文件描述符的狀態改變時,會導致fd上的回調函數被調用,回調函數會將fd對應的epitem添加到rdlist,此時epoll_wait會檢測到rdlist不爲空,進程被喚醒,結束等待後會將rdlist中的epitem複製到txlist中,然後清空rdlist,epoll_wait繼續阻塞等待,接着掃描txlist中的每個epitem,調用相關fd,然後有調用poll取得新的epoll_event,之後將取得的epoll_event發送到用戶空間,用戶空間會對這個事件進行處理,如果fd是LT模式監聽會將其重新加入到rdlist,如果是ET就不在加入到rdlist

所以ET和LT的區別就在ET中的事件只添加到rdlist一次,處理後就刪除了,所有當有數據讀或寫時,要一次讀完或一次寫完。LT是可以把事件多次添加到rdlist中,只有你主動的刪除它。

 

參考文章:https://www.nowcoder.com/discuss/26226

發佈了36 篇原創文章 · 獲贊 15 · 訪問量 5940
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章