Linux下的I/O復用技術之epoll

I/O多路復用

指在單個線程或進程中,同時處理多個I/O操作的技術。

旨在提高程序處理多個并發I/O操作的能力,避免程序因等待某個I/O操作而被阻塞。在傳統的I/O模型中當程序進行I/O操作時(如讀取文件、接受網路數據等),如果數據還未準備好,程序會被阻塞,直到I/O操作完成,這會導致效率低下,尤其是在需要處理大量并發連接的網絡應用中。I/O復用技術的核心理念是允許一個進程或線程同時處理多個I/O操作,而不是等待某一個操作完成后再去處理其他任務。通過則何種方式,程序能夠在多個I/O之間切換,充分利用系統資源,避免每個I/O事件都創建一個新的線程或進程,從而大大提高效率。在linux中相關技術有select()、poll()、epoll(),程序會通過上述機制同時監控多個I/O事件,并在其中某個文件描述符(或I/O操作)就緒時,進行相應的處理。這樣即使有多個I/O操作正在進行,程序也可以及時響應,并繼續進行其他任務,從而達到“非阻塞”的效果。

epoll涉及到的系統調用函數

epoll_create()

用于創建一個epoll示例,返回一個文件描述符,程序通過這個文件描述符與epoll實例進行交互。他為事件提供一個內核空間的數據結構,并為之后的epoll_ctl()和epoll_wait()調用提供一個有效的上下文。

int epoll_create(int size);

size:指定內核事件表的初始大小。這個參數在現代linux系統中已沒有什么作用,通常?? 設置為1即可,因為內核會動態調整。

返回值:創建成功返回一個非負值,表示epoll實例的文件描述符,創建失敗返回-1,??? ? 并設置errno為相應的錯誤代碼。

int epoll_create1(int flags);

flags:創建時指定的額外選項

傳入0代表不指定額外選項

EPOLL_CTL_ADD:添加新的文件描述符及其事件EPOLL_CTL_MOD:修改已經注冊的文件描述符的事件EPOLL_CTL_DEL:刪除文件描述符的注冊

傳入EPOLL_CLOEXEC:設置文件描述符為執行時關閉,以確保在exec系列函數調用后自動關閉該文件的文件描述符。

epoll_ctl()

用于控制epoll實例中的事件,包括向epoll注冊、修改或刪除文件?????? ???????????????? ? 描述符的I/O事件。

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

epfd:由epoll_create()返回的epoll文件描述符,用于標識epoll實例。

op:操作類型,指示所執行的操作。

EPOLL_CTL_ADD:添加新的文件描述符及其事件EPOLL_CTL_MOD:修改已經注冊的文件描述符的事件EPOLL_CTL_DEL:刪除文件描述符的注冊

fd:需要注冊、修改或刪除的文件描述符

event:struct epoll_event類型的指針,表示與文件描述符關聯的事件類型,這個結構體參數的作用不僅僅是保存文件描述符,他還包含了與該文件描述符關聯的事件類型、以及其他用于標識和處理時間的數據

返回值:成功返回0,失敗返回-1,并設置errno為相應的錯誤代碼。

..................................................................................................................................................

補:epoll_event數據結構

該結構體定義了與文件描述符關聯的事件類型及其他數據

struct epoll_event {uint32_t  events;  //事件類型epoll_data_t data;  //與文件描述符關聯的數據,通常為文件描述符或指針
};

其中:

events:表示該文件描述符的事件類型,注意這些事件類型是可以a|b的混合注冊的

data:是一個聯合體epoll_data_t,用于存儲與文件描述符相關的自定義數據,通常用于存儲文件描述符本身或替他數據

epoll_data_t可以是:int fd(文件描述符);? void* ptr(指向用戶數據的指針)等

..................................................................................................................................................

示例:

struct epoll_event ev;
ev.event = EPOLLIN;  //設置為讀取事件
ev.data.fd = server_fd;  //設置文件描述符
int res = epoll_create(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

epoll_wait()

用于等待已注冊的文件描述符上的事件發生。該函數會阻塞直到至少一個事件發生或者超時。等待并返回已發生的事件。

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

epfd:由epoll_create返回的epoll文件描述符

events:指向epoll_event結構體數組的指針,用于接收已發生的事件。

event是一個數組,用于存儲epoll_event類型的結構體,即用于接收發生的事件event。這里的事件是指,在epoll_ctl階段注冊到epoll中的事件event,并且他得已經發生,已經發生的意思是指已注冊的文件描述符的狀態滿足了你注冊的事件條件(如:在epoll_ctl階段為某個文件描述符fd注冊了EPOLLIN,當fd上有數據可讀時,例如socket接收到網絡數據了,那么這個fd上就有數據可以讀取了,這個事件就被認為已經發///或者為某個文件描述符注冊的EPOLLOUT,當fd可以用來寫數據時,比如socket的發送緩沖區有空閑空間時,那么這個fd就可以進行寫,這個事件被認為已經發生了)。

epoll_wait()的作用可以這樣理解,它的作用就是監聽在epoll_ctl注冊到epfd中的事件集合,然后將發生的事件傳入events數組中,通過遍歷這個events可以得到event,通過event的fd和events成員可以得知該fd可以進行events對應操作了(比如:struct epoll_event ev;? ev.fd = fd1;? ev.events = EPOLLOUT,那么我們可以對fd1進行寫操作了,write(fd, ....))。

maxevents:指定events數組的大小,即一次最多返回的事件數

timeout:指定等待的超時時間(單位:毫秒)。設置為-1時表示無限等待,設置為0表示??????????????? 非阻塞,其他正值表示最大等待時間。

返回值:成功則返回已就緒的事件數,即發生的事件數量,失敗則返回-1,并設置errno????????? 為相應的錯誤代碼。

示例:

struct epoll_event events[10];
int nfds = epoll_wait(epfd, events, 10, -1);
for (int i = 0; i < nfds; ++i) {if (events[i].events & EPOLLIN) {// 處理可讀事件}if (events[i].events & EPOLLOUT) {// 處理可寫事件}
}

?綜合使用簡單示例:

// 創建一個 epoll 實例,返回 epoll 文件描述符
int epfd = epoll_create(1);// 定義一個 epoll_event 結構體變量,用于描述要監聽的事件
struct epoll_event ev;// 設置事件類型為 EPOLLOUT,表示監聽 "可寫" 事件
ev.events = EPOLLOUT;// 綁定要監聽的文件描述符(socketfd)到 ev 的 data.fd 字段
ev.data.fd = socketfd;// 將指定的文件描述符(socketfd)注冊到 epoll 實例 epfd 中,監聽可寫事件
epoll_ctl(epfd, EPOLL_CTL_ADD, socketfd, &ev);// 定義一個數組,用來接收 epoll_wait 返回的就緒事件
struct epoll_event events[10];// 調用 epoll_wait,阻塞等待內核檢測 epfd 中注冊的事件
// -1 表示永遠等待,直到有事件發生
int nfds = epoll_wait(epfd, events, 10, -1);// 遍歷所有返回的就緒事件
for (int i = 0; i < nfds; ++i) {// 檢查當前事件是否包含 EPOLLOUT,可寫事件if (events[i].events & EPOLLOUT) {// 取出就緒的文件描述符int fd = events[i].data.fd;// 定義要發送的消息內容const char* message = "hello";// 使用 write 將消息寫入到對應的文件描述符ssize_t bytes_written = write(fd, message, strlen(message));// 這里沒有做錯誤檢查,實際項目中最好檢查 bytes_written 是否出錯}
}
// 創建 epoll 實例,返回 epoll 文件描述符
int epfd = epoll_create1(0);// 注意:這里通過一個 open 打開了一個文件,得到一個文件描述符,但它不一定滿足下面的 EPOLLIN 事件,
// 只有當 example 文件中有數據時,它才可以被讀,才滿足該 fd 監聽的 / 感興趣的事件 EPOLLIN,
// 它才可以在 epoll_wait 的時候被添加到 events 中。
int fd = open("example.txt", O_RDONLY);  // 注意:這里應該是 O_RDONLY,不是 0_RDONLY// 定義一個 epoll_event 結構體變量
struct epoll_event ev;// 設置監聽的事件類型為 EPOLLIN(可讀事件)
ev.events = EPOLLIN;// 將文件描述符 fd 保存到 ev.data.fd 中
ev.data.fd = fd;// 調用 epoll_ctl,將 fd 注冊到 epoll 實例 epfd 中,關注可讀事件
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);// 定義一個數組,用來存放 epoll_wait 返回的就緒事件
struct epoll_event events[10];// 調用 epoll_wait,阻塞等待 epfd 中注冊的文件描述符上有事件發生
int nfds = epoll_wait(epfd, events, 10, -1);// 遍歷所有返回的就緒事件
for (int i = 0; i < nfds; ++i) {// 如果當前事件是 EPOLLIN(可讀事件)if (events[i].events & EPOLLIN) {// 在這里處理對應的可讀文件描述符}
}

epoll底層實現原理

核心組件

①紅黑樹

epoll內部維護了一顆紅黑樹,通過紅黑樹來管理(增加、刪除、修改)所有被監控的文件描述符;當調用epoll_ctl增加、刪除或修改監控事件時,會在紅黑樹中插入、移除或更新相應的節點;紅黑樹的高效查找和插入特性(時間復雜度為O(Logn))使得epoll在管理大量文件描述符時性能優越。

②就緒鏈表(雙向鏈表)

epoll使用一個就緒鏈表來保存當前已經觸發事件的文件描述符;當某個文件描述符變為就緒狀態時(例如數據可讀或可寫),其對應的事件會被添加到就緒鏈表中;這使得epoll_wait調用只需直接掃描這個鏈表,從而避免了向poll或select那樣逐個遍歷所有的文件描述符。

與內核的交互

epoll是依賴內核中的事件通知機制來工作的,通常通過文件系統(如proc文件系統等)監控文件描述符的狀態;每個文件描述符在內核中都有一個對應的事件回調函數,當事件發生時(即通過epoll_ctl添加到紅黑樹中的event對應的fd滿足注冊的事件狀態時),會觸發這個回調函數,將事件添加到就緒鏈表中,當調用epoll_wait時,內核將掃描就緒鏈表,并返回鏈表中的數據。

觸發機制

在epoll中,觸發機制決定了epoll_wait如何返回文件描述符的事件。這直接影響事件的通知方式和應用程序對文件描述符的處理策略,epoll支持兩種觸發機制:水平觸發和邊緣觸發。

①水平觸發(level triggered

這是epoll默認的觸發方式。文件描述符只要處于就緒狀態(可讀或可寫),epoll_wait就會一直返回該事件(調用該方法返回的int值大小中有它一席,并且傳入給epoll_wait的events數組也會一直存入這個事件event);無論文件描述符的狀態是否變化,只要其仍然滿足條件(如緩沖區有數據可讀),事件都會重復觸發,直到應用程序對其處理完成。

優點:使用簡單,適合大多數場景。不容易遺漏事件,即使處理稍有延遲,也可以通過多次調用讀取剩余數據。

缺點:對于大量文件描述符,就緒事件可能被重新觸發,導致處理效率較低。

struct epoll_event ev;
ev.events = EPOLLIN;  //水平觸發是默認的

②邊沿觸發(edge triggered

事件只會在狀態變化時觸發(如從不可讀變為可讀,或從不可寫變成可寫);如果應用程序沒有在事件觸發時處理完數據,則不會再次觸發,可能導致數據遺漏。

優點:減少了重復通知,提高了系統效率,適合大規模并發場景,支持高性能的非阻塞模式。

缺點:復雜性高,必須一次性讀取或寫入盡可能多的數據,否則可能會遺漏數據。

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;	//設置為邊沿觸發

epoll工作流程

首先,用戶通過調用epoll_create創建一個epoll實例,用于管理所有需要監聽的文件描述符(每個epoll實例中都有一個獨立的evetnepoll結構體,用于存放通過epoll_ctl方法向epoll對象中添加的事件)。接著,使用epoll_ctl向epoll內部的紅黑樹中添加、修改或刪除需要監聽的文件描述符及事件;最后,通過調用epoll_wait等待事件發生,內核會將已經觸發的事件加入就緒鏈表,并將鏈表中的就緒文件描述符返回給用戶程序,從而實現高效的事件驅動模型。

相較于selectpollepoll為什么高效?

以下幾個差異導致epoll更高效

1.事件監聽和管理方式的不同

select、poll:每次調用select、poll,都需要將所有文件描述符表傳遞給內核,內?? 核會對這些文件描述符逐一查其狀態,這種查找是線性查找,時間復雜度是O(n), 導致性能開銷大,尤其是在文件描述符數量較多時。

epoll使用一個紅黑樹來存儲用戶注冊的文件描述符事件,文件描述符只需通過?? epoll_ctl注冊一次;每次epoll_wait時,內核只需檢查紅黑樹上的事件,并通過 一個就緒鏈表直接返回有事件發生的文件描述符;事件分發是基于回調的機制,無 需線性掃描。

2.數據拷貝的效率

select、poll:每次調用select、poll都需要將文件描述符列表從用戶態拷貝到內核 態,再從內核態返回結果到用戶態,如果文件描述符很多,這個過程會占用大量的 cpu和內存帶寬。

epoll采用共享內存機制,文件描述符只在epoll_ctl注冊時傳遞給內核;內核和用 戶空間之間通過共享的就緒鏈表來傳遞數據,避免每次調用時的大量拷貝。

3.支持更大的文件描述符集合

select文件描述符數量受到系統常量FD_SETSIZE的限制(通常是1024個)。超過限 制后無法使用。

poll支持更多的文件描述符,但依然需要遍歷整個文件描述符列表

epoll支持的文件描述符數量只受限于系統的最大文件描述符數量,理論上可以達到?? 數十萬甚至更多。即使文件描述符數量龐大,只關注有事件發生的文件描述符,效 率依然很高。

4.觸發機制的差異

select、poll:只支持水平觸發,即只要文件描述符的狀態滿足條件,每次都會返回, 可能會導致重復處理。

epoll支持水平觸發和邊緣觸發,邊緣觸發模式下,只有當文件描述符的狀態從未滿?? 足到滿足時,才會觸發事件,進一步減少系統調用的次數,提高性能。

5.線程安全性

epoll是線程安全的(epoll_wait內存實現對共享的就緒事件列表有鎖機制保活,確 保線程安全),多個線程可以同時調用epoll_wait,充分利用多核CPU提高并?? 發? 能力。

select、poll:通常需要額外的同步機制來確保多線程訪問同一個文件描述符集合時的 線程安全性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78287.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78287.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78287.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用 C 語言實現通用的冒泡排序算法

在日常編程中&#xff0c;排序算法是一個非常常見且重要的工具。雖然有許多排序算法可以選擇&#xff0c;但如果你需要一個能夠處理不同數據類型的排序算法&#xff0c;如何設計一個通用的排序算法呢&#xff1f;今天我們將實現一個通用的冒泡排序算法&#xff0c;支持不同數據…

C# 變量全解析:聲明、初始化與使用

在多用途的編程語言中&#xff0c;程序存取數據是一項基礎且關鍵的功能&#xff0c;而這一功能主要通過變量來實現。本文將全面深入地探討 C# 中的變量&#xff0c;包括變量的種類、聲明、初始化、自動初始化、多變量聲明以及如何使用變量的值。 變量概述 變量是一個名稱&…

Dify中的文本分詞處理技術詳解

Dify中的文本分詞處理技術詳解 引言核心架構概覽索引處理器工廠 文本分詞技術詳解基礎分詞器增強型遞歸字符分詞器固定分隔符文本分詞器遞歸分割算法 索引處理器中的分詞應用特殊索引處理器的分詞特點問答索引處理器父子索引處理器 分詞技術的應用場景技術亮點與優勢總結 引言 …

如何打包python程序為可執行文件

將 Python 程序打包為可執行文件是一個常見需求&#xff0c;尤其是在希望將應用程序分享給不具備 Python 環境的用戶時。以下是使用 PyInstaller 工具將 Python 程序打包為可執行文件的步驟。 步驟 1&#xff1a;安裝 PyInstaller 如果您還沒有安裝 PyInstaller&#xff0c;請…

美團Java后端二面面經!

場景題是面試的大頭&#xff0c;建議好好準備 Q. [美團]如何設計一個外賣訂單的并發扣減庫存系統&#xff1f; Q.[美團]為啥初始標記和重新標記需要STW&#xff1f; Q.[美團]騎手位置實時更新&#xff0c;如何保證高并發寫入&#xff1f; Q.[美團]訂單表數據量過大導致查詢…

在應用運維過程中,業務數據修改的證據留存和數據留存

在應用運維過程中,業務數據修改的證據留存和數據留存至關重要,以下是相關介紹: 一、證據留存 操作日志記錄 : 詳細記錄每一次業務數據修改的操作日志,包括操作人員、操作時間、修改內容、修改前后數據的對比等。例如,某公司業務系統中,操作日志會精確記錄員工小張在 2…

Eigen迭代求解器類

1. 迭代求解器核心類概覽 Eigen 提供多種迭代法求解稀疏線性方程組 AxbAxb&#xff0c;適用于大規模稀疏矩陣&#xff1a; 求解器類適用矩陣類型算法關鍵特性ConjugateGradient對稱正定&#xff08;SPD&#xff09;共軛梯度法&#xff08;CG&#xff09;高精度&#xff0c;內…

ORACLE數據庫備份入門:第四部分:2-備份場景舉例

下面以4個常見的場景為例&#xff0c;介紹如何規劃備份方案。備份方案沒有標準答案&#xff0c;需要根據實現情況來制定&#xff0c;也和管理員的個人使用習慣有很大相關性。 1 交易型數據庫備份 以銀行的交易系統為例&#xff0c;除了前一章節提到的關于RPO和RTO的指標外&am…

小白如何學會完整挪用Github項目?(以pix2pix為例)

[目錄] 0.如何完整地復現/應用一個Github項目 1.建立適用于項目的環境 2.數據準備與模型訓練階段 3.訓練過程中的一些命令行調試必備知識0.如何完整地復現/應用一個Github項目 前日在健身房的組間同一位好友交流時&#xff0c;得到了一個一致結論—— ** Github \texttt{Githu…

藍橋杯 5. 交換瓶子

交換瓶子 原題目鏈接 題目描述 有 N 個瓶子&#xff0c;編號為 1 ~ N&#xff0c;放在架子上。 例如有 5 個瓶子&#xff0c;當前排列為&#xff1a; 2 1 3 5 4每次可以拿起 2 個瓶子&#xff0c;交換它們的位置。 要求通過若干次交換&#xff0c;使得瓶子的編號從小到大…

Linux 系統滲透提權

Linux 系統滲透提權 比賽題庫-Linux 系統滲透提權 文章目錄 Linux 系統滲透提權比賽題庫-Linux 系統滲透提權 前言一、解題過程1.使用滲透機對服務器信息收集&#xff0c;并將服務器中 SSH 服務端口號作為 flag 提 交&#xff1b;2.使用滲透機對服務器信息收集&#xff0c;并將…

華為OD機試真題——查找接口成功率最優時間段(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 A卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C、GO六種語言的最佳實現方式&#xff1b; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析&#xff1b; 本文收錄于專欄&#xff1a;《2025華為OD真題目錄…

華為OD機試真題——繪圖機器(2025A卷:100分)Java/python/JavaScript/C++/C/GO最佳實現

2025 A卷 100分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析&#xff1b; 并提供Java、python、JavaScript、C、C語言、GO六種語言的最佳實現方式&#xff01; 本文收錄于專欄&#xff1a;《2025華為OD真題目錄全流程解析/備考攻略/經驗…

基于 Python(selenium) 的百度新聞定向爬蟲:根據輸入的關鍵詞在百度新聞上進行搜索,并爬取新聞詳情頁的內容

該項目能夠根據輸入的關鍵詞在百度新聞上進行搜索,并爬取新聞詳情頁的內容。 一、項目準備 1. 開發環境配置 操作系統:支持 Windows、macOS、Linux 等主流操作系統,本文以 Windows 為例進行說明。Python 版本:建議使用 Python 3.8 及以上版本,以確保代碼的兼容性和性能。…

MySQL表的操作 -- 表的增刪改查

目錄 1. 表的創建2. 表的查看3. 表的修改4. 表的刪除5. 總結 1. 表的創建 1.查看字符集及效驗規則 2. 表的創建 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校驗規則 engine 存儲引擎;創建用戶表1 創建用…

如何解決極狐GitLab 合并沖突?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 合并沖突 (BASIC ALL) 合并沖突發生在合并請求的兩個分支&#xff08;源分支和目標分支&#xff09;對相同代碼行進行了不同…

oracle不同數據庫版本的自增序列

-- 查看數據庫版本 SELECT * FROM v$version WHERE banner LIKE Oracle%; 1. Oracle 12c及以上版本支持 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id NUMBER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, -- 語法 id NUMBER GENER…

VIC-3D非接觸全場應變測量系統用于小尺寸測量之電子元器件篇—研索儀器DIC數字圖像相關技術

在5G通信、新能源汽車電子、高密度集成電路快速迭代的今天&#xff0c;電子元件的尺寸及連接工藝已進入亞毫米級競爭階段&#xff0c;這種小尺寸下的力學性能評估對測量方式的精度有更高的要求&#xff0c;但傳統應變測量手段常因空間尺寸限制及分辨率不足難以捕捉真實形變場。…

pod 創建私有庫指南

步驟 參考&#xff1a;iOS Pod 私有庫創建指南-百度開發者中心 下面主要是對參考鏈接里面的解釋&#xff1a; 創建兩個倉庫&#xff1a; 一個叫podframe.git&#xff0c;用來存放自定義的framework&#xff0c;比如TestPodFrame.framework一個叫podspec.git&#xff0c;用來…

【JavaEE】Spring AOP的注解實現

目錄 一、AOP 與 Spring AOP二、Spring AOP簡單實現三、詳解Spring AOP3.1 Spring AOP 核心概念3.1.1 切點&#xff08;Pointcut&#xff09;3.1.2 連接點&#xff08;Join Point&#xff09;3.1.3 通知&#xff08;Advice&#xff09;3.1.4 切面&#xff08;Aspect&#xff09…