標籤:
有些進程需要一種預先告知核心的能力,使得核心一旦發現進程指定的一個或多個I/O條件就緒(也就是說輸入已準備好被讀取,或者描述符已能承受更多的輸出),他就通知進程,這個能力稱為I/O複用
1.IO模型
5種基本I/O模型
阻塞式I/O非阻塞式I/OI/O複用(select和poll)訊號驅動式I/O(SIGIO)非同步I/O
一個輸入操作通常包括兩個不同的階段
(1)等待資料準備
(2)從核心向進程複製資料
對於一個套介面上的輸入操作,第一步一般是等待資料到達網路,當分組到達時,它被拷貝到核心中的某個緩衝區,第二步是將資料從核心緩衝區拷貝到應用緩衝區。
(1)阻塞式I/O
最流行的I/O模型是阻塞式I/O(blocking I/O)模型,預設情形下,所有的通訊端都是阻塞的
中進程在從調用recvfrom開始到它返回的整段時間內被阻塞,recvfrom成功返回後,應用進程開始資料處理
(2)非阻塞式I/O
進程把一個通訊端設定成非阻塞是在通知核心:當所請求的I/O操作非得把本進程投入睡眠才能完成,不能把本進程投入睡眠,而是返回一個錯誤。
前三次調用recvfrom時沒有資料可以返回,因此核心轉而立即返回一個EWOULDBLOCK錯誤,第四次調用recvfrom時已經有資料報準備好,它被複製到應用程式緩衝區,於是recvfrom成功返回
當一個應用程式使用了非阻塞模式的通訊端,它需要使用一個迴圈來不停的測試是否一個檔案描述符有資料可讀(稱做 polling,輪詢) 。應用程式不停的 polling核心來檢查是否 I/O操作已經就緒。這將是一個極浪費CPU資源的操作。這種模式使用中不是很普遍。
(3)IO複用模型
有了I/O複用,我們就可以調用select或poll,阻塞在這兩個系統調用中的某一個之上,而不是阻塞真正的I/O系統之上
我們阻塞於select調用,等待資料通訊端變為可讀,當select返回通訊端可讀這一條件時,調用recvfrom把所讀的資料複製到應用程式緩衝區內。另外使用select的優勢在於我們可以等待多個描述符就緒
(4)訊號驅動IO模型
可以用訊號讓核心在描述符就緒時發送SIGIO訊號通知我們
無論如何處理SIGIO訊號,這種模型的優勢在於等待資料報到達期間進程不被阻塞。主迴圈可以繼續執行,只要等待來自訊號處理函數的通知:既可以是資料已處理好被處理,也可以是資料已準備被讀取
(5)非同步IO模型
非同步 I/O 和 訊號驅動I/O的區別是:
a) 訊號驅動 I/O 模式下,核心在操作可以被操作的時候通知給我們的應用程式發送SIGIO 訊息。
b) 非同步 I/O 模式下,核心在所有的操作都已經被核心操作結束之後才會通知我們的應用程式。
2 select函數
該函數允許進程指示核心等待多個事件的任何一個發生,並只在有一個或多個事件發生或經曆一段指定的時間才喚醒它,也就是說我們調用select告知核心對哪些描述符(就讀、寫或異常條件)感興趣以及等待多長時間,當然感興趣的描述符可以不僅局限於通訊端,任何描述符都可以用select測試
#include <sys/select.h>#include <sys/time.h>int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); // 返回: 準備好描述字的正數目, 0 -逾時, -1 -出錯
我們從此函數的最後一個參數開始介紹,它告訴核心等待一組指定的描述字中的任一個準備好可花多長時間,結構timeval指定了秒數和微秒數成員
struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */};
這個參數有以下三種可能:
a. 永遠等待下去:僅在有一個描述字準備好I/O時才返回,為此,我們將參數timeout設定為空白指標。
b. 等待固定時間:在有一個描述字準備好I/O是返回,但不超過由timeout參數所指timeval結構中指定的秒數和微秒數。
c. 根本不等待:檢查描述字後立即返回,這稱為輪詢(polling)。為了實現這一點,參數timeout必須指向結構timeval,且定時器的值(由結構timeval指定的秒數和微秒數)必須為0
在前兩者情況的等待中,如果進程捕獲了一個訊號並從訊號處理常式返回,那麼等待一般被中斷。
中間三個參數readset,wirteset和exceptset指定我們要讓核心測試讀寫和異常條件所需的描述字,參數maxfdp1說明了被測試的描述符的個數,它的值是要被測試的最大的描述符+1
為了分配一個fd_set資料類型的描述符集,並用這些宏初始化,設定或測試該集合的每一位,有下面是四個宏函數:
void FD_ZERO(fd_set * fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set * fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set * fdset); /* turn on the bit for fd in fdset */
int FD_ISSET(int fd, fd_set * fdset); /* is the bit for fd on in fdset */
描述符就緒條件
1).滿足下面四個中任意條件,則一個通訊端準備好讀:
a.通訊端接收緩衝區的資料位元組數大於等於,通訊端接收緩衝區低水位線,可以用SO_RCVLOWAT套接選項來設定低水位線,對於TCP和UDP套按字,預設值為1
b.該串連的讀半部分關閉(接收到了FIN的TCP串連).對這樣的通訊端讀操作,返回0(EOF)
c.該通訊端是一個監聽通訊端且已經完成的串連數不為0.對這樣的套按字的accept通常不會阻塞
d.其上有一個通訊端錯誤待處理.對這樣的套按字的讀操作將不阻塞並返回-1(錯誤),同時把errno設定成錯誤條件,這些待處理錯誤也可以通過指定SO_ERROR通訊端選項調用getsockopt擷取.
2).滿足下面四個中任意條件,則一個通訊端準備好寫:
a.該通訊端發送緩衝區的可用位元組數大於等於通訊端發送緩衝區低水位線的當前大小.並且或者該套接已經串連,或者套按字不需要串連(UDP),如果我們把這通訊端設定成非阻塞,寫操作將不阻塞並返回一個正值.可以使用SO_SNDLOWAT設定一個該通訊端的低水位標記.對於TCP和UDP預設值通常為2048.
b.該串連的寫半部關閉.對這樣的套接寫的寫操作將產生SIGPIPE訊號.
c.使用非阻塞式的connect的套按字已經建立串連,或者connect已經失敗.
d.其上有一個通訊端錯誤等處理。對這樣的通訊端進行寫操作會返回-,且,把ERROR設定成錯誤條件,可以通過指定SO_ERROR套按選項調用getsockopt擷取並清除.
3).如果一個通訊端存在帶外資料或者仍處於帶外標記,那麼它有異常條件待處理
3 shutdown函數
終止網路連接的正常方法是調用close,但close有兩個限制可由函數shutdown來避免:
1). close將描述字的訪問計數減1,僅在此計數為0時才關閉套介面。用shutdown我們可以激發TCP的正常串連終止序列,而不管訪問計數。
2). close終止了資料傳送的兩個方向:讀和寫。由於TCP串連是全雙工系統的,有很多時候我們要通知另一端我們已經完成了資料發送,即使那一端仍有許多資料要發送也是如此。
#include <sys/socket.h>int shutdown(int sockfd, int howto); // 返回: 0-成功, -1-出錯
該函數的行為依賴於howto參數的值:
SHUT_RD – 關閉通訊端的讀取資料方向的串連
SHUT_WR – 關閉通訊端的寫入資料方向的串連
SHUT_RDWR – 關閉通訊端雙向的串連
4 pselect函數
#include <sys/select.h>#include <signal.h>#include <time.h>int pselect(int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timespec * timeout, const sygset_t * sigmask); //返回: 準備好描述字的個數, 0-逾時 -1-出錯
pselect相對於select有兩個變化
1).pselect函數採用timespec結構,這個結構支援納秒
struct timespec{ time_t tv_sec; // seconds long tv_nsec; // nanoseconds};
2).pselect函數增加了第六個函數:一個指向訊號掩碼的指標
5 poll函數
poll提供了與select相似的功能,但當涉及到流裝置時,它還提供了附加資訊
#include <poll.h>int poll(struct pollfd * fdarray, unsigned long nfds, int timeout); // 返回: 準備好描述字的個數, 0-逾時, -1-出錯
第一個參數是指向一個結構數組第一個元素的指標,每個數組元素都是一個pollfd結構,它規定了為測試一給定描述字fd的一些條件。
struct pollfd{ int fd; /* descriptor to check */ short events /* events of interest on fd */ short revents /* events that occurred on fd */};
參數nfds說明我們關心的描述字的個數,參數timeout 逾時等待的時間,單位是毫秒
UNPv1第六章:IO複用select&poll