1. 程式人生 > >LINUX核心研究----IO複用函式epoll核心原始碼深度剖析

LINUX核心研究----IO複用函式epoll核心原始碼深度剖析

select和poll的效率瓶頸有兩個

    1、每次呼叫這些函式的時候都需要將監控的fd和需要監控的事件從使用者空間拷貝到核心空間,非常影響效率。

而epoll就是自己儲存使用者空間拷入的fd和需要監控的事件,只需在呼叫epoll_ctl的時候就把所有的fd和需要監控的事件只進行一次從使用者空間到核心空間的拷貝。

    2、select和poll在核心中都是採用線性輪詢的方式檢查整個陣列(poll是連結串列)裡的活躍fd,對於許多沒有資料的fd來說這浪費了不必要的時間。

如果我們不再檢查活躍的fd,而是活躍的fd自動呼叫一個回撥函式,把自己掛到就緒佇列裡。那不是簡單的多麼?

EPOLL是通過回撥函式自動把就緒的檔案描述符放入到一個就緒連結串列中而不需要遍歷檔案描述符。通過epoll_wait()函式將就緒的檔案描述符返回給使用者。

EPOLL應用廣泛的情景

連結的人數非常多,活躍的人數相對來說少CPU利用率提升非常明顯,這是epoll的優勢所在。

多連結多活躍的情況下效率的提升並不明顯,三種多路IO轉接都差別不大。如果活躍量很大的話回撥函式反覆呼叫反而影響效率。

這個時候設定的最大開啟檔案描述符可以通過更改到硬體水平,軟體水平一般都是65535 epoll需要突破系統資源設定65535,在配置文r件里加入硬體限制和軟體限制設定。

$ulimit –n 90000

通過命令

$cat /pro/sys/fs/fs/file-max

檢視最大的檔案開啟數量。

Epoll核心實現:

具題剖析過程比較複雜,請點選圖片擴大詳細檢視!!!以下只是簡練的概括:

核心為EPOLL

做的準備工作:

這個模組在核心初始化時(作業系統啟動)註冊了一個新的檔案系統,叫"eventpollfs"(在eventpoll_fs_type結構裡),然後掛載此檔案系統。

另外建立兩個核心cache(在核心程式設計中,如果需要頻繁分配小塊記憶體,應該建立kmem_cahe來做“記憶體池”),分別用於存放struct epitem和eppoll_entry。

這個核心高速cache區,就是建立連續的實體記憶體頁,就是物理上分配好你想要的size的記憶體物件,每次使用時都是使用空閒的已分配好的記憶體。

現在想想epoll_create為什麼會返回一個新的fd?

因為它就是在這個叫做"eventpollfs"的檔案系統裡建立了一個新檔案!返回的就是這個檔案的fd索引。很好地遵行了Linux一切皆檔案的特色。

int epoll_create(int size);

epoll_ create時,核心除了幫我們在epoll檔案系統裡建了新的檔案結點,將該節點返回給使用者。並在核心cache裡建立一個紅黑樹用於儲存以後epoll_ctl傳來的需要監聽檔案fd外,這些fd會以紅黑樹節點的形式儲存在核心cache裡,以便支援快速的查詢、插入、刪除操作。

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

核心還會再建立一個list連結串列,用於儲存事件就緒的fd。核心將就緒事件會拷貝到傳入引數的events中的使用者空間。就緒佇列的事件陣列events需要自己建立,並作為引數傳入這樣才可以在epoll_wait返回時接收需要處理的描述符集合。

當epoll_wait呼叫時,僅僅觀察這個list連結串列裡有沒有資料即可。

有資料就返回,沒有資料就sleep,等到timeout時間到後即使連結串列沒資料也返回。

而且,通常情況下即使我們要監控百萬計的fd,大多一次也只返回很少量的準備就緒fd而已因為在基數大,活躍量少的情況下應用優勢明顯,如果活躍量很大的話回撥函式反覆呼叫影響效率。所以,epoll_wait僅需要從核心態copy少量的fd到使用者態. 所以,epoll_wait非常高效。

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

