IO多路複用之epoll

IO多路複用之epoll總結

基本概念

相對於之前介紹的select和poll, epoll更加的靈活而且沒有描述符數量的限制。epoll使用一個文件描述符管理多個描述符,將用戶關心的文件描述符以及事件存放到內核的一個事件表中,這樣在用戶空間和內核空間只需要copy一次。

epoll接口

int epoll_create(int size);
int epoll_ctl(int epollfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  1. epoll_create

    創建一個epoll句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不同於select的第一個參數,select的第一個參數是最大的fd+1的值。epoll_create創建好之後也會佔用一個fd,因此我們在使用完epoll之後需要調用close()來關閉句柄,否則的話會導致fd被耗盡。

  2. epoll_ctl

    epoll的事件註冊函數,它有別於select。select是在監聽事件的時候告訴內核要監聽什麼類型的事件,而epoll是在這裏註冊監聽事件,當關心的事件類型發生的話,內核會調用該事件註冊的callback函數。第一個參數是epoll_create的返回值,第二個參數表示工作,有如下幾種:

    • EPOLL_CTL_ADD: 註冊新的fd到epollfd中
    • EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件
    • EPOLL_CTL_DEL:從epollfd中刪除一個監聽的fd

    第三個參數是需要監聽的fd,第四個參數是告訴內核需要監聽什麼類型的事件,struct epoll_evet如下:

    struct epoll_event {
        __uint32_t events;  /*Epoll events*/
        epoll_data data; /*User data variable*/
    }
    
    struct epoll_data {
        void *ptr;
        int fd;
        __uint32_t u32;
        __uint64_t u64;
    }

    events可以是如下幾種類型的宏的集合:

    • EPOLLIN:表示對應的文件描述符可讀
    • EPOLLOUT:表示對應的文件描述符可寫
    • EPOLLPRI:表示對應的文件描述符有緊急的數據可讀
    • EPOLLERR: 表示對應的文件描述符發生錯誤
    • EPOLLHUP:表示對應的文件描述符被掛斷
    • EPOLLET:將EPOLL設置爲邊緣觸發,默認epoll是LT觸發的
    • EPOLLONESHOT:只監聽一次事件,如果還需要監聽這個socket的話那麼需要再次將這個socket加入到EPOLL隊列裏
  3. epoll_wait

    參數event用來從內核得到事件的集合,maxevents告訴內核這個events有多大,不能超過epoll_create的時候size。timeout是超時時間(0表示立即返回)。函數的返回結果是需要處理的事件數目。

epoll的工作模式

上面我們提到一個LT模式和ET模式。這是epoll工作的兩種模式,LT模式是epoll的默認模式。

  • LT模式:當epoll_wait檢測到有關心的事件發生並通知應用程序,應用程序可以不立即處理該事件,下次調用epoll_wait的時候epoll會再次通知此事件。還有一種情況即如果應用程序在處理該事件的時候沒有將所有的數據讀取完,那麼之後調用epoll_wait的時候仍然會通知應用程序緩衝中有數據
  • ET模式:當epoll_wait檢測到關心的事件發生並通知給應用程序,應用程序必須立即處理該事件,因爲下一次調用epoll_wait不會再通知此事件了。

ET模式再很大程序減少了epoll事件被重複觸發的次數,效率比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞接口,避免由於一個句柄的阻塞讀/寫把其他多個文件描述符的任務餓死。

epoll的工作原理

epoll比select/poll的優越之處就在於:後者每次調用的時候都要傳遞你所有監控的所有的socket給select/poll系統調用,這就需要將用戶態的socket列表copy到內核態,如果句柄過多的話會非常低效。而我們調用epoll_wait的時候是不需要傳遞socket句柄給內核的,因爲在epoll_ctl的時候已經拿到了句柄列表。

那麼epoll是如何實現監聽的呢?

我們在調用epoll_create的時候內核就已經開始分配必要的空間,創建必要的數據結構來爲我們保存監控的句柄了。每次我們調用epoll_ctl的時候也只是往內核的數據結構中塞入新的socket句柄。

在內核裏,一些皆文件。epoll向內核註冊一個文件系統,用於存儲上述被監控的socket。當調用epoll_create時候,就會在這個虛擬的epoll文件系統裏創建一個file節點。當然這個file不是普通文件,只是服務於epoll。

epoll在被內核初始化的時候,會申請開闢自己的內核高速cache區,用於安置每一個我們想監控的socket,這些socket會以紅黑樹的形式保存在內核的cache區,以支持快速的查找、插入、刪除。這個內核高速cache區,就是建立連續的物理內存頁。

epoll的優勢即當我們調用很多句柄的時候,epoll_wait仍然具有很高的效率,並將有效的發生事件的句柄給我們,由於我們在調用epoll_create時候,除了幫我們在epoll文件系統建立一個file結點,在內核cache裏建立一個紅黑樹用於存儲epoll_ctl傳來的socket外,還會再建立一個list鏈表,用戶存儲準備就緒的事件。當epoll_wait調用的時候,僅僅只需要觀察這個list鏈表裏有沒有數據即可。而且當有事件發生的時候,epoll_wait也只是從內核態copu少量的句柄到用戶態而已。

那麼這個準備就緒list鏈表是怎麼維護的呢?當我們執行epoll_ctl的時候,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上之外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,如果這個句柄的終端到了,就把它放到準備就緒的list鏈表裏。

如此,一棵紅黑樹,一張準備就緒句柄鏈表,少量的內存cache就解決了socket大併發下的問題。執行epoll_create時,創建了紅黑樹和就緒鏈表,執行epoll_ctl時。如果增加socket則檢查紅黑樹中是否存在,存在就立即返回,不存在則添加到樹幹上,然後向內核註冊回調函數,當中斷事件來臨時向準備就緒列表中插入數據。執行epoll_wait時立刻返回準備就緒鏈表裏的數據即可。

上面說的LT模式中,如果上一次的事件沒有處理完成那麼下一次調用epoll_wait的時候仍然會觸發事件。那麼這個是怎麼做到的呢? 我們在調用epoll_wait的時候會將準備就緒的socket拷貝到用戶態,然後清空準備就緒list鏈表,而如果不是在ET模式的話,並且這些socket上確實有未處理的事件的話,epoll_wait會把這些句柄放回到剛剛清空的準備就緒鏈表中。這就實現了LT模式下只要有事件epoll_wait每次就會返回的功能。

參考鏈接:
http://www.cnblogs.com/Anker/p/3263780.html
http://blog.csdn.net/hdutigerkin/article/details/7517390

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章