精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

但是,I/O多路復(fù)用中是如何判斷文件“可讀”/“可寫”的?

存儲(chǔ) 存儲(chǔ)架構(gòu)
在進(jìn)行網(wǎng)絡(luò)編程或處理其他類型的 I/O 操作時(shí),一個(gè)常見(jiàn)的挑戰(zhàn)是如何高效地管理多個(gè)并發(fā)的 I/O 通道。如果為每個(gè)連接或文件都創(chuàng)建一個(gè)單獨(dú)的線程或進(jìn)程來(lái)阻塞等待 I/O,當(dāng)連接數(shù)非常多時(shí),系統(tǒng)資源的開(kāi)銷(如內(nèi)存、上下文切換成本)會(huì)變得非常巨大。

在學(xué)習(xí)I/O多路復(fù)用時(shí),經(jīng)常會(huì)得到如下描述:

...,在其中任何一個(gè)或多個(gè)描述符 準(zhǔn)備好進(jìn)行 I/O 操作(可讀、可寫或異常)時(shí)獲得通知 。

那么,操作系統(tǒng)內(nèi)核到底是如何判斷某個(gè)文件描述符“可讀”/“可寫”呢?在達(dá)到相關(guān)狀態(tài)后,是如何“立即”通知到應(yīng)用程序的呢?本文在探究這個(gè)問(wèn)題。

I/O 多路復(fù)用與文件描述符狀態(tài)檢測(cè)

在進(jìn)行網(wǎng)絡(luò)編程或處理其他類型的 I/O 操作時(shí),一個(gè)常見(jiàn)的挑戰(zhàn)是如何高效地管理多個(gè)并發(fā)的 I/O 通道。如果為每個(gè)連接或文件都創(chuàng)建一個(gè)單獨(dú)的線程或進(jìn)程來(lái)阻塞等待 I/O,當(dāng)連接數(shù)非常多時(shí),系統(tǒng)資源的開(kāi)銷(如內(nèi)存、上下文切換成本)會(huì)變得非常巨大。

I/O 多路復(fù)用 (I/O Multiplexing) 技術(shù)應(yīng)運(yùn)而生,它允許單個(gè)進(jìn)程或線程監(jiān)視多個(gè) 文件描述符 (file descriptor),并在其中任何一個(gè)或多個(gè)變得“就緒”(例如,可讀或可寫)時(shí)得到通知,從而可以在單個(gè)執(zhí)行流中處理多個(gè) I/O 事件。Linux 提供了幾種經(jīng)典的 I/O 多路復(fù)用系統(tǒng)調(diào)用,主要是 select、poll 和 epoll。

要理解這些系統(tǒng)調(diào)用如何工作,關(guān)鍵在于理解 Linux 內(nèi)核是如何跟蹤和通知文件描述符狀態(tài)變化的。這涉及到內(nèi)核中的文件系統(tǒng)抽象、網(wǎng)絡(luò)協(xié)議棧以及一種核心機(jī)制: 等待隊(duì)列 (wait queue) 。

文件描述符與內(nèi)核結(jié)構(gòu)

在 Linux 中,“一切皆文件”是一個(gè)核心設(shè)計(jì)哲學(xué)。無(wú)論是磁盤文件、管道、終端還是網(wǎng)絡(luò)套接字 (socket),在用戶空間看來(lái),它們都通過(guò)一個(gè)非負(fù)整數(shù)來(lái)標(biāo)識(shí),即文件描述符。

當(dāng)應(yīng)用程序通過(guò) socket() 系統(tǒng)調(diào)用創(chuàng)建一個(gè)套接字時(shí),內(nèi)核會(huì)執(zhí)行以下關(guān)鍵步驟:

  1. 在內(nèi)核空間創(chuàng)建表示該套接字的核心數(shù)據(jù)結(jié)構(gòu),通常是 struct socket。這個(gè)結(jié)構(gòu)包含了套接字的狀態(tài)、類型、協(xié)議族、收發(fā)緩沖區(qū)、指向協(xié)議層處理函數(shù)的指針等信息。
  2. 創(chuàng)建一個(gè) struct file 結(jié)構(gòu)。這是內(nèi)核中代表一個(gè)打開(kāi)文件的通用結(jié)構(gòu),它包含訪問(wèn)模式、當(dāng)前偏移量等,并且有一個(gè)重要的成員 f_op,指向一個(gè) file_operations 結(jié)構(gòu)。
  3. file_operations 結(jié)構(gòu)包含了一系列函數(shù)指針,定義了可以對(duì)這類文件執(zhí)行的操作,如 read、write、poll、release 等。對(duì)于套接字,struct file 會(huì)通過(guò)其私有數(shù)據(jù)指針 (private_data) 關(guān)聯(lián)到對(duì)應(yīng)的 struct socket,并且其 f_op 會(huì)指向一套適用于套接字的文件操作函數(shù)集。
  4. 內(nèi)核在當(dāng)前進(jìn)程的文件描述符表中找到一個(gè)空閑位置,將該位置指向新創(chuàng)建的 struct file 結(jié)構(gòu),并將該位置的索引(即文件描述符)返回給用戶空間。

因此,后續(xù)所有對(duì)該文件描述符的操作(如 read, write, bind, listen, accept, select, poll, epoll_ctl 等),都會(huì)通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核,內(nèi)核根據(jù)文件描述符找到對(duì)應(yīng)的 struct file,再通過(guò) f_op 調(diào)用相應(yīng)的內(nèi)核函數(shù)來(lái)執(zhí)行。

等待隊(duì)列:事件通知的核心

操作系統(tǒng)需要一種機(jī)制,讓某個(gè)進(jìn)程在等待特定事件(例如,數(shù)據(jù)到達(dá)套接字、套接字發(fā)送緩沖區(qū)有可用空間)發(fā)生時(shí)能夠暫停執(zhí)行(睡眠),并在事件發(fā)生后被喚醒。這就是 等待隊(duì)列 (wait queue) 機(jī)制 (wait_queue_head_t 在 Linux 內(nèi)核中)。

