EPOLL的理解和深入分析(轉)

原文鏈接: https://blog.csdn.net/apacat/article/details/51375950

搞Linux 服務器開發的人肯定了解 select、poll、epoll,他們都是基于事件驅動的IO多路復用技術,而他們之間的區別網上已經有很多的文章了,大家可以去詳細的閱讀,我在這里主要想寫寫我對epoll的底層實現的理解。

首先還是先說說 select、poll相比與epoll來說他們效率低下的原因吧:

select、poll、epoll是Linux平臺下的IO多路復用技術,適合用來管理大量的文件描述符,但是這些系統調用本身是阻塞的,而他們管理的socket描述符其實是可以阻塞,也可以非阻塞的,但是大部分情況下設置為非阻塞的要更好一些,效率會更高一些。因此,他們并不是真正的異步IO。是偽異步的。

1、select

首先,select的缺點1:是select管理的描述符的數量在不重新編譯內核的情況下是一個固定的值:1024,當然,重新編譯了Linux內核之后,這個數值可以繼續增大到用戶的需求,但是這是相對來說比較麻煩的一件事。

其次。select的缺點2:是select對于socket描述符的管理方式,因為Linux內核對select的實現方式為每次返回前都要對所有的描述符進行一遍遍歷,然后將有事件發生的socket描述符放到描述符集合里,然后將這個描述符集合返回。這種情況對于描述符的數量不是很大的時候還是可以的,但是當描述符達到數十萬,甚至上百萬的時候,select的效率就會急劇的降低,因為這樣的輪詢機制會造成大量的浪費和資源開銷。因為每一次的輪詢都要將這些所有的socket描述符從用戶態拷貝到內核態,在內核態,進行輪詢,查看是否有事件發生,這是select的底層需要做的。而這些拷貝完全是可以避免的。

2、poll

poll的實現機制和select是一樣的,也是采用輪詢機制來查看有事件發生的socket描述符,所以效率也是很低,但是poll對select有一項改進就是能夠監視的描述符是任意大小的而不是局限在一個較小的數值上(當然這個描述符的大小也是需要操作系統來支持的)。

綜上:在總結一下,select與poll的實現機制基本是一樣的,只不過函數不同,參數不同,但是基本流程是相同的;

1、復制用戶數據到內核空間

2、估計超時時間

3、遍歷每個文件并調用f_op->poll()取得文件狀態

4、遍歷完成檢查狀態

如果有就緒的文件(描述符對應的還是文件,這里就當成是描述符就可以)則跳轉到5,

如果有信號產生則重新啟動poll或者select

否則掛起進程并等待超時或喚醒超時或再次遍歷每個文件的狀態

5、將所有文件的就緒狀態復制到用戶空間

6、清理申請的資源

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

3、epoll

epoll改進了select的兩個缺點,使用了三個數據結構從而能夠在管理大量的描述符的情況下,對系統資源的使用并沒有急劇的增加,而只是對內存的使用有所增加(畢竟存儲大量的描述符的數據結構會占用大量內存)。

epoll在實現上的三個核心點是:1、mmap,2、紅黑樹,3、rdlist(就緒描述符鏈表)接下來一一解釋這三個并且解釋為什么會高效;

1、mmap是共享內存,用戶進程和內核有一段地址(虛擬存儲器地址)映射到了同一塊物理地址上,這樣當內核要對描述符上的事件進行檢查的時候就不用來回的拷貝了。


三、共享內存

這點實際上涉及到epoll的具體實現了。內核/用戶空間 內存拷貝問題,如何讓內核把FD消息通知給用戶空間呢?

在這個問題上select采取了內存拷貝方法。Poll也是么?應該是,poll和select基本無區別?(肯定多少還有點區別,只是這些缺點是相同的)

既然是內存拷貝,因為也要拷貝啊,還是慢!

對于poll來說需要將用戶傳入的 pollfd 數組拷貝到內核空間,因為拷貝操作和數組長度相關,時間上這是一個O(n)操作,當事件發生,poll返回將獲得的數據傳送到用戶空間并執行釋放內存和剝離等待隊列等善后工作,向用戶空間拷貝數據與剝離等待隊列等操作的的時間復雜度同樣是O(n)。

而epoll是共享內存,拷貝都不用,相對來說應該會更快。epoll是通過內核與用戶空間mmap同一塊內存實現的。

mmap是什么

mmap操作提供了一種機制,讓用戶程序直接訪問設備內存,這種機制,相比較在用戶空間內核空間互相拷貝數據,效率更高。在要求高性能的應用中比較常用。mmap映射內存必須是頁面大小的整數倍,面向流的設備不能進行mmap,mmap的實現和硬件有關。

(涉及到內核和用戶空間的概念,共享內存有沒有安全隱患?)


