精品欧美一区二区三区在线观看 _久久久久国色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)

免费看日韩毛片| 国产一线二线三线在线观看| 精品久久久中文字幕人妻| 欧美一区二区三区久久精品| 亚洲国产成人一区| 欧美日韩一区二区三区69堂| 在线观看电影av| 91网站最新网址| 91色p视频在线| 国产精品久久久久久久久久久久久久久久久| 久久99国内| 欧美一级片免费看| 欧美亚洲日本在线观看| 久久亚洲导航| 国产精品国产三级国产三级人妇| www.久久艹| 一区二区乱子伦在线播放| 国产一区二区三区四区老人| 亚洲视频免费一区| 成人午夜精品无码区| 丁香婷婷久久| 精品国产户外野外| 国产91porn| 高清福利在线观看| 99视频一区二区| 国产啪精品视频| 天天干天天色综合| 国产欧美一级| 久久久欧美精品| 国产真实乱在线更新| 国产精品片aa在线观看| 亚洲福利视频专区| 在线观看欧美一区二区| 成人午夜sm精品久久久久久久| 精品高清美女精品国产区| 欧美日韩中文字幕在线播放| 日本在线视频站| 国产午夜精品一区二区 | 日韩精品视频中文字幕| 欧美在线一区二区三区| 少妇人妻互换不带套| 妞干网免费在线视频| 亚洲国产视频直播| www.日本三级| 国产激情视频在线| **网站欧美大片在线观看| 日韩中文字幕一区| porn视频在线观看| 欧美国产日韩一二三区| 欧美精品国产精品久久久| 午夜在线视频免费| 99r国产精品| 精品一区二区不卡| 神马久久精品| 久久综合九色综合久久久精品综合 | 99精品视频网站| 99青草视频在线播放视| 欧美精彩视频一区二区三区| 色一情一区二区三区四区| 久青青在线观看视频国产| 久久久久久综合| 日本免费高清一区二区| 9i精品一二三区| 国产精品高潮呻吟| 免费看污污视频| 欧美xxxx黑人又粗又长| 亚洲影视在线播放| 黄色国产一级视频| 国模冰冰炮一区二区| 在线免费观看一区| 日韩中文字幕a| 精品一区二区三区四区五区| 日韩三级精品电影久久久| 天堂va欧美va亚洲va老司机| 精品国产午夜肉伦伦影院| 日韩精品久久久久久久玫瑰园| 亚洲天堂网一区二区| 国产伦一区二区三区| 色婷婷久久av| 国产一级视频在线| 久久精品成人| 91在线色戒在线| 天堂中文在线官网| 中文在线一区二区| japanese在线播放| 亚洲涩涩在线| 717成人午夜免费福利电影| 中文字幕在线观看91| 免费短视频成人日韩| 日韩视频在线一区| 久久午夜免费视频| 久久99国产精品久久99| 电影午夜精品一区二区三区| 欧美套图亚洲一区| 中文字幕人成不卡一区| 久久综合九色综合88i| 国产精品无码久久久久| 精品福利视频一区二区三区| 亚洲午夜久久久久久久国产| 你懂的成人av| 国产精品成人品| 午夜精品久久久久久久99| 久久亚洲精精品中文字幕早川悠里| 亚洲精品9999| 九色porny丨入口在线| 6080午夜不卡| 亚洲天堂久久新| 国内激情久久| 国产日韩欧美夫妻视频在线观看| 欧美一级在线免费观看| 国产精品久久久爽爽爽麻豆色哟哟| 国产黄色片免费在线观看| 国语自产精品视频在线看抢先版结局 | 欧美国产激情一区二区三区蜜月| 青青草视频国产| 国模私拍国内精品国内av| 国产手机视频精品| 久久久精品99| 国产在线精品一区二区三区不卡| 麻豆蜜桃91| 国产三线在线| 91精品国产全国免费观看| 成人免费毛片糖心| 国产精品普通话对白| 99久久伊人精品影院| 91青青在线视频| 色88888久久久久久影院按摩| xxxxwww一片| 欧美91福利在线观看| 国产精品一区二区三区免费视频| 欧美视频综合| 福利视频一区二区| 亚洲av成人片色在线观看高潮 | 日韩成人精品一区二区| 55夜色66夜色国产精品视频| 免费观看国产精品| 亚洲精品一二三| 亚洲无在线观看| 91久久电影| 91精品国产综合久久久久久久久| 国产三级在线看| 在线精品视频免费播放| 亚洲国产天堂av| 久久先锋资源| 欧美一卡2卡3卡4卡无卡免费观看水多多| 草莓视频丝瓜在线观看丝瓜18| 日韩欧美久久一区| 久草资源在线视频| 国产白丝精品91爽爽久久| 小泽玛利亚av在线| 白嫩白嫩国产精品| 韩剧1988免费观看全集| 欧美性猛交 xxxx| 欧美日韩国产一区在线| 鲁大师私人影院在线观看| 亚洲乱码视频| 欧美日韩电影一区二区| 美女日韩欧美| 中文字幕欧美日韩精品| 中文字幕 日韩有码| 国产精品久久久久久久久动漫| 亚洲污视频在线观看| 91视频一区| 成人资源视频网站免费| 超碰在线最新网址| 亚洲欧美综合区自拍另类| 国产女主播喷水视频在线观看| 国产欧美一区视频| 欧美视频亚洲图片| 亚洲高清免费| 日韩av电影免费观看| 欧美一级免费| 久久久久这里只有精品| 欧美成人片在线| 欧美日本不卡视频| 免费一级全黄少妇性色生活片| 成a人片亚洲日本久久| 国产美女三级视频| 日韩专区精品| 国产精品一区二区三区在线| 在线毛片观看| 久久精品成人欧美大片古装| 亚洲欧美另类一区| 在线精品视频一区二区| 精品99在线观看| 久久久精品免费观看| 中文字幕剧情在线观看| 国产精品美女久久久| 在线码字幕一区| 日韩精品免费一区二区三区竹菊| 国产精品ⅴa在线观看h| 伊人影院在线视频| 亚洲人成在线免费观看| a级片在线视频| 色综合夜色一区| 男女羞羞免费视频| 国产三级精品三级| 香蕉在线观看视频| 奇米精品一区二区三区四区| 妺妺窝人体色www看人体| 女人av一区| 国产精品国产一区二区| 狠狠久久综合| 国产97人人超碰caoprom| av免费在线观看网站| 亚洲欧美999| 亚洲精品第五页| 欧美日韩国产美女| 亚洲图片在线视频| 亚洲影视资源网| 性色av无码久久一区二区三区| 久久伊人蜜桃av一区二区| 夜夜爽久久精品91| 青青草精品视频| 激情五月开心婷婷| 亚洲美女一区| 欧美中文字幕在线观看视频| 日韩在线欧美| 五月天亚洲综合情| 欧美**vk| 蜜桃成人在线| 老牛精品亚洲成av人片| 91嫩草国产在线观看| 色成人综合网| 国产欧美日韩最新| 欧美日韩在线精品一区二区三区激情综合| 97在线视频国产| 欧美videossex| 久久99青青精品免费观看| 免费黄网站在线| 在线精品国产欧美| 国产大学生校花援交在线播放| 亚洲精品国产精品国自产在线| 国产色片在线观看| 3d成人h动漫网站入口| 在线免费观看一区二区| 在线免费精品视频| 国产成人精品亚洲| 欧美午夜影院一区| 中文天堂在线资源| 欧美写真视频网站| 小泽玛利亚一区二区三区视频| 色综合中文字幕国产| 日韩美一区二区| 色婷婷久久99综合精品jk白丝| 黄色污污网站在线观看| 色综合视频在线观看| 中文字幕一区在线播放| 色婷婷激情久久| 亚洲高清视频免费观看| 在线看一区二区| 中文av免费观看| 欧美一区二区精品| www国产一区| 亚洲国产精彩中文乱码av在线播放| 亚洲AV午夜精品| 亚洲精品www| 美国成人毛片| 日韩在线免费观看视频| 国产成人无吗| 久久久久久国产精品| 日韩激情电影免费看| 国产97色在线|日韩| 久久99久久久精品欧美| 91久久久在线| 精品网站aaa| 日本一区美女| 91成人免费| 国产69精品久久久久久久| 免播放器亚洲| 日本黄色的视频| 懂色av一区二区三区免费观看 | 男女爽爽爽视频| 精品一区二区影视| 欧亚乱熟女一区二区在线| 久久九九久精品国产免费直播| 亚洲女人久久久| 午夜精品久久久久久不卡8050| 免费精品一区二区| 欧美一级理论片| 久久久久国产精品嫩草影院| 最近2019年手机中文字幕| 性欧美1819sex性高清大胸| 日韩精品丝袜美腿| 欧洲一区二区日韩在线视频观看免费| av伊人久久| 久久观看最新视频| 亚洲影视综合| 亚洲av无日韩毛片久久| av影院午夜一区| 国产在线观看免费视频软件| 亚洲亚洲人成综合网络| 成人一二三四区| 亚洲精品在线观| 日本中文字幕在线播放| 午夜精品一区二区三区在线视频| 色天使综合视频| 国产精品乱子乱xxxx| 久久综合国产| 免费黄色日本网站| 国产福利一区二区三区在线视频| mm131美女视频| 亚洲日本欧美天堂| 久久人人爽人人爽人人片av免费| 日韩美女视频在线| www.中文字幕久久久| 国内免费久久久久久久久久久| 欧美a视频在线| 久久综合给合久久狠狠色| 女生裸体视频一区二区三区| 国产911在线观看| 免费一级欧美片在线观看| 精品人妻无码中文字幕18禁| 亚洲国产电影在线观看| 成人免费视频毛片| 日韩欧美不卡在线观看视频| 国产福利小视频在线观看| 久久全国免费视频| 国产一区二区三区视频在线| 欧美一进一出视频| 亚洲视频播放| 人妖粗暴刺激videos呻吟| 成人欧美一区二区三区视频网页 | 制服 丝袜 综合 日韩 欧美| 亚洲自拍偷拍麻豆| 国产欧美久久久| 日韩视频免费看| 国产精品99| 欧洲国产精品| 亚洲欧美视频| 风间由美一二三区av片| 亚洲一区二区欧美激情| 国产激情无套内精对白视频| www.亚洲一区| 欧美大陆国产| 伊人久久av导航| 久久激五月天综合精品| 美女福利视频网| 欧美日韩国产免费一区二区| 尤物网在线观看| 国产精品亚洲自拍| 99热国内精品| 午夜xxxxx| 亚洲精品中文字幕在线观看| 国产免费叼嘿网站免费| 久久久999精品| 日韩精品视频在线看| 女同性恋一区二区| 国产乱理伦片在线观看夜一区| 中国毛片直接看| 日韩欧美一级二级三级久久久| 在线中文字幕-区二区三区四区| 亚洲一区美女视频在线观看免费| 亚洲国产精品成人| 中文字幕在线视频一区二区| 亚洲制服丝袜av| 天堂在线观看免费视频| 国产mv久久久| 色一区二区三区四区| 激情文学亚洲色图| 一区二区三区毛片| 天天干天天爽天天操| 欧美影院久久久| 久久国产成人午夜av影院宅| 亚欧美在线观看| 亚洲精品成人悠悠色影视| 亚洲产国偷v产偷v自拍涩爱| 97香蕉久久夜色精品国产| 女人丝袜激情亚洲| 午夜剧场在线免费观看| 亚洲精品国产一区二区三区四区在线| 亚洲成人久久精品| 91av免费观看91av精品在线| 国产探花一区| 欧美色图校园春色| 午夜精品福利一区二区三区蜜桃| 黄色在线视频观看网站| 91免费国产视频| 亚洲国产一区二区精品专区| av网在线播放| 日韩网站在线看片你懂的| 理论不卡电影大全神| 亚洲第一导航| 波多野结衣一区二区三区| 日韩国产成人在线| 欧美人成在线视频| 国产精品一区二区av交换| 日韩精品在线播放视频| 天天射综合影视| 精品国产丝袜高跟鞋| 看高清中日韩色视频| 久久se这里有精品| 日韩色图在线观看| 久久精品久久久久久| 欧美精品国产白浆久久久久| 午夜精品中文字幕| 午夜电影一区二区三区| 免费av在线| 久久这里精品国产99丫e6| 国产99久久久国产精品免费看|