那麼準備就緒list連結串列是怎麼維護的呢?當我們執行epoll_ctl時,除了把socketf放到epoll對應的紅黑樹上之外,還會給核心中斷處理程式註冊一個回撥函式,告訴核心,如果這個socketf的中斷了也就是有資料要處理時呼叫回撥函式,這個回撥函式也就把fd放到準備就緒list連結串列裡。所以,當一個fd(例如socket)上有資料到了,核心在把裝置(例如網絡卡)上的資料copy到核心中後就來把fd插入到準備就緒list連結串列裡了。

如此,一顆紅黑樹,一張準備就緒fd連結串列,少量的核心cache,就幫我們解決了大併發下的fd(socket)處理問題。

1.執行epoll_create時,建立了紅黑樹

2.執行epoll_ctl時,建立就緒list連結串列,如果增加fd新增到紅黑樹上,然後向核心註冊有事件到來時的回撥函式,當裝置上的中斷事件來臨時這個回撥函式會向list連結串列中插入就緒的fd。

3.執行epoll_wait時立刻返回準備就緒連結串列裡的資料即可

epoll的優點:

1)支援一個程序開啟大數目的socket描述符(FD)

select最不能忍受的是一個程序所開啟的FD是有一定限制的,由FD_SETSIZE設定,預設值是1024。對於那些需要支援的上萬連線數目的IM伺服器來說顯然太少了。這時候你一是可以選擇修改這個巨集然後重新編譯核心。

不過 epoll則沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於1024,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

2)IO效率不隨FD數目增加而線性下降

傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網路延時,任一時間只有部分的socket是”活躍”的,但是select/poll每次呼叫都會線性輪詢掃描全部的fd集合,導致效率呈現線性下降。

epoll不存在這個問題,它只會對”活躍”的socket進行操作—這是因為在核心實現中epoll是根據每個fd上面的回撥函式實現的。那麼,只有”活躍”的socket才會主動的去呼叫callback函式,其他idle狀態socket則不會,在這點上,epoll實現了一個”偽”AIO,因為這時候推動力在Linux核心。

3)兩種觸發模式,ET模式減少epoll_wait()的呼叫次數

EPOLL ET 邊沿觸發:只觸發一次,無論緩衝區中是否還有剩餘資料,直到有新的資料到達才會被觸發,再去讀取緩衝區裡面的資料。

EPOLL LT 水平觸發(預設): LT(level triggered)是預設的工作方式,並且同時支援block和no-block socket,每次緩衝區都有資料都要觸發。

Epoll可以監控管道檔案,任意檔案,不僅僅是socket檔案

邊沿觸發的應用場景之一

客戶端給我寫的資料帶有自己設計的協議頭

而我只需要讀取客戶端資料的協議頭,判斷是否需要繼續往下讀取

如果不需要則不繼續讀取剩下的資料,增加程式執行的效率。

epoll工作在ET模式的時候,必須使用非阻塞套檔案讀寫,以避免由於一個檔案控制代碼的阻塞讀/阻塞寫操作容易阻塞在read函式時,因為沒有讀取到需要的位元組數,而伺服器又不能脫離read的阻塞狀態去呼叫epoll函式接收客戶端的資料造成死鎖。

解決方法

1、非阻塞讀取用fcntl 修改檔案描述符的非阻塞讀屬性。

2、在open時指定非阻塞開啟屬性

Epoll-ET的非阻塞IO模型::read非阻塞輪詢+邊沿觸發

去讀取檔案描述符的資料,直到為0,最效率的方法。

減少epoll_wait的呼叫次數提高程式的效率。

支援ET模式的原理,具體看最後面的核心的分析!!!!

當一個fd上有事件發生時,核心會把該fd插入上面所說的準備就緒rdlist連結串列,這時我們呼叫epoll_wait,會把準備就緒的fd拷貝到使用者態記憶體,然後清空準備就緒list連結串列。

  最後,epoll_wait檢查這些fd,如果不是ET模式(就是LT模式的控制代碼了),並且這些socket上確實有未處理的事件時,又把該控制代碼放回到剛剛清空的準備就緒rdlist連結串列了。

