epoll使用與原理

by_mzy發表於2024-06-12

使用要點

邊緣模式(ET)與水平模式(LT)區別

下面內容來自linux man page

The  epoll event distribution interface is able to behave both as edge-triggered (ET) and as level-triggered (LT).  The difference between he two mechanisms can be described as follows.  Suppose that this scenario happens:
       1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.
       2. A pipe writer writes 2 kB of data on the write side of the pipe.
       3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.
       4. The pipe reader reads 1 kB of data from rfd.
       5. A call to epoll_wait(2) is done.

       If the rfd file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to  epoll_wait(2)  done in step 5 will probably hang despite the available data still present in the file input buffer; meanwhile the remote peer might be expecting a response based on the data it already sent.  The reason for this is that edge-triggered mode delivers events only when changes occur on  the  monitored  file descriptor.  So, in step 5 the caller might end up waiting for some data that is already present inside the input buffer.  In the above example, an event on rfd will be generated because of the write done in 2 and the event is consumed in 3.  Since theread operation done in 4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.

簡單來說,如果使用ET模式,對於同一個事件,核心只會上報一次。如果程式碼沒有讀取完所有可讀取的資料,然後繼續呼叫epoll_wait那麼是不會收到事件的。ET模式一般要設定成setnonblocking模式,並且等到讀取資料返回EAGAIN或者

如果使用LT模式,則呼叫epoll_wait後會繼續有事件觸發。LT模式與poll保持一樣的語義,區別是比poll的效能更好一些。

讀取資料要點

  • 對於流式連線,例如pipe、fifo、tcp等,透過判定read的返回值比指定讀取的小,來判斷緩衝區中的資料已經讀完了
  • 對於資料包,例如udp等,則通常需要需要持續讀取到EAGAIN返回,才能判斷緩衝區資料已經讀完了
  • 每次從epoll_wait返回後都需要呼叫epoll_ctl往epoll中繼續增加事件

驚群問題解決

  • EPOLLEXCLUSIVE 當多個fd指向同一個檔案,並且都被新增到epoll中,那麼就可能產生驚群問題:一旦這個檔案有被寫入,那麼read事件會觸發給多個fd。加了這個標誌後,保證至少一個能收到事件。

ET模式下的飢餓現象

某一個檔案,可能來的資料非常多,持續的多,那麼這個檔案會不停的進行read(按照我們前面說的返回EAGAN或者返回小於指定大小的思路)。由於整個執行時單執行緒的,那麼其他fd就會一直等待,造成了所謂的飢餓現象

epoll的飢餓模式如何避免?linux man page中給出的答案時,不直接在epoll_wait返回後直接讀取資料,而是將可讀的fd放到一個list裡面。然後再對這個list中的fd分別執行read,可以透過限制每個fd一次read的位元組數來控制切換到其他fd,當一個fd 讀完後從list刪除掉。

處理使用 epoll ET 模式下檔案描述符出現飢餓的情況_epoll wait防飢餓-CSDN部落格

控制代碼數限制

/proc/sys/fs/epoll/max_user_watches 中記錄系統範圍內,限制加入到epoll中的控制代碼數,實際上是限制控制代碼相關的記憶體(64位下160位元組,32位下90位元組)最多隻能佔用記憶體的4%。

核心實現原理

epoll_wait是如何實現的

  • 使用者呼叫epoll_wait,會透過syscall指令執行系統呼叫
  • 系統呼叫統一入口根據系統呼叫號走到核心對應的sys_epoll_wait處理函式(核心用call指令 執行)
  • 然後回判斷epoolfd裡面關聯的檔案控制代碼是否有可讀的控制代碼
  • 如果暫時沒有那麼epoll_wait執行緒就會等待在這些控制代碼的queue上
  • 一旦這些檔案控制代碼(例如socket),有資料進來後,核心的ksoftirqd執行緒會嘗試喚醒執行緒,並從上次執行緒切換的位置寵幸執行sys_epoll_wait

注意:

  • epoll_ctl會將epollfd加入到io控制代碼的等待佇列中

參考資料

  • 深入學習Linux_邋遢的流浪劍客的部落格-CSDN部落格
  • linux 原始碼角度看 epoll - JackTang's Blog (jacktang816.github.io)
  • Linux篇-select/poll/epoll原始碼分析 - 麥奇 (mikeygithub.github.io)
  • 深入理解 epoll 原理 - 愛笑的張飛 - 部落格園 (cnblogs.com)
  • Linux man page 7
  • Linux IO模式及 select、poll、epoll詳解 - 人云思雲 - SegmentFault 思否

相關文章