struct list_head {
 struct list_head *next, *prev;
};

struct wait_queue_head {
 spinlock_t  lock;
 struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;

struct wait_queue_entry {
 unsigned int  flags;
 void   *private;
 wait_queue_func_t func;
 struct list_head entry;
};
typedef struct wait_queue_entry wait_queue_entry_t;

等待隊(duì)列 (wait_queue_head_t) 是 Linux 內(nèi)核中的一個(gè)數(shù)據(jù)結(jié)構(gòu),用于管理一組等待特定事件(如數(shù)據(jù)到達(dá))的進(jìn)程。它本質(zhì)上是一個(gè)鏈表,鏈表中的每個(gè)節(jié)點(diǎn) (wait_queue_entry_t) 代表一個(gè)等待該事件的進(jìn)程。

  • wait_queue_head_t:包含一個(gè)鎖(保護(hù)隊(duì)列)和一個(gè)指向等待條目鏈表的指針。
  • wait_queue_entry_t:包含進(jìn)程的標(biāo)識(shí)(如任務(wù)結(jié)構(gòu)體 task_struct)和指向下一個(gè)條目的指針。

等待隊(duì)列允許進(jìn)程在事件未發(fā)生時(shí)暫停執(zhí)行,并在事件發(fā)生時(shí)被喚醒,是阻塞式 I/O 和多路復(fù)用的基礎(chǔ)。

內(nèi)核中幾乎所有可能導(dǎo)致阻塞等待的資源(如套接字的接收緩沖區(qū)、發(fā)送緩沖區(qū)、管道、鎖等)都會(huì)關(guān)聯(lián)一個(gè)或多個(gè)等待隊(duì)列。

數(shù)據(jù)結(jié)構(gòu)中的嵌入

每個(gè)資源在內(nèi)核中都有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。例如,對(duì)于網(wǎng)絡(luò)套接字,內(nèi)核維護(hù)一個(gè) struct socket 結(jié)構(gòu),其中嵌入了與接收緩沖區(qū)和發(fā)送緩沖區(qū)相關(guān)的等待隊(duì)列。通常,每個(gè)套接字會(huì)有兩個(gè)獨(dú)立的 wait_queue_head_t:

  • 一個(gè)用于接收數(shù)據(jù)(等待接收緩沖區(qū)有數(shù)據(jù))。
  • 一個(gè)用于發(fā)送數(shù)據(jù)(等待發(fā)送緩沖區(qū)有空間)。

這些等待隊(duì)列是 struct socket 或相關(guān)結(jié)構(gòu)(如 struct sock)的成員變量,直接與資源綁定。

當(dāng)進(jìn)程對(duì)資源執(zhí)行操作(例如通過(guò) read() 讀取套接字?jǐn)?shù)據(jù))時(shí),如果資源不可用(接收緩沖區(qū)為空),內(nèi)核會(huì):

  1. 內(nèi)核創(chuàng)建一個(gè) wait_queue_entry_t,關(guān)聯(lián)到當(dāng)前進(jìn)程的 task_struct。
  2. 將這個(gè)條目加入與接收緩沖區(qū)相關(guān)的等待隊(duì)列。
  3. 將進(jìn)程狀態(tài)設(shè)置為睡眠(TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE),然后調(diào)用調(diào)度器 schedule(),讓出 CPU,運(yùn)行其他進(jìn)程。

對(duì)于套接字,struct sock(TCP/IP 協(xié)議棧中的核心結(jié)構(gòu))包含字段如 sk_sleep,它指向一個(gè)等待隊(duì)列。當(dāng)接收緩沖區(qū)為空時(shí),進(jìn)程會(huì)被加入這個(gè)隊(duì)列;當(dāng)數(shù)據(jù)到達(dá)時(shí),協(xié)議棧會(huì)操作這個(gè)隊(duì)列來(lái)喚醒進(jìn)程。

喚醒過(guò)程是如何實(shí)現(xiàn)的?

當(dāng)網(wǎng)絡(luò)協(xié)議棧(如 TCP/IP)收到數(shù)據(jù)并將其放入套接字的接收緩沖區(qū)時(shí):

  1. 事件檢測(cè): 協(xié)議棧代碼檢測(cè)到接收緩沖區(qū)從空變?yōu)榉强铡?/li>
  2. 檢查等待隊(duì)列: 協(xié)議棧訪問(wèn)與該緩沖區(qū)關(guān)聯(lián)的 wait_queue_head_t,檢查是否有進(jìn)程在等待。
  3. 調(diào)用 wake_up(): 如果隊(duì)列不為空,協(xié)議棧調(diào)用內(nèi)核函數(shù) wake_up()(或其變體,如 wake_up_interruptible())。
  4. wake_up() 的工作:
  • 遍歷等待隊(duì)列中的每個(gè) wait_queue_entry_t。
  • 將對(duì)應(yīng)的進(jìn)程狀態(tài)從睡眠改為 TASK_RUNNING。
  • 將這些進(jìn)程加入 CPU 的運(yùn)行隊(duì)列,等待調(diào)度器重新調(diào)度它們。
進(jìn)程 A 調(diào)用 read(sockfd) -> 接收緩沖區(qū)為空
  -> 加入等待隊(duì)列 -> 進(jìn)程睡眠
數(shù)據(jù)到達(dá) -> 協(xié)議棧放入緩沖區(qū) -> 調(diào)用 wake_up()
  -> 進(jìn)程 A 被喚醒 -> 重新調(diào)度 -> read() 返回?cái)?shù)據(jù)

可讀性

當(dāng)一個(gè)套接字接收到數(shù)據(jù)時(shí),網(wǎng)絡(luò)協(xié)議棧(如 TCP/IP 棧)處理完數(shù)據(jù)包后,會(huì)將數(shù)據(jù)放入該套接字的接收緩沖區(qū)。如果此時(shí)有進(jìn)程正在等待該套接字變?yōu)榭勺x(即接收緩沖區(qū)中有數(shù)據(jù)),協(xié)議棧代碼會(huì) 喚醒 (wake up) 在該套接字接收緩沖區(qū)關(guān)聯(lián)的等待隊(duì)列上睡眠的所有進(jìn)程。

