精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

epoll的基礎數據結構

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-10 10:20 ? 次閱讀

一、epoll的基礎數據結構

在開始研究源代碼之前,我們先看一下 epoll 中使用的數據結構,分別是 eventpoll、epitem 和 eppoll_entry。

1、eventpoll

我們先看一下 eventpoll 這個數據結構,這個數據結構是我們在調用 epoll_create 之后內核創建的一個句柄,表示了一個 epoll 實例。后續如果我們再調用 epoll_ctl 和 epoll_wait 等,都是對這個 eventpoll 數據進行操作,這部分數據會被保存在 epoll_create 創建的匿名文件 file 的 private_data 字段中。

* This structure is stored inside the "private_data" member of the file
* structure and represents the main data structure for the eventpoll
* interface.
*/
struct eventpoll {
/* Protect the access to this structure */
spinlock_t lock;

/*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
struct mutex mtx;

/* Wait queue used by sys_epoll_wait() */
// 這個隊列里存放的是執行 epoll_wait 從而等待的進程隊列
wait_queue_head_t wq;

/* Wait queue used by file->poll() */
// 這個隊列里存放的是該 eventloop 作為 poll 對象的一個實例,加入到等待的隊列
// 這是因為 eventpoll 本身也是一個 file, 所以也會有 poll 操作
wait_queue_head_t poll_wait;

/* List of ready file descriptors */
// 這里存放的是事件就緒的 fd 列表,鏈表的每個元素是下面的 epitem
struct list_head rdllist;

/* RB tree root used to store monitored fd structs */
// 這是用來快速查找 fd 的紅黑樹
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;

// 這是 eventloop 對應的匿名文件,充分體現了 Linux 下一切皆文件的思想
struct file *file;

/* used to optimize loop detection check */
int visited;
struct list_head visited_list_link;

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

2、epitem

每當我們調用 epoll_ctl 增加一個 fd 時,內核就會為我們創建出一個 epitem 實例,并且把這個實例作為紅黑樹的一個子節點,增加到 eventpoll 結構體中的紅黑樹中,對應的字段是 rbr。這之后,查找每一個 fd 上是否有事件發生都是通過紅黑樹上的 epitem 來操作。

/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the "rbr" RB tree.
* Avoid increasing the size of this struct, there can be many thousands
* of these on a server and we do not want this to take another cache line.
*/
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 */
// 將這個 epitem 連接到 eventpoll 里面的 rdllist 的 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 */
//epoll 監聽的 fd
struct epoll_filefd ffd;

/* Number of active wait queue attached to poll operations */
// 一個文件可以被多個 epoll 實例所監聽,這里就記錄了當前文件被監聽的次數
int nwait;

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

/* The "container" of this item */
// 當前 epollitem 所屬的 eventpoll
struct eventpoll *ep;

/* List header used to link this item to the "struct file" items list */
struct list_head 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;
};

3、eppoll_entry

每次當一個 fd 關聯到一個 epoll 實例,就會有一個 eppoll_entry 產生。eppoll_entry 的結構如下:

/* Wait structure used by the poll hooks */
struct eppoll_entry {
/* List header used to link this structure to the "struct epitem" */
struct list_head llink;

/* The "base" pointer is set to the container "struct epitem" */
struct epitem *base;

/*
* Wait queue item that will be linked to the target file wait
* queue head.
*/
wait_queue_entry_t wait;

/* The wait queue head that linked the "wait" wait queue item */
wait_queue_head_t *whead;
};

二、epoll底層原理

在高并發場景下,如果有100萬用戶同時與一個進程保持著TCP連接,而每一時刻只有幾十個或幾百個TCP連接是活躍的(接收TCP包),也就是說在每一時刻進程只需要處理這100萬連接中的一小部分連接。

對于這種場景,select或者poll事件驅動方式采用了輪詢的方式操作系統收集有事件發生的TCP連接,把這100萬個連接告訴操作系統。但這里有一個明顯的問題,在某一時刻,進程收集有事件的連接時,其實這100萬連接中的大部分都是沒有事件發生的。因此如果每次收集事件時,都把100萬連接的套接字傳給操作系統,從用戶態內存到內核態內存的大量復制,這無疑會產生巨大的開銷。而由操作系統內核尋找這些連接上有沒有未處理的事件,將會是巨大的資源浪費,然后select和poll就是這樣做的,因此它們最多只能處理幾千個并發連接。

而epoll不這樣做,它在Linux內核中申請了一個簡易的文件系統,把原先的一個select或poll調用分成了3部分:

int epoll_create(int size);
int epoll_ctl(int epfd, 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對象(在epoll文件系統中給這個句柄分配資源);
  2. 調用epoll_ctl向epoll對象中添加這100萬個連接的套接字;
  3. 調用epoll_wait收集發生事件的連接。

這樣只需要在進程啟動時建立1個epoll對象,并在需要的時候向它添加或刪除連接就可以了,因此,在實際收集事件時,epoll_wait的效率就會非常高,因為調用epoll_wait時并沒有向它傳遞這100萬個連接,內核也不需要去遍歷全部的連接。

1、epoll_create

我們在調用epoll_create時,內核除了幫我們在epoll文件系統里建了個file結點,在內核cache里建了個紅黑樹用于存儲以后epoll_ctl傳來的socket外,還會再建立一個rdllist雙向鏈表,用于存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個rdllist雙向鏈表里有沒有數據即可。有數據就返回,沒有數據就sleep,等到timeout時間到后即使鏈表沒數據也返回。所以,epoll_wait非常高效。

紅黑樹操作使用的是互斥鎖,在添加和刪除操作時需要加鎖。

雙向鏈表操作使用的是spinlock自旋鎖,當沒有競爭到鎖資源時,不會睡眠,加快了鏈表操作的速度,添加和刪除操作需要加鎖。

總之,紅黑樹存儲所監控的文件描述符的節點數據,就緒鏈表存儲就緒的文件描述符的節點數據

圖片

epoll_create工作流程

首先,epoll_create 會對傳入的 flags 參數做簡單的驗證。

/* Check the EPOLL_* constant for consistency. */
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);

if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/*

接下來,內核申請分配 eventpoll 需要的內存空間。

/* Create the internal data structure ("struct eventpoll").
*/
error = ep_alloc(&ep);
if (error < 0)
return error;

在接下來,epoll_create 為 epoll 實例分配了匿名文件和文件描述字,其中 fd 是文件描述字,file 是一個匿名文件。這里充分體現了 UNIX 下一切都是文件的思想。注意,eventpoll 的實例會保存一份匿名文件的引用,通過調用 fd_install 函數將匿名文件和文件描述字完成了綁定。

這里還有一個特別需要注意的地方,在調用 anon_inode_get_file 的時候,epoll_create 將 eventpoll 作為匿名文件 file 的 private_data 保存了起來,這樣,在之后通過 epoll 實例的文件描述字來查找時,就可以快速地定位到 eventpoll 對象了。

最后,這個文件描述字作為 epoll 的文件句柄,被返回給 epoll_create 的調用者。

/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure and a free file descriptor.
*/
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
if (fd < 0) {
error = fd;
goto out_free_ep;
}
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto out_free_fd;
}
ep->file = file;
fd_install(fd, file);
return fd;

2、epoll_ctl

接下來,我們看一下一個套接字是如何被添加到 epoll 實例中的。這就要解析一下 epoll_ctl 函數實現了。

查找 epoll 實例

首先,epoll_ctl 函數通過 epoll 實例句柄來獲得對應的匿名文件,這一點很好理解,UNIX 下一切都是文件,epoll 的實例也是一個匿名文件。

// 獲得 epoll 實例對應的匿名文件
f = fdget(epfd);
if (!f.file)
goto error_return;

接下來,獲得添加的套接字對應的文件,這里 tf 表示的是 target file,即待處理的目標文件。

/* Get the "struct file *" for the target file */
// 獲得真正的文件,如監聽套接字、讀寫套接字
tf = fdget(fd);
if (!tf.file)
goto error_fput;

再接下來,進行了一系列的數據驗證,以保證用戶傳入的參數是合法的,比如 epfd 真的是一個 epoll 實例句柄,而不是一個普通文件描述符。

/* The target file descriptor must support poll */
// 如果不支持 poll,那么該文件描述字是無效的
error = -EPERM;
if (!tf.file->f_op->poll)
goto error_tgt_fput;
...

紅黑樹查找

接下來 epoll_ctl 通過目標文件和對應描述字,在紅黑樹中查找是否存在該套接字,這也是 epoll 為什么高效的地方。紅黑樹(RB-tree)是一種常見的數據結構,這里 eventpoll 通過紅黑樹跟蹤了當前監聽的所有文件描述字,而這棵樹的根就保存在 eventpoll 數據結構中。

對于每個被監聽的文件描述字,都有一個對應的 epitem 與之對應,epitem 作為紅黑樹中的節點就保存在紅黑樹中。

紅黑樹是一棵二叉樹,作為二叉樹上的節點,epitem 必須提供比較能力,以便可以按大小順序構建出一棵有序的二叉樹。其排序能力是依靠 epoll_filefd 結構體來完成的,epoll_filefd 可以簡單理解為需要監聽的文件描述字,它對應到二叉樹上的節點。

ep_insert

