偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

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

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

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

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

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

I/O 多路復(fù)用 (I/O Multiplexing) 技術(shù)應(yīng)運而生,它允許單個進程或線程監(jiān)視多個 文件描述符 (file descriptor),并在其中任何一個或多個變得“就緒”(例如,可讀或可寫)時得到通知,從而可以在單個執(zhí)行流中處理多個 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é)議棧以及一種核心機制: 等待隊列 (wait queue) 。

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

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

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

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

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

等待隊列:事件通知的核心

操作系統(tǒng)需要一種機制,讓某個進程在等待特定事件(例如,數(shù)據(jù)到達套接字、套接字發(fā)送緩沖區(qū)有可用空間)發(fā)生時能夠暫停執(zhí)行(睡眠),并在事件發(fā)生后被喚醒。這就是 等待隊列 (wait queue) 機制 (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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

可讀性

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

可寫性

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

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

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

這個“生產(chǎn)者-消費者”模型(網(wǎng)絡(luò)棧是數(shù)據(jù)的生產(chǎn)者/消費者,應(yīng)用程序是數(shù)據(jù)的消費者/生產(chǎn)者)通過等待隊列和喚醒機制實現(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)核操作 :

某個被監(jiān)視的文件描述符相關(guān)的等待隊列被喚醒(例如,因為數(shù)據(jù)到達或緩沖區(qū)變空)。

超時時間到達。

收到一個信號。

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

解釋一下 poll_wait() :它建立了一種關(guān)聯(lián):當(dāng)?shù)却犃斜粏拘褧r,當(dāng)前執(zhí)行 select 或 poll 的進程也會被喚醒。

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

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

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

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

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

select 和 poll 的主要缺點

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

epoll:更高效的事件通知

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

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

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

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

這個 epoll 實例本身也由一個文件描述符表示。

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

  • 關(guān)鍵操作:當(dāng)使用 EPOLL_CTL_ADD 添加一個文件描述符 fd 時,內(nèi)核不僅將其加入 epoll 實例的監(jiān)視列表,更重要的是,它會在與 fd 相關(guān)的 等待隊列 上注冊一個 回調(diào)函數(shù)。
  • 這個回調(diào)函數(shù)非常特殊:當(dāng) fd 對應(yīng)的資源(如套接字緩沖區(qū))狀態(tài)改變,導(dǎo)致其關(guān)聯(lián)的等待隊列被喚醒時,這個注冊的回調(diào)函數(shù)會被執(zhí)行。
  • 回調(diào)函數(shù)的任務(wù)是:檢查 fd 的當(dāng)前狀態(tài)是否匹配 epoll 實例對其關(guān)心的事件。如果匹配,就將這個 fd 添加到 epoll 實例的 就緒列表 中。如果此時有進程正在 epoll_wait 中睡眠等待該 epoll 實例,則喚醒該進程。
+-----------------+      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 實例監(jiān)視的文件描述符上發(fā)生事件。

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

epoll 的優(yōu)勢

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

    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ù),下次仍會觸發(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)聽的套接字 */;

    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)系起來:

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

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

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

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

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

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

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

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

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

總結(jié)

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

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

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

總結(jié)

  • 可讀

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

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

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

  • 可寫

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

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

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

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

2023-05-08 00:06:45

Go語言機制

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

2020-10-13 07:51:03

五種IO模型

2021-03-24 08:03:38

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

2021-06-09 19:25:13

IODubbo

2024-12-30 00:00:05

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ù)用機制

2023-12-06 07:16:31

Go語言語句

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2020-10-14 09:11:44

IO 多路復(fù)用實現(xiàn)機

2022-09-12 06:33:15

Select多路復(fù)用

2022-08-26 00:21:44

IO模型線程

2024-05-15 16:41:57

進程IO文件

2024-08-08 14:57:32

點贊
收藏

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