可寫性

當(dāng)應(yīng)用程序通過(guò) write() 或 send() 發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)首先被復(fù)制到套接字的發(fā)送緩沖區(qū)。網(wǎng)絡(luò)協(xié)議棧隨后從緩沖區(qū)取出數(shù)據(jù)并發(fā)送到網(wǎng)絡(luò)。當(dāng)數(shù)據(jù)成功發(fā)送出去,或者發(fā)送緩沖區(qū)中的空間被釋放到某個(gè)閾值以上時(shí),協(xié)議棧代碼會(huì) 喚醒 在該套接字發(fā)送緩沖區(qū)關(guān)聯(lián)的等待隊(duì)列上睡眠的所有進(jìn)程,通知它們現(xiàn)在可以寫入更多數(shù)據(jù)了。

假設(shè)發(fā)送緩沖區(qū)大小為 8KB,高水位標(biāo)記為 6KB:

  • 寫入 8KB 數(shù)據(jù) -> 緩沖區(qū)滿 -> 進(jìn)程睡眠。
  • 協(xié)議棧發(fā)送 3KB 數(shù)據(jù) -> 剩余 5KB(低于高水位) -> 喚醒進(jìn)程 -> 可寫。

這個(gè)“生產(chǎn)者-消費(fèi)者”模型(網(wǎng)絡(luò)棧是數(shù)據(jù)的生產(chǎn)者/消費(fèi)者,應(yīng)用程序是數(shù)據(jù)的消費(fèi)者/生產(chǎn)者)通過(guò)等待隊(duì)列和喚醒機(jī)制實(shí)現(xiàn),是理解 I/O 事件通知的基礎(chǔ)。

select 和 poll 的工作原理

select 和 poll 是較早的 I/O 多路復(fù)用接口。它們的工作方式類似:

  1. 用戶調(diào)用 :應(yīng)用程序準(zhǔn)備好要監(jiān)視的文件描述符集合(select 使用 fd_set,poll 使用 struct pollfd 數(shù)組),并指定關(guān)心的事件類型(可讀、可寫、異常),然后調(diào)用 select 或 poll 系統(tǒng)調(diào)用。
  2. 內(nèi)核操作 :

某個(gè)被監(jiān)視的文件描述符相關(guān)的等待隊(duì)列被喚醒(例如,因?yàn)閿?shù)據(jù)到達(dá)或緩沖區(qū)變空)。

超時(shí)時(shí)間到達(dá)。

收到一個(gè)信號(hào)。

  • 檢查當(dāng)前狀態(tài) :立即檢查該文件描述符的當(dāng)前狀態(tài)是否滿足用戶請(qǐng)求的事件(例如,接收緩沖區(qū)是否非空?發(fā)送緩沖區(qū)是否有足夠空間?是否有錯(cuò)誤?)。如果滿足,就標(biāo)記該文件描述符為就緒。
  • 注冊(cè)等待 :如果當(dāng)前狀態(tài)不滿足,并且調(diào)用者準(zhǔn)備阻塞等待,則該 poll 方法會(huì)將當(dāng)前進(jìn)程添加到與所關(guān)心事件相關(guān)的 等待隊(duì)列 上。這是通過(guò)內(nèi)核函數(shù) poll_wait() 實(shí)現(xiàn)的,它并不直接使進(jìn)程睡眠,只是建立一個(gè)關(guān)聯(lián):如果未來(lái)該等待隊(duì)列被喚醒,當(dāng)前正在執(zhí)行 select/poll 的進(jìn)程也應(yīng)該被喚醒。
  • 內(nèi)核接收到文件描述符列表和關(guān)心的事件。
  • 內(nèi)核遍歷應(yīng)用程序提供的 每一個(gè) 文件描述符。
  • 對(duì)于每個(gè)文件描述符,內(nèi)核找到對(duì)應(yīng)的 struct file,然后調(diào)用其 file_operations 結(jié)構(gòu)中的 poll 方法(例如,對(duì)于套接字,最終會(huì)調(diào)用到類似 sock_poll 的函數(shù))。
  • 該 poll 方法執(zhí)行兩個(gè)關(guān)鍵任務(wù):
  • 遍歷完所有文件描述符后,如果發(fā)現(xiàn)至少有一個(gè)文件描述符是就緒的,select/poll 就將就緒信息返回給應(yīng)用程序。
  • 如果沒(méi)有文件描述符就緒,并且設(shè)置了超時(shí)時(shí)間,則進(jìn)程會(huì) 睡眠 (阻塞),直到以下任一情況發(fā)生:
  • 當(dāng)進(jìn)程被喚醒后(如果是因等待隊(duì)列事件喚醒),內(nèi)核并 不知道 是哪個(gè)具體的文件描述符導(dǎo)致了喚醒。因此,內(nèi)核需要 重新遍歷一遍 所有被監(jiān)視的文件描述符,再次調(diào)用它們的 poll 方法檢查狀態(tài),找出哪些現(xiàn)在是就緒的,然后將結(jié)果返回給用戶。

解釋一下 poll_wait() :它建立了一種關(guān)聯(lián):當(dāng)?shù)却?duì)列被喚醒時(shí),當(dāng)前執(zhí)行 select 或 poll 的進(jìn)程也會(huì)被喚醒。

  • 在 select 或 poll 的內(nèi)核實(shí)現(xiàn)中,對(duì)于每個(gè)文件描述符,內(nèi)核調(diào)用其 file_operations 中的 poll 方法(例如 sock_poll)。
  • 在 poll 方法中,如果事件尚未就緒(例如接收緩沖區(qū)為空),會(huì)調(diào)用 poll_wait(file, wait_queue_head_t, poll_table),poll_wait() 中創(chuàng)建一個(gè) wait_queue_entry_t,關(guān)聯(lián)到當(dāng)前進(jìn)程:

