UNP Chapter 22 - 信号驱动I/O
22.1. 概述
信號驅(qū)動是指當某個描述字上發(fā)生了某個事件時,讓內(nèi)核通知進程。
這里描述的信號驅(qū)動不是真正的異步I/O。
第15章描述的非阻塞I/O同樣不是異步I/O。在非阻塞I/O中,啟動I/O操作后內(nèi)核并不像真正的異步I/O那樣立即返回,它只有在進程非得睡眠才能完成操作時才立即返回。
?
22.2. 套接口上的信號驅(qū)動I/O
使用套接口上的信號驅(qū)動I/O(SIGIO)需要進程執(zhí)行以下三個步驟:
1. 給SIGIO信號建立信號處理程序
2. 設(shè)置套接口屬主,通常使用fcntl的F_SETOWN命令
3. 激活套接口的信號驅(qū)動I/O,通常使用fcntl的F_SETFL命令打開O_ASYNC標志
?
UDP套接口上的SIGIO信號
? UDP上使用信號驅(qū)動I/O是簡單的。當下述事件發(fā)生時產(chǎn)生SIGIO信號:
? 1. 數(shù)據(jù)報到達套接口
? 2. 套接口上發(fā)生異步錯誤
因此,當我們捕獲到SIGIO信號時,我們調(diào)用recvfrom讀取到達的數(shù)據(jù)報或者獲取異步錯誤。
?
TCP套接口上的SIGIO信號
? 不幸的是,信號驅(qū)動I/O對TCP套接口幾乎是沒用的,原因是該信號產(chǎn)生得過于頻繁,并且該信號的出現(xiàn)并沒有告訴我們發(fā)生了什么事情。
? 下列條件均可在TCP套接口上產(chǎn)生SIGIO信號(假設(shè)信號驅(qū)動I/O是使能的):
??? 1. 在監(jiān)聽套接口上有一個連接請求已經(jīng)完成
??? 2. 發(fā)起了一個連接拆除請求
??? 3. 一個連接拆除請求已經(jīng)完成
??? 4. 一個連接的一半已經(jīng)關(guān)閉
??? 5. 數(shù)據(jù)到達了套接口
??? 6. 數(shù)據(jù)已從套接口上發(fā)出(即輸出緩沖區(qū)有空閑時間)
??? 7. 發(fā)生了一個異步錯誤
例如,如果一個進程既從一個TCP套接口讀數(shù)據(jù),又向其上寫數(shù)據(jù),當新數(shù)據(jù)到達或者以前所寫數(shù)據(jù)得到確認后均會產(chǎn)生SIGIO信號,進程無法在信號處理程序中區(qū)分這兩種情況。如果在這種情況下使用SIGIO,TCP套接口應(yīng)該被設(shè)置為非阻塞方式以防止read或write發(fā)生阻塞。我們應(yīng)該考慮只在監(jiān)聽TCP套接口上使用SIGIO,因為在監(jiān)聽套接口上產(chǎn)生SIGIO的唯一條件是一個新連接的完成。
?
這里找到的實際使用信號驅(qū)動I/O的程序是基于UDP的NTP(網(wǎng)絡(luò)時間協(xié)議)服務(wù)器程序。NTP服務(wù)器的主循環(huán)從客戶接收數(shù)據(jù)報并送回相應(yīng),但是每個客戶請求都要進行相當數(shù)量的處理(比我們簡單的回射服務(wù)器多得多)。對于服務(wù)器來講,很重要的一點是給每個收到的數(shù)據(jù)報記錄精確的時間戳,因為這個值要返回給客戶,客戶要用它來計算到達該服務(wù)器的來回時間。圖22.1展示了構(gòu)建這樣一個UDP服務(wù)器的兩種方法。
大多數(shù)UDP服務(wù)器都被設(shè)計成圖中左邊的方式。但是NTP服務(wù)器采用了圖中右邊的技術(shù): 當一個新數(shù)據(jù)報到達時,SIGIO處理程序讀得該數(shù)據(jù)報,同時記錄數(shù)據(jù)報到達的時刻,然后把它放入進程的另一個隊列中,由主服務(wù)器循環(huán)移走和處理。雖然這種技巧使服務(wù)器代碼變得復(fù)雜,但是它為到達的數(shù)據(jù)報提供了精確的時間戳。
?
22.3. 使用SIGIO的UDP回射服務(wù)器程序
現(xiàn)在舉一個類似圖22.1.右邊的例子:一個使用SIGIO信號接收到達的數(shù)據(jù)報的UDP服務(wù)器程序。
我們使用圖8.7和8.8中同樣的客戶程序以及圖8.3中同樣的服務(wù)器程序main函數(shù)。我們做的唯一修改是dg_echo函數(shù),下邊四張圖給出這些修改,圖22.2給出了全局變量聲明。
#include "unp.h"static int sockfd;
#define QSIZE 8 /* size of input queue */
#define MAXDG 4096 /* maximum datagram size */ /* SIGIO信號處理程序?qū)⒌竭_的數(shù)據(jù)報放入一個隊列中。該隊列是一個DG結(jié)構(gòu)數(shù)組,我們將它處理成環(huán)形緩沖區(qū)。 */ /* 每個DG結(jié)構(gòu)包括一個指向收到的數(shù)據(jù)報的指針,數(shù)據(jù)報的長度,一個指向包含客戶協(xié)議地址的套接口地址結(jié)構(gòu)的指針以及協(xié)議地址的大小。 */ /* 靜態(tài)分配我們QSIZE個DG結(jié)構(gòu),從圖22.4我們將看到dg_echo函數(shù)調(diào)用malloc給所有的數(shù)據(jù)報和套接口地址結(jié)構(gòu)分配內(nèi)存。 */ /* 我們還分配一個診斷用計數(shù)器cntread,不久將會解釋到。圖22.3展示了當?shù)谝豁椫赶蛞粋€150字節(jié)數(shù)據(jù)報,與其關(guān)聯(lián)的套接口地址結(jié)構(gòu)長度為16時,DG結(jié)構(gòu)數(shù)組的內(nèi)容 */
typedef struct {
void * dg_data; /* ptr to actual datagram */
size_t dg_len; /* length of datagram */
struct sockaddr * dg_sa; /* ptr to sockaddr{} w/client's address */
socklen_t dg_salen; /* lenght of sockaddr{} */
} DG;
static DG dg[QSIZE]; /* the queue of datagrams to process */
static long cntread[QSIZE+1]; /* diagnostic counter */ /* iget是主循環(huán)將處理的下一個數(shù)組元素的下標 */ /* iput是信號處理程序?qū)⒁娣诺南乱粋€數(shù)組元素的下標 */ /* nqueue是主循環(huán)將要處理的隊列中數(shù)據(jù)報的總數(shù)目 */ static int iget; /* next one for main loop to process */
static int iput; /* next one for signal handler to read into */
static int nqueue; /* #on queue for main loop to process */
static socklen_t clilen; /* max length of sockaddr{} */
static void sig_io(int);
static void sig_hup(int);
?
dg_echo函數(shù):服務(wù)器主處理循環(huán)
void dg_echo(int sockfd_arg, SA * pcliaddr, socklen_t clilen_arg){
int on = 1;
sigset_t zeromask, newmask, oldmask;
sockfd = sockfd_arg;
clilen = clilen_arg; /* 套接口描述字保存在一個全局變量中,因為信號處理程序要用到它。已收到數(shù)據(jù)報隊列被初始化 */
for ( i = 0; i < QSIZE; i++) { /* init queue of buffers */
dg[i].dg_data = Malloc(MAXDG);
dg[i].dg_sa = Malloc(clilen);
dg[i].dg_salen = clilen;
}
iget = iput = nqueue = 0; /* 給SIGHUP和SIGIO建立信號處理程序 */
Signal(SIGHUP, sig_hup);
Signal(SIGIO, sig_io); /* 用fcntl設(shè)置套接口屬主 */
Fcntl(sockfd, F_SETOWN, getpid()); /* 用ioctl設(shè)置信號驅(qū)動和非阻塞I/O標志 */
ioctl(sockfd, FIOASYNC, &on);
ioctl(sockfd, FIONBIO, &on); /* 初始化三個信號集:zeromask(從不改變)、oldmask(記錄我們阻塞SIGIO時的老信號掩碼)和newmask */
Sigemptyset(&zeromask); /* init three signal sets */
Sigemptyset(&oldmask);
Sigemptyset(&newmask); /* sigaddset打開newmask中與SIGIO對應(yīng)的位 */
Sigaddset(&newmask, SIGIO); /* the signal we want to block */
/* sigprocmask將進程當前信號掩碼存入oldmask中,然后將newmask與當前的信號掩碼進行邏輯或。這將阻塞SIGIO并返回當前的信號掩碼 */ Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* 進入for循環(huán)并測試nqueue計數(shù)器。只要這個計數(shù)器為0,進程就無事可做。這時我們調(diào)用sigsuspend。 */ /* 因為zeromask是一個空信號集,所有的信號將被解阻塞。sigsuspend在捕獲一個信號并在其信號處理程序返回后返回 */
/* 但在返回前,sigsuspend總是將信號掩碼恢復(fù)為調(diào)用它時的信號掩碼值,在這里這個掩碼值為newmask,所以我們能夠保證sigsuspend返回后,SIGIO仍被阻塞 */ /* 這就是為什么我們能夠測試nqueue標志,因為當我們測試時,SIGIO信號不可能被遞交。 */ for( ; ; ) {
while (nqueue == 0)
sigsuspend(&zeromask); /* wait for a datagram to process */
/* unblock SIGIO */
Sigprocmask(SIG_SETMASK, &oldmask, NULL); /* 調(diào)用sigprocmask將進程的信號掩碼設(shè)置為先前保存的oldmask的舊值,從而解除了SIGIO阻塞 */
Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, dg[iget].dg_sa, dg[iget].dg_salen);/* 然后調(diào)用sendto發(fā)送應(yīng)答 */
if( ++iget >= QSIZE) /* 下標iget加1,如果其值等于數(shù)據(jù)元素個數(shù),則置iget為0,因為我們把數(shù)組當作環(huán)形緩沖區(qū)對待 */
iget = 0; /* 當修改iget時,我們不需要阻塞SIGIO,因為iget只被主循環(huán)使用,信號處理程序永遠不會修改它 */
/* block SIGIO */
Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* 阻塞SIGIO,nqueue減1,我們在修改nqueue是必須阻塞SIGIO,因為主循環(huán)和信號處理程序在共享這個變量 */
nqueue--;
}
}
SIGIO處理程序
static void sig_io(int signo){
ssize_t len;
int nread;
DG *ptr;
for(nread = 0; ; ) {
if(nqueue >= QSIZE) /* 如果隊列滿,進程就終止 */
err_quit("receive overflow");
ptr = &dg[iput]; /* 在非阻塞的套接口上調(diào)用recvfrom,iput做下標的數(shù)組項是數(shù)據(jù)報存儲的地方,如果沒有可讀數(shù)據(jù)報,則跳出for循環(huán) */
ptr->dg_salen = clilen;
len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0, ptr->dg_sa, &ptr->dg_salen);
if(len<0) {
if(errno == EWOULDBLOCK)
break; /* all done; no more queued to read */
else
err_sys("recvfrom error");
}
ptr->dg_len = len;
nread++; /* nread是一個計數(shù)器,記錄每個信號讀的數(shù)據(jù)報數(shù) */
nqueue++; /* nqueue是主循環(huán)將要處理的數(shù)據(jù)報數(shù) */
if(++iput >= QSIZE)
iput = 0;
}
cntread[nread]++; /* histogram of #datagrams read per signal */
/* 信號處理程序在返回前,將與每個信號讀到的數(shù)據(jù)報數(shù)目對應(yīng)的計數(shù)器加1,當SIGHUP遞交后,該數(shù)組的內(nèi)容做為診斷信息輸出 */ }
SIGHUP信號處理程序
static void sig_hup(int signo){/* SIGHUP信號處理程序,它輸出cntread數(shù)組的內(nèi)容,cntread數(shù)組統(tǒng)計每個信號讀到的數(shù)據(jù)報數(shù)目 */
int i;
for(i = 0; i <= QSIZE; i++)
printf("cntread[%d] = %d\n", i, cntread[i];
}
22.4. 小結(jié)
?
轉(zhuǎn)載于:https://www.cnblogs.com/s7vens/archive/2012/03/28/2421602.html
總結(jié)
以上是生活随笔為你收集整理的UNP Chapter 22 - 信号驱动I/O的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go学习笔记—Channel通道
- 下一篇: QQ机器人制作教程,超详细