2、紅黑樹是用來存儲這些描述符的,因為紅黑樹的特性,就是良好的插入,查找,刪除性能O(lgN)。

 當內核初始化epoll的時候(當調用epoll_create的時候內核也是個epoll描述符創建了一個文件,畢竟在Linux中一切都是文件,而epoll面對的是一個特殊的文件,和普通文件不同),會開辟出一塊內核高速cache區,這塊區域用來存儲我們要監管的所有的socket描述符,當然在這里面存儲一定有一個數據結構,這就是紅黑樹,由于紅黑樹的接近平衡的查找,插入,刪除能力,在這里顯著的提高了對描述符的管理。

3、rdlist 就緒描述符鏈表這是一個雙鏈表,epoll_wait()函數返回的也是這個就緒鏈表。

 當內核創建了紅黑樹之后,同時也會建立一個雙向鏈表rdlist,用于存儲準備就緒的描述符,當調用epoll_wait的時候在timeout時間內,只是簡單的去管理這個rdlist中是否有數據,如果沒有則睡眠至超時,如果有數據則立即返回并將鏈表中的數據賦值到events數組中。這樣就能夠高效的管理就緒的描述符,而不用去輪詢所有的描述符。所以當管理的描述符很多但是就緒的描述符數量很少的情況下如果用select來實現的話效率可想而知,很低,但是epoll的話確實是非常適合這個時候使用。

  對與rdlist的維護:當執行epoll_ctl時除了把socket描述符放入到紅黑樹中之外,還會給內核中斷處理程序注冊一個回調函數,告訴內核,當這個描述符上有事件到達(或者說中斷了)的時候就調用這個回調函數。這個回調函數的作用就是將描述符放入到rdlist中,所以當一個socket上的數據到達的時候內核就會把網卡上的數據復制到內核,然后把socket描述符插入就緒鏈表rdlist中。

補充:epoll的工作模式ET和LT

都知道epoll有兩個工作模式,ET和LT,其中ET模式是高速模式,叫做邊緣觸發模式,LT模式是默認模式,叫做水平觸發模式。

這兩種工作模式的區別在于:

當工作在ET模式下,如果一個描述符上有數據到達,然后讀取這個描述符上的數據如果沒有將數據全部讀完的話,當下次epoll_wait返回的時候這個描述符里的數據就再也讀取不到了,因為這個描述符不會再次觸發返回,也就沒法去讀取,所以對于這種模式下對一個描述符的數據的正確讀取方式是用一個死循環一直讀,讀到么有數據可讀的情況下才可以認為是讀取結束。

而工作在LT模式下,這種情況就不會發生,如果對一個描述符的數據沒有讀取完成,那么下次當epoll_wait返回的時候會繼續觸發,也就可以繼續獲取到這個描述符,從而能夠接著讀。

那么這兩種模式的實現方式是什么樣的?

基于以上的數據結構是怎么實現這種工作模式的呢?

實現原理:當一個socket描述符的中斷事件發生,內核會將數據從網卡復制到內核,同時將socket描述符插入到rdlist中,此時如果調用了epoll_wait會把rdlist中的就緒的socekt描述符復制到用戶空間,然后清理掉這個rdlist中的數據,最后epoll_wait還會再次檢查這些socket描述符,如果是工作在LT模式下,并且這些socket描述符上還有數據沒有讀取完成,那么L就會再次把沒有讀完的socket描述符放入到rdlist中,所以再次調用epoll_wait的時候是會再次觸發的,而ET模式是不會這么干的。

ET模式在物理實現上是基于電平的高低變化來工作的,就是從高電平變成低電平,或者從低電平變成高電平的這個上升沿或者下降沿才會觸發,也就是狀態變化導致觸發,而當一個描述符上數據未讀完的時候這個狀態是不會發生變化的,所以觸發不了,LT模式是在只有出現高電平的時候才會觸發。

高電平和低電平:

LT水平觸發:

EPOLLIN的觸發事件:當輸入緩沖區為空-->低電平,當輸入緩沖區不為空-->高電平

高電平的時候觸發EPOLLIN事件,如果沒有把緩沖區的數據讀取完,下次還會觸發的,因為始終是高電平

EPOLLOUT的觸發事件:當發送緩沖區滿-->低電平,當發送緩沖區不滿-->高電平

高電平的時候觸發EPOLLOUT事件,所以在一開始的時候不要關注EPOLLOUT時間,因為發送緩沖區是不滿的所以會導致CPU忙等待,每次都觸發。什么時候關注EPOLLOUT事件呢? 當write的時候沒有寫完全,因為發送緩沖區滿了,這個時候才關注EPOLLOUT事件直到下次把所有數據都發送完畢了,才取消EPOLLOUT事件

ET邊緣觸發:

EPOLLIN事件發生的條件:

有數據到來(輸入緩沖區初始為空,為低電平,有數據到來變成了高電平)

EPOLLout事件發生的條件:

內核發送緩沖區不滿(當發送緩沖區出現滿之后為低電平,然后內核發送出去了部分數據后變成了不滿,也就是高電平)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容