file:文件描述符對(duì)應(yīng)的 struct file。

wait_queue_head_t:與事件(如接收緩沖區(qū))關(guān)聯(lián)的等待隊(duì)列。

poll_table:select/poll 傳入的臨時(shí)結(jié)構(gòu),用于收集等待隊(duì)列。

  • poll_wait() 只是注冊(cè)關(guān)聯(lián),不會(huì)直接調(diào)用 schedule() 使進(jìn)程睡眠。睡眠是在 select/poll 遍歷所有文件描述符后統(tǒng)一處理的。
select(fd_set) -> 內(nèi)核遍歷 fd
  -> fd1: poll() -> poll_wait(接收隊(duì)列) -> 注冊(cè)進(jìn)程
  -> fd2: poll() -> poll_wait(發(fā)送隊(duì)列) -> 注冊(cè)進(jìn)程
無(wú)就緒 fd -> 進(jìn)程睡眠
數(shù)據(jù)到達(dá) fd1 -> wake_up(接收隊(duì)列) -> 進(jìn)程喚醒 -> select 返回

select 和 poll 的主要缺點(diǎn)

  • 效率問(wèn)題 :每次調(diào)用都需要將整個(gè)文件描述符集合從用戶空間拷貝到內(nèi)核空間。更重要的是,內(nèi)核需要線性遍歷所有被監(jiān)視的文件描述符來(lái)檢查狀態(tài)和注冊(cè)等待,喚醒后還需要再次遍歷來(lái)確定哪些就緒。當(dāng)監(jiān)視的文件描述符數(shù)量 N 很大時(shí),這個(gè) O(N) 的開(kāi)銷變得非常顯著。
  • select 有最大文件描述符數(shù)量的限制(通常由 FD_SETSIZE 定義)。poll 沒(méi)有這個(gè)限制,但仍有上述效率問(wèn)題。

epoll:更高效的事件通知

epoll 是 Linux 對(duì) select 和 poll 的重大改進(jìn),旨在解決大規(guī)模并發(fā)連接下的性能瓶頸。它采用了一種不同的、基于 回調(diào) (callback) 的事件驅(qū)動(dòng)機(jī)制:

  1. **epoll_create() / epoll_create1()**:創(chuàng)建一個(gè) epoll 實(shí)例。這會(huì)在內(nèi)核中創(chuàng)建一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu),用于維護(hù)兩個(gè)列表:

監(jiān)視列表 (Interest List) :通常使用高效的數(shù)據(jù)結(jié)構(gòu)(如紅黑樹(shù)或哈希表)存儲(chǔ)所有用戶通過(guò) epoll_ctl 添加的、需要監(jiān)視的文件描述符及其關(guān)心的事件。

就緒列表 (Ready List) :一個(gè)鏈表,存儲(chǔ)那些已經(jīng)被內(nèi)核檢測(cè)到發(fā)生就緒事件、但尚未被 epoll_wait 報(bào)告給用戶的文件描述符。

這個(gè) epoll 實(shí)例本身也由一個(gè)文件描述符表示。

2.epoll_ctl() :用于向 epoll 實(shí)例的監(jiān)視列表添加 (EPOLL_CTL_ADD)、修改 (EPOLL_CTL_MOD) 或刪除 (EPOLL_CTL_DEL) 文件描述符。

  • 關(guān)鍵操作:當(dāng)使用 EPOLL_CTL_ADD 添加一個(gè)文件描述符 fd 時(shí),內(nèi)核不僅將其加入 epoll 實(shí)例的監(jiān)視列表,更重要的是,它會(huì)在與 fd 相關(guān)的 等待隊(duì)列 上注冊(cè)一個(gè) 回調(diào)函數(shù)。
  • 這個(gè)回調(diào)函數(shù)非常特殊:當(dāng) fd 對(duì)應(yīng)的資源(如套接字緩沖區(qū))狀態(tài)改變,導(dǎo)致其關(guān)聯(lián)的等待隊(duì)列被喚醒時(shí),這個(gè)注冊(cè)的回調(diào)函數(shù)會(huì)被執(zhí)行。
  • 回調(diào)函數(shù)的任務(wù)是:檢查 fd 的當(dāng)前狀態(tài)是否匹配 epoll 實(shí)例對(duì)其關(guān)心的事件。如果匹配,就將這個(gè) fd 添加到 epoll 實(shí)例的 就緒列表 中。如果此時(shí)有進(jìn)程正在 epoll_wait 中睡眠等待該 epoll 實(shí)例,則喚醒該進(jìn)程。
+-----------------+      epoll_ctl(ADD fd)      +----------------------------+
| epoll instance  | <-------------------------- | Application Process        |
| - Interest List |                             +----------------------------+
| - Ready List    |                                          |
| - Wait Queue    |      Registers Callback                  | system call
+-----------------+---------------------------> +----------------------------+
                                                | Kernel                     |
                                                | +------------------------+ |
                                                | | struct file (for fd)   | |
                                                | | - f_op                 | |
                                                | | - private_data (socket)| |
                                                | +---------+--------------+ |
                                                |           |                |
                                                |           v                |
                                                | +---------+-------------+  |
                                                | | Wait Queue (e.g., rx) |  |
                                                | | - epoll callback entry|  |
                                                | +-----------------------+  |
                                                +----------------------------+

3.**epoll_wait()**:等待 epoll 實(shí)例監(jiān)視的文件描述符上發(fā)生事件。

  • 調(diào)用 epoll_wait 時(shí),內(nèi)核首先檢查 epoll 實(shí)例的 就緒列表。
  • 如果就緒列表 非空,內(nèi)核直接將就緒列表中的文件描述符信息拷貝到用戶空間提供的緩沖區(qū),并立即返回就緒的文件描述符數(shù)量。
  • 如果就緒列表 為空,進(jìn)程將 睡眠,等待在 epoll 實(shí)例自身的等待隊(duì)列上。
  • 當(dāng)某個(gè)被監(jiān)視的文件描述符 fd 發(fā)生事件(如數(shù)據(jù)到達(dá)),其關(guān)聯(lián)的等待隊(duì)列被喚醒,觸發(fā)之前注冊(cè)的 epoll 回調(diào)。
  • 回調(diào)函數(shù)將 fd 加入 epoll 實(shí)例的就緒列表,并喚醒在 epoll_wait 中等待該實(shí)例的進(jìn)程。
  • epoll_wait 被喚醒后,發(fā)現(xiàn)就緒列表非空,于是收集就緒信息并返回給用戶。