所以,非ET的控制代碼,只要它上面還有事件,epoll_ wait每次都會返回。而ET模式的控制代碼,除非有新中斷到,即使fd上的事件沒有處理完,也是不會次次從epoll_wait返回的

4能處理EPOLLONESHOT事件

當我們使用ET模式,一個socket上的某個事件還是可能被觸發多次,這在併發程式中就會引起一個問題。

比如線上程池或者程序池模型中,某一個執行緒或程序正在處理一個有事件的socket檔案描述符時,在這個時候這個socket檔案描述符又有新的資料可以讀取,此時另外一個執行緒被喚醒來讀取這些新的資料,於是就出現了連個執行緒或者程序同時操作一個檔案描述符的情況,這個時候就容易發生錯誤因為某個執行緒排程的時間和順序是不能確定的,很有可能一個執行緒或者程序已經把資料讀取完成,而且另外的一個程序或者執行緒還在讀取這個檔案描述符。

    這是我們所不希望的,我們希望在任意時刻都只有一個執行緒或者程序在操作一個檔案描述符,關於這一點要求epoll提供了一個叫做EPOLLONESHOT事件的實現。

對於註冊了EPOLLONESHOT事件的檔案描述符,作業系統最多觸發其註冊的一個可讀可寫或者異常事件,且只觸發一次。除非我們使用epoll_ctl()函式重置該檔案描述符上註冊的EPOLLONESHOT事件。

反過來思考的話,註冊EPOLLONESHOT事件的檔案描述符一旦被某個執行緒或者程序處理完成後,該執行緒或程序就應該立即重置這個sock檔案描述符上的EPOLLONESHOT事件,以確保這個sockfd檔案描述符下一次可讀時,其EPOLLIN事件能被觸發,進而讓其他工作執行緒有機會繼續處理這個sockfd檔案描述符.。

圖片中是剖析核心中整個EPOLL原始碼的過程,強烈推介的技術乾貨,剖析完就覺得自己的程式設計能力實在是渣的不行,也會學習和收穫到前輩的璀璨思想,不可言傳只可意會。


相關推薦

LINUX核心研究----IO函式epoll核心原始碼深度剖析

select和poll的效率瓶頸有兩個    1、每次呼叫這些函式的時候都需要將監控的fd和需要監控的事件從使用者空間拷貝到核心空間,非常影響效率。而epoll就是自己儲存使用者空間拷入的fd和需要監控

Linux----網路程式設計(IOepoll系統呼叫函式

伺服器端epoll.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <unistd.h&

linux IOepoll

linux IO複用之epoll 這篇文章是我檢視網上各種文章來總結的,為自己學習來做個筆記!!! 大多數來源於:https://www.cnblogs.com/lojunren/p/3856290.html 首先,什麼事是epoll? epoll是Linu

Linux關於IOepoll模型)

在這篇開始之前,可以檢視前一篇對poll的概念的描述,這樣閱讀起這篇比較不困難。首先我們要知道,epoll模型和前面poll,select是有差別的,他實現的方法不大一樣,我們來看看下面的程式碼,為了和之前的poll,select進行區別,我們依舊採用C/S架構實現。伺服器程

linux下I/Oepoll實際使用(二)

上一節《linux下I/O複用與epoll實際使用(一)》主要講解了epoll的原理,這一節結合socket的程式設計,詳解select與epoll程式設計示例。 一、socket程式設計 在TCP/IP協議中“IP地址+TCP或者UDP埠號”唯一標識網路通訊中

IOepoll

  在前面的文章中講了實現IO複用的兩種方式:select和poll。今天主要講一個更為高效的函式epoll。 epoll   epoll能顯著提高在大量連結中,只有少量活躍連線時的cpu利用率。因為,首先epoll可以複用監聽的檔案描述符集合,而不用每