ep_insert 首先判斷當前監控的文件值是否超過了 /proc/sys/fs/epoll/max_user_watches 的預設最大值,如果超過了則直接返回錯誤。接下來是分配資源和初始化動作。

if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
return -ENOMEM;

/* Item initialization follow here ... */
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;

再接下來的事情非常重要,ep_insert 會為加入的每個文件描述字設置回調函數。這個回調函數是通過函數 ep_ptable_queue_proc 來進行設置的。這個回調函數是干什么的呢?其實,對應的文件描述字上如果有事件發生,就會調用這個函數,比如套接字緩沖區有數據了,就會回調這個函數。這個函數就是 ep_poll_callback。這里你會發現,原來內核設計也是充滿了事件回調的原理。

/*
* This is the callback that is used to add our wait queue to the
* target file wakeup lists.
*/
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq;

if (epi>nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
if (epi->event.events & EPOLLEXCLUSIVE)
add_wait_queue_exclusive(whead, &pwq->wait);
else
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
/* We have to signal that an error occurred */
epi->nwait = -1;
}
}

總而言之,當我們使用epoll_ctl()函數注冊一個socket時,內核將會做這些事情:

  1. 分配一個紅黑樹節點對象epitem
  2. 添加等待事件到socket的等待隊列中
  3. 將epitem插入到epoll對象的紅黑樹中

3、epoll_wait

epoll_wait被調用時會觀察 eventpoll->rdllist 鏈表里有沒有數據,有數據就返回,沒有數據就創建一個等待隊列項,將其添加到 eventpoll 的等待隊列上(1.1節中的wait_queue_head_t),然后把自己阻塞掉就結束。

查找 epoll 實例

epoll_wait 函數首先進行一系列的檢查,例如傳入的 maxevents 應該大于 0。和前面介紹的 epoll_ctl 一樣,通過 epoll 實例找到對應的匿名文件和描述字,并且進行檢查和驗證。

還是通過讀取 epoll 實例對應匿名文件的 private_data 得到 eventpoll 實例。


/* The maximum number of event must be greater than zero */
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
return -EINVAL;

/* Verify that the area passed by the user is writeable */
if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))
return -EFAULT;
/* Get the "struct file *" for the eventpoll file */
f = fdget(epfd);
if (!f.file)
return -EBADF;

/*
* We have to check that the file structure underneath the fd
* the user passed to us _is_ an eventpoll file.
*/
error = -EINVAL;
if (!is_file_epoll(f.file))
goto error_fput;

4、總結

  • 執行epoll_create()時,創建了紅黑樹和就緒鏈表;
  • 執行epoll_ctl()時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,用于當中斷事件來臨時向準備就緒鏈表中插入數據;
  • 執行epoll_wait()時立刻返回準備就緒鏈表里的數據即可;

三、epoll的兩種觸發模式

epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。

LT(水平觸發)模式下,只要這個文件描述符還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作;

LT比ET多了一個開關EPOLLOUT事件(系統調用消耗,上下文切換)的步驟;對于監聽的sockfd,最好使用水平觸發模式(參考nginx),邊緣觸發模式會導致高并發情況下,有的客戶端會連接不上,LT適合處理緊急事件;對于讀寫的connfd,水平觸發模式下,阻塞和非阻塞效果都一樣,不過為了防止特殊情況,還是建議設置非阻塞;LT的編程與poll/select接近,符合一直以來的習慣,不易出錯;

ET(邊緣觸發)模式下,在它檢測到有 I/O 事件時,通過 epoll_wait 調用會得到有事件通知的文件描述符,對于每一個被通知的文件描述符,如可讀,則必須將該文件描述符一直讀到空,讓 errno 返回 EAGAIN 為止,否則下次的 epoll_wait 不會返回余下的數據,會丟掉事件。如果ET模式不是非阻塞的,那這個一直讀或一直寫勢必會在最后一次阻塞。

邊沿觸發模式很大程度上降低了同一個epoll事件被重復觸發的次數,所以效率更高;對于讀寫的connfd,邊緣觸發模式下,必須使用非阻塞IO,并要一次性全部讀寫完數據。ET的編程可以做到更加簡潔,某些場景下更加高效,但另一方面容易遺漏事件,容易產生bug;

總之,ET和LT各有優缺點,需要根據業務場景選擇最合適的模式。

四、epoll的不足之處

1.定時的精度不夠,只到5ms級別,select可以到0.1ms;

2.當連接數少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好;

3.epoll_ctrl每次只能夠修改一個fd(kevent可以一次改多個,每次修改,epoll需要一個系統調用,不能 batch 操作,可能會影響性能);