epoll 的優(yōu)勢(shì)

  • 高效 :epoll_wait 的復(fù)雜度通常是 O(1),因?yàn)樗恍枰獧z查就緒列表,而不需要像 select/poll 那樣遍歷所有監(jiān)視的文件描述符。文件描述符狀態(tài)的檢查和就緒列表的填充是由事件發(fā)生時(shí)的回調(diào)機(jī)制異步完成的。
  • 回調(diào)機(jī)制 :避免了 select/poll 在每次調(diào)用和喚醒時(shí)都需要重復(fù)遍歷所有文件描述符的問(wèn)題。文件描述符和 epoll 實(shí)例的關(guān)聯(lián)(包括回調(diào)注冊(cè))只需要在 epoll_ctl 時(shí)建立一次。
  • 邊緣觸發(fā) (Edge Triggered, ET) 與水平觸發(fā) (Level Triggered, LT) :epoll 支持這兩種模式。LT 模式(默認(rèn))行為類似于 poll,只要條件滿足(如緩沖區(qū)非空),epoll_wait 就會(huì)一直報(bào)告就緒。ET 模式下,只有當(dāng)狀態(tài)從未就緒變?yōu)榫途w時(shí),epoll_wait 才會(huì)報(bào)告一次,之后即使條件仍然滿足也不會(huì)再報(bào)告,直到應(yīng)用程序處理了該事件(例如,讀取了所有數(shù)據(jù)使得緩沖區(qū)變空,然后又有新數(shù)據(jù)到達(dá))。ET 模式通常能提供更高的性能,但編程也更復(fù)雜,需要確保每次事件通知后都將數(shù)據(jù)處理完畢。

LT 模式(默認(rèn))

#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int epfd = epoll_create1(0);
    int sockfd = /* 假設(shè)已創(chuàng)建并綁定監(jiān)聽(tīng)的套接字 */;

    struct epoll_event event;
    event.events = EPOLLIN; // LT 模式,默認(rèn)
    event.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

    struct epoll_event events[10];
    while (1) {
        int nfds = epoll_wait(epfd, events, 10, -1);
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == sockfd) {
                char buf[1024];
                ssize_t n = read(sockfd, buf, sizeof(buf));
                if (n > 0) {
                    printf("Read %zd bytes\n", n);
                    // 可只讀部分?jǐn)?shù)據(jù),下次仍會(huì)觸發(fā)
                } else if (n == 0) {
                    printf("Connection closed\n");
                }
            }
        }
    }
}

ET 模式

#include <sys/epoll.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

int main() {
    int epfd = epoll_create1(0);
    int sockfd = /* 假設(shè)已創(chuàng)建并綁定監(jiān)聽(tīng)的套接字 */;

    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET; // ET 模式
    event.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

    struct epoll_event events[10];
    while (1) {
        int nfds = epoll_wait(epfd, events, 10, -1);
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == sockfd) {
                while (1) { // 必須一次性讀完
                    char buf[1024];
                    ssize_t n = read(sockfd, buf, sizeof(buf));
                    if (n > 0) {
                        printf("Read %zd bytes\n", n);
                    } else if (n == 0) {
                        printf("Connection closed\n");
                        break;
                    } else {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            printf("Buffer drained\n");
                            break;
                        }
                    }
                }
            }
        }
    }
}

連接套接字(socket, bind, listen, accept)與就緒狀態(tài)

現(xiàn)在我們將這些概念與服務(wù)器套接字的工作流程聯(lián)系起來(lái):

  • socket() : 創(chuàng)建一個(gè)套接字文件描述符 sockfd。此時(shí)它通常既不可讀也不可寫。
  • bind() : 將 sockfd 綁定到一個(gè)本地地址和端口。這本身通常不改變其可讀寫狀態(tài)。
  • listen() : 將 sockfd 標(biāo)記為監(jiān)聽(tīng)套接字,并創(chuàng)建兩個(gè)隊(duì)列(SYN 隊(duì)列和 Accept 隊(duì)列)。此時(shí) sockfd 仍不可直接讀寫數(shù)據(jù)。

何時(shí)監(jiān)聽(tīng)套接字 sockfd 變?yōu)椤翱勺x”?

當(dāng)一個(gè)客戶端連接請(qǐng)求完成 TCP 三次握手后,內(nèi)核會(huì)創(chuàng)建一個(gè)代表這個(gè)新連接的 已完成連接 (established connection),并將其放入與監(jiān)聽(tīng)套接字 sockfd 關(guān)聯(lián)的 Accept 隊(duì)列 中。此時(shí),對(duì)于 select/poll/epoll 來(lái)說(shuō),監(jiān)聽(tīng)套接字 sockfd 就被認(rèn)為是 可讀 的。調(diào)用 accept(sockfd, ...) 將會(huì)從 Accept 隊(duì)列中取出一個(gè)已完成連接,并返回一個(gè) 新的 文件描述符 connfd,這個(gè) connfd 才代表了與客戶端的實(shí)際通信通道。如果 Accept 隊(duì)列為空,則監(jiān)聽(tīng)套接字 sockfd 不可讀。

  • accept(): 從監(jiān)聽(tīng)套接字的 Accept 隊(duì)列中取出一個(gè)已完成的連接,返回一個(gè)新的已連接套接字 connfd。

何時(shí)已連接套接字 connfd 變?yōu)椤翱勺x”?