Linux程式設計】IO之poll詳解

poll系統呼叫 poll系統呼叫和select非常相似,關於select的詳解可以在本部落格中找到,poll也是在一定時間內輪詢監聽使用者感興趣的檔案描述符上的可讀、可寫和異常事件。 poll系統呼叫原型 #include<poll.h&g

深入理解Linux的I/Oepoll機制

0.概述 通過本篇文章將瞭解到以下內容: I/O複用的定義和產生背景 Linux系統的I/O複用工具演進 epoll設計的基本構成 epoll高效能的底層實現 epoll的ET模式和LT模式 epoll相關的一道有意思的面試題 1.複用技術和I/O複用 複用的概念 複用技術(multiplexing)

Linux網路程式設計——I/O函式epoll

https://blog.csdn.net/lianghe_work/article/details/46544567一、epoll概述epoll 是在 2.6 核心中提出的,是之前的 select() 和 poll() 的增強版本。相對於 select() 和 poll()

Linux----網路程式設計(IO中select,poll,epoll之間的區別)

前面學習了select、poll和epoll三組IO複用系統呼叫,現在從向核心傳遞檔案描述符數、核心實現、檢索就緒描述符方式、工作模式和時間複雜度等五個方面比較其中的區別,以明確在實際應用中應該選擇使用哪個。 由於select與poll的特性相似,所以把它們聯絡在一起與ep

linux網路程式設計之select函式實現io(基於TCP)引發的思考

1、基本概念    IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。IO多路複用適用如下場合:   (1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。   (2)當一個客戶同時處理多個套介面時,而這種情況

Linux IO多路epoll網路程式設計,高併發的使用例子 (含原始碼)

#include <unistd.h> #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definiti

IO 多路epoll(高效併發伺服器)

  epoll 是在 2.6 核心中提出的,是之前的select和 poll的增強版本。相對於 select和 poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個檔案描述符管理多個描述符,將使用者關係的檔案描述符的事件存放到核心的一個事件表中,這樣在使用者空間和核心空間

一次讀懂 Select、Poll、Epoll IO技術

“ 閱讀本文大概需要 6 分鐘。” 我們之前採用的多程序方式實現的伺服器端,一次建立多個工作子程序來給客戶端提供服務。其實這種方式是存在問題的。 可以打個比方:如果我們先前建立的幾個程序承載不了目前快速發展的業務的話,是不是還得增加程序數?我們都知道系統建立程序是需

Linux關於IO(poll模型)

POLL函式概念 Poll函式和select類似,但它是用檔案描述符而不是條件的型別來組織資訊的. 也就是說,一個檔案描述符的可能事件都儲存在struct pollfd中.與之相反,select用事件的型別來組織資訊,而且讀,寫和錯誤情況都有獨立的描述符掩碼.poll函式是

嵌入式Linux網路程式設計,I/O多路epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表

文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h

18.Linux下的I/Oepoll詳解

為什麼引出epoll? 1.select的缺點 1.select所用到的FD_SET是有限的 /linux/posix_types.h: #define __FD_SETSIZE 102

epollIO多路epoll總結

1、基本知識   epoll是在2.6核心中提出的,是之前的select和poll的增強版本。相對於select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個檔案描述符管理多個描述符,將使用者關係的檔案描述符的事件存放到核心的一個事件表中,這樣在使用

IO多路epoll反應堆

想寫一個關於反應堆的技術部落格。 先佔個坑吧。 epoll反應堆: /* * epoll 基於非阻塞I/O事件驅動 */ #include <stdio.h> #include <sys/socket.h> #include <s

Linux關於IO(select使用)

I/O複用網路應用場合 當客戶處理多個描述字 一個客戶同時處理多個套介面 如果一個tcp伺服器既要處理監聽套介面,又要處理連線套介面 如果一個伺服器既要處理TCP,又要處理UDP select函式作用 這個函式允許程序指示核心等待多個事件中的任一個發生,並僅在一個或多個事件發生或經過某指