4.可能會在定時到期之前返回,導致還需要下一個epoll_wait調用;

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • TCP
    TCP
    +關注

    關注

    8

    文章

    1324

    瀏覽量

    78754
  • 文件
    +關注

    關注

    1

    文章

    551

    瀏覽量

    24559
  • 數據結構
    +關注

    關注

    3

    文章

    568

    瀏覽量

    40030
  • epoll
    +關注

    關注

    0

    文章

    28

    瀏覽量

    2938
收藏 人收藏

    評論

    相關推薦

    什么是數據結構(Data Structrue)

    什么是數據結構(Data Structrue) 一 名詞術語數據:描述客觀事物的數字,字符以及一切能夠輸入到計算機中,并且能夠被計算機程序處理的符號的集合。數據元素:數據這個
    發表于 02-09 17:17

    數據結構

    1.數據結構的概念 所謂數據結構是指由某一數據對象及該對象中所有數據成員之間的關系組成的集合。成員之間的關系有很多種,最常見的是前后件關系。 2.
    發表于 03-04 14:13

    常見的數據結構

    `數據結構在實際應用中非常常見,現在各種算法基本都牽涉到數據結構,因此,掌握數據結構算是軟件工程師的必備技能。一、什么是數據結構數據結構,直
    發表于 05-10 07:58

    epoll_wait的事件返回的fd為錯誤是怎么回事?

    event數據結構中的data.fd2、在嵌入式Linux下執行返回的 fd 為 0,在Ubuntu下運行為4217881
    發表于 06-12 09:03

    數據結構教程,下載

    1. 數據結構的基本概念 2. 算法與數據結構3. C語言的數據類型及其算法描述要點4. 學習算法與數據結構的意義與方法
    發表于 05-14 17:22 ?0次下載
    <b class='flag-5'>數據結構</b>教程,下載

    GPIB命令的數據結構

    針對GPIB命令的結構,提出一種存儲GPIB命令的數據結構。根據GPIB命令的層次關系的特點,選擇數據結構中“樹”的概念來存儲GPIB命令結點;并考慮程序實現的效率問題以及管理維護
    發表于 02-10 16:20 ?70次下載

    什么是數據結構

    什么是數據結構 1、數據類型和數據結構·數據值:atomic data value: 不可再分解。如3、2、5等。nonatomicdata value: 可以再分解,其成分稱為
    發表于 08-13 13:56 ?1634次閱讀

    數據結構與算法

    全國C語言考試公共基礎知識點——數據結構與算法,該資料包含了有關數據結構與算法的全部知識點。
    發表于 03-30 14:27 ?0次下載

    數據結構

    數據結構PPT教程
    發表于 02-27 16:43 ?0次下載

    數據結構是什么_數據結構有什么用

    數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關系的數據元素的集合。通常情況下,精心選擇的數據結構可以帶來更高
    發表于 11-17 14:45 ?1.6w次閱讀
    <b class='flag-5'>數據結構</b>是什么_<b class='flag-5'>數據結構</b>有什么用

    為什么要學習數據結構數據結構的應用詳細資料概述免費下載

    本文檔的主要內容詳細介紹的是為什么要學習數據結構數據結構的應用詳細資料概述免費下載包括了:數據結構在串口通信當中的應用,數據結構在按鍵監測當中的應用
    發表于 09-11 17:15 ?13次下載
    為什么要學習<b class='flag-5'>數據結構</b>?<b class='flag-5'>數據結構</b>的應用詳細資料概述免費下載

    什么是數據結構?為什么要學習數據結構數據結構的應用實例分析

    本文檔的主要內容詳細介紹的是什么是數據結構?為什么要學習數據結構數據結構的應用實例分析包括了:數據結構在串口通信當中的應用,數據結構在按鍵
    發表于 09-26 15:45 ?14次下載
    什么是<b class='flag-5'>數據結構</b>?為什么要學習<b class='flag-5'>數據結構</b>?<b class='flag-5'>數據結構</b>的應用實例分析

    一文詳解epoll的實現原理

    本文以四個方面介紹epoll的實現原理,1.epoll數據結構;2.協議棧如何與epoll通信;3.epoll線程安全如何加鎖;4.ET與
    的頭像 發表于 08-01 13:28 ?3863次閱讀

    SystemVerilog中可以嵌套的數據結構

    SystemVerilog中除了數組、隊列和關聯數組等數據結構,這些數據結構還可以嵌套。
    的頭像 發表于 11-03 09:59 ?1465次閱讀

    NetApp的數據結構是如何演變的

    混合和多云部署模型是企業IT組織的新常態。隨著這些復雜的環境,圍繞數據管理的新挑戰出現了。NetApp的數據管理愿景是一種無縫連接不同的數據結構云,無論它們是私有環境、公共環境還是混合環境。
    發表于 08-25 17:15 ?0次下載
    NetApp的<b class='flag-5'>數據結構</b>是如何演變的