當(dāng)內(nèi)核的網(wǎng)絡(luò)協(xié)議棧收到屬于 connfd 這個(gè)連接的數(shù)據(jù),并將數(shù)據(jù)放入其 接收緩沖區(qū) 后,connfd 就變?yōu)榭勺x。此時(shí),網(wǎng)絡(luò)棧會(huì)喚醒在該套接字接收緩沖區(qū)等待隊(duì)列上的進(jìn)程(包括通過(guò) epoll 注冊(cè)的回調(diào))。

何時(shí)已連接套接字 connfd 變?yōu)椤翱蓪憽保?/p>

當(dāng) connfd 的 發(fā)送緩沖區(qū) 有足夠的可用空間來(lái)容納更多待發(fā)送的數(shù)據(jù)時(shí),connfd 就變?yōu)榭蓪憽.?dāng)內(nèi)核成功將發(fā)送緩沖區(qū)中的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò),釋放了空間后,會(huì)喚醒在該套接字發(fā)送緩沖區(qū)等待隊(duì)列上的進(jìn)程(包括 epoll 回調(diào))。初始狀態(tài)下,新創(chuàng)建的 connfd 通常是可寫的。

異常狀態(tài) :通常指帶外數(shù)據(jù)到達(dá),或者發(fā)生某些錯(cuò)誤(如連接被對(duì)方重置 RST)。

總結(jié)

Linux 內(nèi)核通過(guò)為每個(gè)可能阻塞的 I/O 資源(如套接字緩沖區(qū))維護(hù) 等待隊(duì)列 來(lái)跟蹤哪些進(jìn)程在等待事件。當(dāng)事件發(fā)生時(shí)(數(shù)據(jù)到達(dá)、緩沖區(qū)變空),內(nèi)核代碼(如網(wǎng)絡(luò)協(xié)議棧)會(huì) 喚醒 相應(yīng)等待隊(duì)列上的進(jìn)程。

  • select 和 poll 在每次調(diào)用時(shí),都需要遍歷所有被監(jiān)視的文件描述符,檢查它們的當(dāng)前狀態(tài),并將進(jìn)程注冊(cè)到相關(guān)的等待隊(duì)列上。喚醒后還需要再次遍歷以確定哪些就緒。
  • epoll 通過(guò) epoll_ctl 預(yù)先在文件描述符的等待隊(duì)列上注冊(cè) 回調(diào)函數(shù) 。當(dāng)事件發(fā)生并喚醒等待隊(duì)列時(shí),回調(diào)函數(shù)被觸發(fā),它負(fù)責(zé)將就緒的文件描述符添加到 epoll 實(shí)例的 就緒列表 中,并喚醒等待在 epoll_wait 上的進(jìn)程。epoll_wait 只需檢查這個(gè)就緒列表即可,大大提高了效率。

理解等待隊(duì)列和喚醒機(jī)制,以及 epoll 基于回調(diào)的事件驅(qū)動(dòng)模型,是掌握 Linux 下高性能網(wǎng)絡(luò)編程和 I/O 多路復(fù)用技術(shù)的關(guān)鍵。

總結(jié)

  • 可讀

監(jiān)聽(tīng)套接字:Accept 隊(duì)列非空(有新連接)。

已連接套接字:接收緩沖區(qū)有數(shù)據(jù)。

內(nèi)核通過(guò)網(wǎng)絡(luò)協(xié)議棧監(jiān)控緩沖區(qū)狀態(tài)。

  • 可寫

發(fā)送緩沖區(qū)有足夠空間(低于高水位)。

協(xié)議棧監(jiān)控發(fā)送進(jìn)度并更新空間。

如何“立即”通知應(yīng)用程序?

  • 等待隊(duì)列機(jī)制: 資源狀態(tài)變化時(shí),協(xié)議棧調(diào)用 wake_up() 喚醒等待隊(duì)列上的進(jìn)程。
  • select/poll: 通過(guò) poll_wait() 注冊(cè)等待,事件發(fā)生時(shí)喚醒并重新檢查狀態(tài)。
  • epoll: 通過(guò)回調(diào)函數(shù)異步將就緒文件描述符加入就緒列表,epoll_wait 直接返回。
責(zé)任編輯:武曉燕 來(lái)源: Pipeliu
相關(guān)推薦

2023-05-08 00:06:45

Go語(yǔ)言機(jī)制

2021-03-17 16:53:51

IO多路

2021-02-10 08:09:48

Netty網(wǎng)絡(luò)多路復(fù)用

2009-06-29 18:09:12

多路復(fù)用Oracle

2021-03-24 08:03:38

NettyJava NIO網(wǎng)絡(luò)技術(shù)

2020-10-13 07:51:03

五種IO模型

2024-12-30 00:00:05

2021-06-09 19:25:13

IODubbo

2023-11-08 09:22:14

I/ORedis阻塞

2019-12-23 14:53:26

IO復(fù)用

2011-12-08 10:51:25

JavaNIO

2023-01-09 10:04:47

IO多路復(fù)用模型

2023-08-07 08:52:03

Java多路復(fù)用機(jī)制

2023-12-06 07:16:31

Go語(yǔ)言語(yǔ)句

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2020-10-14 09:11:44

IO 多路復(fù)用實(shí)現(xiàn)機(jī)

2022-09-12 06:33:15

Select多路復(fù)用

2022-08-26 00:21:44

IO模型線程

2024-05-15 16:41:57

進(jìn)程IO文件

2024-08-08 14:57:32

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

亚洲91精品| 亚洲精品无码国产| 国产又粗又猛又爽又黄的视频四季| 欧美日韩视频免费观看| 国产精品国产自产拍在线| 99蜜桃在线观看免费视频网站| 精品久久免费视频| av亚洲免费| 精品精品国产高清一毛片一天堂| 欧美二区在线视频| 麻豆av在线免费看| 91原创在线视频| 成人中心免费视频| 国产亚洲欧美在线精品| 亚洲最新av| 亚洲男人的天堂在线播放| 日韩不卡的av| 午夜无码国产理论在线| 亚洲一区在线观看网站| 神马影院我不卡| 日本高清视频www| 久久精品国产亚洲一区二区三区| 97超级碰在线看视频免费在线看| 三级黄色免费观看| 国产亚洲第一伦理第一区| 欧美一区二区三区免费在线看| 99re在线视频免费观看| 暖暖在线中文免费日本| 国产精品福利一区二区三区| 欧美日韩一区二区三区免费| 成人免费视频国产免费麻豆| 精品无人区卡一卡二卡三乱码免费卡| 日本一本a高清免费不卡| 久久久久免费看| 亚洲国产精品日韩专区av有中文| 国产性猛交xxxx免费看久久| 国产精品无码一区二区三区免费 | 国产欧美婷婷中文| 欧美 日韩 精品| 国产欧美日韩一级| 久久久久久久久爱| 欧美三级 欧美一级| 婷婷久久一区| 久久精品视频在线播放| 国产午夜精品久久久久久久久| 综合干狼人综合首页| 亚洲国产欧美自拍| 青青草视频网站| 成人黄色av网址| 日韩免费观看高清完整版 | 四虎精品在线观看| 欧美三级欧美一级| 在线免费av播放| 粉嫩av一区二区三区四区五区 | 久久精品香蕉视频| 乱人伦视频在线| 天天综合天天综合色| 免费av观看网址| 伊伊综合在线| 91国产免费看| 亚洲国产高清av| 亚洲欧洲二区| 日韩免费一区二区| 欧美成人三级伦在线观看| 日韩黄色网络| 视频一区欧美精品| 亚洲一二三级电影| 日韩网站在线免费观看| free性护士videos欧美| 岛国av一区二区| 黄在线观看网站| 亚洲不卡系列| 在线成人免费视频| 国偷自产av一区二区三区麻豆| 亚洲乱码一区| 日韩精品在线私人| 91在线无精精品白丝| 欧美3p在线观看| 欧美猛交免费看| 国产成人愉拍精品久久| 日韩极品在线观看| 亚洲在线一区二区| 日本精品一二区| 国产区在线观看成人精品| 亚洲精品在线免费| 欧美日韩经典丝袜| 欧美日韩免费在线观看| 91高清国产视频| 2021年精品国产福利在线| 国产视频精品免费播放| 激情高潮到大叫狂喷水| 欧美日本二区| 国产成人a亚洲精品| 国产婷婷在线视频| 成人在线一区二区三区| 欧美午夜精品久久久久久蜜| 黄黄的网站在线观看| 午夜欧美在线一二页| 亚洲一二三区av| 91亚洲精品视频在线观看| 亚洲欧美日韩一区二区三区在线| 中文字幕在线观看2018| 日韩午夜激情| 亚洲va欧美va在线观看| 欧美视频综合| 亚洲精品国产一区二区三区四区在线| 青青草原成人网| 久久gogo国模啪啪裸体| 亚洲欧美在线看| 欧美成人免费看| 日韩成人精品在线| 国产精品免费一区二区三区在线观看| 国产成人天天5g影院在线观看| 夜夜揉揉日日人人青青一国产精品| www国产黄色| 中文字幕久久精品一区二区| 色偷偷偷综合中文字幕;dd| 国产精品一区二区三区四| 国产福利精品一区二区| 亚洲一二三区精品| 中日韩脚交footjobhd| 日韩三级在线免费观看| 久久精品三级视频| 免费一区视频| 精品国产一区二区三| av免费在线观看网站| 欧美日韩五月天| 天堂久久精品忘忧草| 一区二区精品| 国产精品视频入口| 欧美性video| 日韩免费观看高清完整版在线观看| 国产调教在线观看| 视频一区在线播放| 另类视频在线观看+1080p| 日韩av毛片| 日韩三级.com| 欧美黑人猛猛猛| 国产一区二区在线视频| 一个色的综合| 欧美日韩视频免费看| 亚洲人成电影在线播放| 国产伦精品一区二区三区视频网站| jiyouzz国产精品久久| 免费视频爱爱太爽了| 视频一区在线| 欧美日韩高清在线观看| 精品国自产拍在线观看| 亚洲麻豆国产自偷在线| 无套白嫩进入乌克兰美女| 天天影视天天精品| 国产在线视频欧美| 国产视频一区二区| 欧美一区二区在线免费观看| 精品人妻伦九区久久aaa片| 久久99国产精品久久| 自拍另类欧美| 激情视频亚洲| 高清欧美性猛交| 欧洲成人一区二区三区| 偷窥国产亚洲免费视频| 人妻大战黑人白浆狂泄| 老司机亚洲精品| 亚洲一区三区| 欧美视频精品全部免费观看| 色综合天天综合网国产成人网| 亚洲第九十九页| 欧美日韩视频在线| 熟女俱乐部一区二区| 日韩电影免费在线观看网站| 椎名由奈jux491在线播放| 无码国模国产在线观看| 午夜精品一区二区三区av| 免费在线视频一级不卡| 欧美视频一区二区三区四区| sm捆绑调教视频| 国产成人日日夜夜| 国产日产欧美视频| 久久国产电影| 国产精品推荐精品| 成人免费毛片嘿嘿连载视频…| 综合国产在线观看| 懂色av蜜臀av粉嫩av分享吧| 色婷婷综合五月| 三上悠亚在线观看视频| 成人免费视频app| 99久久国产宗和精品1上映| 日韩免费av| 福利精品视频| 日本一区二区电影| 久久99久久亚洲国产| 日韩欧美电影在线观看| 欧美精选一区二区| 韩国av免费观看| 国产精品青草综合久久久久99| 国产情侣久久久久aⅴ免费| 久久都是精品| 性高湖久久久久久久久aaaaa| 你懂的一区二区三区| 亚洲一区二区三区在线视频 | 第九色区aⅴ天堂久久香| 91观看网站| 97精品国产综合久久久动漫日韩| 欧美国产日产韩国视频| 川上优的av在线一区二区| 日韩亚洲欧美综合| 中文字幕 日韩有码| 偷拍亚洲欧洲综合| 九九精品在线观看视频| 国产精品毛片无遮挡高清| chinese麻豆新拍video| 国产在线视视频有精品| 亚洲精品一二三四五区| 国产日韩一区二区三区在线播放 | 精品久久久久久一区二区里番| 欧美天堂一区| 国产91免费观看| a在线视频v视频| 精品中文字幕在线观看| 在线激情免费视频| 亚洲人成电影在线播放| 色婷婷视频在线| 日韩视频一区二区在线观看| 在线观看视频二区| 色婷婷综合久久久久中文一区二区| 破处女黄色一级片| 中文字幕在线一区| av手机在线播放| 久久久久久久久久久黄色| 无码成人精品区在线观看| 国产麻豆精品在线观看| 欧美大尺度做爰床戏| 日韩精品一卡二卡三卡四卡无卡| 欧美成人一区二区在线观看| 亚洲国产精品第一区二区| 日韩中文字幕在线不卡| 亚洲女同另类| 好吊色这里只有精品| 999精品在线| 一道精品一区二区三区| 久久精品欧美一区| 中文一区一区三区免费| 97在线精品| 亚洲香蕉成视频在线观看| 无码人妻丰满熟妇区毛片| 精品1区2区3区4区| 欧美a级免费视频| 伊人久久成人| 777精品久无码人妻蜜桃| 国产精品呻吟| 99福利在线观看| 视频精品一区二区| 91日韩视频在线观看| 奇米在线7777在线精品| gai在线观看免费高清| 免费观看成人av| 欧美精品 - 色网| 国产精品911| 久久精品aⅴ无码中文字字幕重口| 国产成人精品网址| 给我免费观看片在线电影的| 久久影视一区二区| 91视频免费在观看| 最新日韩av在线| 久久久久成人片免费观看蜜芽| 亚瑟在线精品视频| 波多野结衣在线观看一区| 欧美日韩亚洲综合| www.久久久久久| 亚洲国产精品人人爽夜夜爽| 九色在线免费| 久久色精品视频| 成人福利电影| 国产不卡视频在线| 高清不卡一区| 久久66热这里只有精品| 日韩精品免费| 国产青草视频在线观看| 可以看av的网站久久看| 亚洲网中文字幕| 99久久99久久综合| 老司机福利在线观看| 亚洲欧美日韩在线| 五月天综合激情| 欧美军同video69gay| 农村少妇久久久久久久| 国产午夜一区二区| 成人av影院在线观看| 国产精品欧美一区二区三区奶水| 国产欧美日韩电影| 日本成人黄色免费看| 欧美在线网站| 黄色一级二级三级| 国产不卡一区视频| 免费成人深夜天涯网站| 亚洲国产aⅴ天堂久久| 自拍偷拍福利视频| 亚洲激情免费观看| 麻豆影院在线| 日本a级片电影一区二区| 精品中文字幕一区二区三区四区| 欧美一区二区福利| 亚洲视频一二| 五月婷婷之婷婷| www国产精品av| 少妇久久久久久被弄高潮| 日本高清不卡在线观看| 蜜桃91麻豆精品一二三区| 在线视频日本亚洲性| 2021天堂中文幕一二区在线观| 国产日韩视频在线观看| 综合国产视频| 黄色大片在线免费看| 精品一区二区三区的国产在线播放| 国产制服丝袜在线| 一区二区三区在线观看国产| 一级片在线免费观看视频| 精品视频—区二区三区免费| 欧美另类tv| 91在线视频导航| 日韩欧美视频| 精品久久久久久久无码| 99久久综合国产精品| 久久免费视频精品| 欧美一区日本一区韩国一区| 1769视频在线播放免费观看| 国产成人欧美在线观看| 神马久久av| 国产一级爱c视频| 国产a视频精品免费观看| 国产高潮流白浆| 在线播放亚洲一区| 日本美女在线中文版| 国产精品美女免费| 国产免费久久| 99久久国产宗和精品1上映| 久久日韩粉嫩一区二区三区| 亚洲日本视频在线观看| 日韩经典第一页| 91色在线看| 精品无码久久久久久久动漫| 亚洲区一区二| 中文字幕天堂网| 亚洲国产精品久久不卡毛片| 黄频网站在线观看| 午夜精品福利电影| 色婷婷狠狠五月综合天色拍 | 18岁成人毛片| 日韩一区二区视频在线观看| www久久日com| 成人av免费电影| 一本色道久久综合一区| 日本黄色网址大全| 欧美中文字幕一二三区视频| yourporn在线观看中文站| 国产精品日日摸夜夜添夜夜av| 成人高清电影网站| www.cao超碰| 一区二区三区在线免费| 天天干天天色天天| 国产不卡精品视男人的天堂 | 97超碰在线人人| www.一区二区| 免费看污视频的网站| 三级精品视频久久久久| 欧美一级片网址| 男人插女人视频在线观看| 91视频精品在这里| 欧美日韩在线视频播放| 久久综合久久美利坚合众国| 国产精品国产三级在线观看| 僵尸世界大战2 在线播放| 久久综合久久综合久久| 免费黄色一级大片| 久久艹在线视频| 美女一区2区| 中文字幕一区二区三区四区在线视频| 国产精品不卡一区二区三区| 亚洲av综合色区无码一二三区| 91av中文字幕| 久久美女精品| 性久久久久久久久久久| 欧美午夜宅男影院| 天天色天天射天天综合网| 免费99视频| 国产精品正在播放| 日日夜夜操视频| 久久99精品久久久久久琪琪 | 在线观看日韩www视频免费| 精品国产第一国产综合精品| 一区二区传媒有限公司| 国产精品国产a| 青青草在线免费视频| 91中文在线视频| 久久久久国内| 久久久精品国产sm调教| 中国日韩欧美久久久久久久久| 亚洲2区在线| 特级丰满少妇一级| 欧美视频13p|