epoll實現原理的理解


  Epoll是Linux IO的多路復用的機制,是select/poll的增強版本,在Linux內核fs/eventpoll.c中可以查看epoll的具體的實現。

一、epoll數據結構

  學習任何組件,首先得知道它有什么數據結構或者數據類型,epoll主要有兩個結構體:eventpoll和epitem。epitem是每一個IO對應的事件,比如EPOLL_CTL_ADD操作時,就需要創建一個epitem;eventpoll是每一個epoll所對應的,比如epoll_create就是創建一個eventpoll。

struct epitem {
    union {
        /* RB tree node links this structure to the eventpoll RB tree */
        struct rb_node rbn;
        /* Used to free the struct epitem */
        struct rcu_head rcu;
    };

    /* List header used to link this structure to the eventpoll ready list */
    struct list_head rdllink;

    /*
     * Works together "struct eventpoll"->ovflist in keeping the
     * single linked chain of items.
     */
    struct epitem *next;

    /* The file descriptor information this item refers to */
    struct epoll_filefd ffd;//sockfd

    /* List containing poll wait queues */
    struct eppoll_entry *pwqlist;

    /* The "container" of this item */
    struct eventpoll *ep;

    /* List header used to link this item to the "struct file" items list */
    struct hlist_node fllink;

    /* wakeup_source used when EPOLLWAKEUP is set */
    struct wakeup_source __rcu *ws;

    /* The structure that describe the interested events and the source fd */
    struct epoll_event event;
};

struct eventpoll {

    struct mutex mtx;

    /* Wait queue used by sys_epoll_wait() */
    wait_queue_head_t wq;

    /* Wait queue used by file->poll() */
    wait_queue_head_t poll_wait;

    /* List of ready file descriptors */
    struct list_head rdllist;

    /* Lock which protects rdllist and ovflist */
    rwlock_t lock;

    /* RB tree root used to store monitored fd structs */
    struct rb_root_cached rbr;

    /*
     * This is a single linked list that chains all the "struct epitem" that
     * happened while transferring ready events to userspace w/out
     * holding ->lock.
     */
    struct epitem *ovflist;

    /* wakeup_source used when ep_scan_ready_list is running */
    struct wakeup_source *ws;

    /* The user that created the eventpoll descriptor */
    struct user_struct *user;

    struct file *file;

    /* used to optimize loop detection check */
    u64 gen;
    struct hlist_head refs;

#ifdef CONFIG_NET_RX_BUSY_POLL
    /* used to track busy poll napi_id */
    unsigned int napi_id;
#endif

#ifdef CONFIG_DEBUG_LOCK_ALLOC
    /* tracks wakeup nests for lockdep validation */
    u8 nests;
#endif
};

   數據結構如下圖所示。

   list用來存儲就緒的IO,rbtree用來存儲所有IO,方便快速查找fd,這兩種數據結構我們都從inster和remove來討論。對於list,當內核IO准備就緒時,則執行epoll_event_callback的回調函數,將epitem添加到list中;當epoll_wait激活重新運行時,將list的epitem 逐一拷貝到events中,並刪除list中被拷貝出來的epitem。

對於rbtree又該何時添加何時刪除呢?當app執行epoll_ctl(EPOLL_CTL_ADD)操作,將epitem添加到rbtree中;當app執行epoll_ctl(EPOLL_CTL_DEL)操作,將對應的epitem從rbtree中刪除。那么list和rbtree又如何做到線程安全呢?

二、epoll鎖的機制

  list使用最小粒度的鎖spinlock,便於在SMP下添加操作的時候,能夠快速操作list。避免SMP體系下,多核競爭,此處采用自旋鎖,不適合采用睡眠鎖;添加操作如下。

    (1)獲取spinlock

    (2)epitem的rdy置為1,代表epitem已在就緒隊列中

    (3)添加到list

    (4)將eventpoll的rdnum加1

    (5)釋放spinlock

  刪除則與添加類似。

  對於rbtree的操作使用互斥鎖,過程如下:

    (1)獲取互斥鎖

    (2)查找sockid的epitem是否存在,不存在可以添加

    (3)分配epitem

    (4)sockid賦值

    (5)設置event添加到epitem的event域

    (6)將epitem添加到rbtree

    (7)釋放互斥鎖

  這里的互斥鎖,鎖的是整顆樹,而不是節點;刪除則與之類似操作。

三、epoll回調

  首先要知道回調函數何時執行,此部分需要與tcp的協議棧聯系起來理解。

  (1)三次握手完成時,把fd加入到就緒隊列,把event置為EPOLLIN可讀,此時標識可以進入到accept讀取socket數據;

  (2)recvbuffer有數據的時候(可讀數據),找到對應的fd加入到就緒隊列,把event置EPOLLIN為可讀;

    (3)sendbuffer有空隙的時候(可發數據),找到對應的fd加入到就緒隊列,把event置EPOLLOUT為可寫;

    (4)接收到fin的時候(斷開連接),找到對應的fd加入到就緒隊列,把event置EPOLLIN為可讀;

 

四、LT與ET

   (1)LT是水平出發,有數據就一直觸發,只要recvBuffer里面有數據就一直觸發,直到數據讀取完,適用於大塊;

  (2)ET是邊沿觸發,從沒有數據到有數據,才觸發,只觸發一次,即使沒讀完數據,也不會再觸發去讀取剩余的數據,剩余的數據等待下一次觸發再讀,適用於小塊;

 

思考:

  就緒集合為啥使用隊列而不適用棧?

    首先就緒集合的數據本身就需要遍歷所有,肯定使用鏈式的數據結構,如果使用棧,就會存在就緒節點一次那不完的情況,導致上一次沒被取出的節點,在下一次epoll_wait再拿的時候也可能拿不到,導致出現一些就緒節點永遠都不被處理。

   epoll的誤區:
  (1)epoll性能高,里面有內存映射,mmap
  (2)epoll比select/poll要高,在fd很少時select/poll比epoll更好

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM