Linux下I O復用模型

2021-10-07 12:40:33 字數 4237 閱讀 2891

以下內容引述至《linux/unix系統程式設計手冊》

常見的程式使用的i/o模型都是單個程序每次只在乙個檔案描述符上執行i/o操作,每次i/o系統呼叫會阻塞直到完成資料傳輸。

對於許多應用來說,傳統的阻塞式i/o模型已經足夠了,但這不代表所有的應用都能得到滿足。

非阻塞式i/o可以讓我們周期性地檢查(「輪詢」)某個檔案描述符上是否可執行i/o操作。

由於非阻塞式i/o和多進(線)程都有各自的侷限性,下列備選方案往往更可取:

實際上i/o多路復用、訊號驅動i/o以及epoll都是用來實現同一目標的技術——同時檢查多個檔案描述符,看它們是否準備好了執行i/o操作。

檔案描述符就緒狀態的轉化是通過一些i/o事件來觸發的,比如輸入資料到達,套接字連線建立完成,或者是之前滿載的套接字傳送緩衝區在tcp將佇列中的資料傳送到對端之後有了剩餘空間。

技術選型

我們先區分兩種檔案描述符準備就緒的通知模式:

使用水平觸發和邊緣觸發通知模型

i/o模式

水平觸發

邊緣觸發

select(), poll()

*訊號驅動

*epoll**

當採用水平觸發通知時,我們可以在任意時刻檢查檔案描述符的就緒狀態。這表示當我們確定了檔案描述符處於就緒狀態時(比如存在有輸入資料),就可以對其執行一些i/o操作,然後重複檢查we年描述符,看看是否仍然處於就緒態(比如還有更多的輸入資料),此時我們就能執行更多的i/o,以此類推。

於此相反的是,當我們採用邊緣觸發時,只有當i/o事件發生時我們才會收到通知,在另乙個i/o事件到來前我不會收到任何新的通知。另外當檔案描述符收到i/o事件通知時,通常我們並不知道要處理多少i/o。因此,採用邊緣觸發通知的程式通常要按照如下規則來設計

i/o多路復用允許我們同時檢查多個檔案描述符,看其中任意乙個是否可執行i/o操作。我們可以採用兩個功能幾乎相同的系統呼叫來執行i/o多路復用操作。

系統呼叫select()會一直阻塞,直到乙個或多個檔案描述符集合稱為就緒態

#include

#include

intselect

(int ndfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,

struct timeval * timeout)

;

fd_set 檔案描述符集合

引數readfsd, writefds 以及 exceptfds 都是指向檔案描述符集合的指標,所指向的資料型別是fd_set

在linux上,乙個異常情況只會在下面兩種情況下發生

通常,資料型別fd_set以位掩碼的形式來實現

#include

// 將fdset所指向的集合初始化為空

void

fd_zero

(fd_set *fdset)

;// 將檔案描述符fd新增到由fdset所指向的集合中

void

fd_set

(int fd, fd_set *fdset)

;// 將檔案描述符fd從fdset所指向的結婚中移除

void

fd_clr

(int fd, fd_set *fdset)

;// 如果檔案描述符fd是fdset所指向的集合中的成員,fd_isset()返回true

intfd_isset

(int fd, fd_set *fdset)

;

檔案描述符集合由乙個最大容量限制,由常量fs_setsize來決定。在linux上,該常量的值為1024

select()的返回值

-1 表示錯誤發生

0 表示超時

正整數表示有1個或多個檔案描述符已達到就緒態,返回值表示處於就緒態的檔案描述符個數。

系統呼叫poll()執行的任務同select()很相似。兩者間主要的區別是我們如何指定待檢查的檔案描述符。

#include

intpoll

(strcut pollfd[

], nfds_t nfds,

int timeout)

;

引數fds列出了我們需要poll()來檢查的檔案描述符。定義如下

strcut pollfd 

;

引數nfds指定了陣列fds中元素的個數。資料型別nfds_t 實際為無符號整型

pollfd結構體中的events和revents欄位都是位掩碼。呼叫者初始化events來指定需要描述符fd做檢查的事件。當poll()返回時,revents被設定以此來表示該檔案描述符上實際發生的事件。

返回值同select()函式,但是乙個檔案描述符只統計一次,select同乙個檔案描述符統計多次。

如果對i/o函式的呼叫不會阻塞,而不論該函式是否能夠實際傳輸資料,此時檔案描述符(未指定o_nonblock標誌)被認為是就緒的。

select()和poll()只會告訴我們i/o操作是否阻塞,而不是高數我們到底能夠成功傳輸資料。

普通檔案

代表普通檔案的檔案描述符總是被select()標記為可讀和可寫。對於poll()來說,則會在revents欄位中返回pollin和pollout標誌

實現細節

在linux核心層面,select() 和 poll() 都使用了相同的核心poll例程集合。這些poll例程有別於系統呼叫poll()本身。每個例程都返回有關單個檔案描述符就緒的資訊。就緒資訊以位掩碼表示,其值同poll()系統呼叫中返回的revents欄位中的位元值相關

為了實現select(),我們使用一組巨集將核心poll例程返回的資訊轉化為由select()返回的與之對應的事件型別

#define pollin_set (pollrdnorm | pollrdband | pollin | pollhup | pollerr)

#define pollout_set (pollwrband | pollwrnorm | pollout | pollerr)

#define pollex_set (pollpri)

區別 效能

當滿足如下兩條任意一條時,效能接近

存在的問題

在i/o多路復用中,程序時通過系統呼叫(select()或poll())來檢查檔案描述符上是否可以執行i/o操作。而在訊號驅動i/o中,當檔案描述符上可執行i/o操作時,程序請求核心為自己傳送乙個訊號。之後程序就可以執行其他的任務直到i/o就緒為止。

過程如下:

為核心傳送的通知訊號安裝乙個訊號處理例程,預設情況下,這個通知訊號為sigio

設定檔案描述符的屬主,也就是當檔案描述符上可執行i/o時會收到通知訊號的程序或程序組。通常我們讓呼叫程序稱為屬主。設定屬主可通過fcntl()的f_setown操作來完成

通過設定o_nonblock標誌使能非阻塞i/o

通過開啟o_async標誌使能訊號驅動i/o

呼叫程序現在可以執行其他的任務了,當i/o操作就緒時,核心為程序傳送乙個訊號,然後呼叫訊號處理例程

訊號驅動i/o提供的是邊緣觸發通知,這表示一旦程序被通知i/o就緒,就應該進口嫩更多地執行i/o

想要全部利用訊號驅動i/o的優點,可以通過下面兩個步驟

同i/o多路復用和訊號驅動i/o一樣,linux的epoll api可以檢查多個檔案描述符上的i/o就緒狀態。epoll api的主要優點如下:

效能表現上,epoll同訊號驅動i/o相似,但是epoll有一些勝過訊號驅動i/o的優點

epoll api的核心資料結構稱為epoll例項,它和乙個開啟的檔案描述符相關聯。這個檔案描述符不是用來做i/o操作的,相反,它是核心資料結構的控制代碼,這些核心資料結構實現了兩個目的

epoll api由以下3個系統呼叫組成

當我們通過epoll_create() 建立乙個epoll例項時,核心在記憶體中建立乙個新的i-node並開啟檔案描述,隨後在呼叫程序中為開啟的這個檔案描述分配乙個新的檔案描述符。同epoll例項的興趣列表相關聯的是開啟的檔案描述,而不是epoll 檔案描述符

epoll預設是水平觸發,表示epoll會告訴我們何時能在檔案描述符上以非阻塞的方式執行i/o操作。這同poll() 和 select() 所提供的通知型別相同

epoll api還可以通過邊緣觸發方式進行通知——也就是說,會告訴我們自從上一次epoll_wait()以來檔案描述符上是否已有i/o活動了。使用epoll的邊緣觸發通知在語義上類似於訊號驅動i/o,只是如果有多個i/o事件發生的話,epoll會將他們合併成一次單獨的通知,通過epoll_wait()返回,而在訊號驅動i/o中可能會產生多個訊號

I O復用和I O模型

由 unix網路程式設計卷1 總結而來。同時監視多個i o條件,在其中任意乙個就緒時通知程序,這樣的能力稱為i o復用。由select和poll函式支援,較新的還有posix中的pselect函式。linux中還有epoll i o復用應用場合 1 客戶同時處理多個描述符時,必須使用i o復用。2 ...

IO復用模型 epoll

參見 1.epoll模型簡介 epoll是linux多路服用io介面select poll的加強版,e對應的英文單詞就是enhancement,中文翻譯為增強,加強,提高,充實的意思。所以epoll模型會顯著提高程式在大量併發連線中只有少量活躍的情況下的系統cpu利用率。epoll把使用者關心的檔案...

IO模型 IO多路復用

用socket 一定會用到accept recv recvfrom這些方法 正常情況下 accept recv recvfrom都是阻塞的 如果setblocking false 整個程式就變成乙個非阻塞的程式了非阻塞的特點 沒有併發程式設計的機制 是乙個同步的程式 程式不會在某乙個連線的recv或...