linux信号驱动i o,信号驱动I/O实例

4,信号驱动I/O :让内核在描述字就绪时发送SIGIO信号通知我们。首先开启套接口的信号驱动1/O功能,sigaction系统调用安装一个信号处理函数,

当内核数据包准备好时,会为该进程产生一个SIGIO信号。应用可以在信号处理时接收数据。

具体步骤:1,建立SIGIO信号处理函数。

2,设置该套接口的属主,通常使用fcntl的F_SETOWN命令设置。

3,开启该套接口的信号驱动I/O,通常使用fcntl的F_SETFL命令打开O_ASYNC标志完成。

UDP套接口上的SIGIO信号:

UDP上使用信号驱动I/O是简单的。当下述事件发生时产生SIGIO信号:

1. 数据报到达套接口

2. 套接口上发生异步错误

TCP套接口上的SIGIO信号

不幸的是,信号驱动I/O对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情。

下列条件均可在TCP套接口上产生SIGIO信号(假设信号驱动I/O是使能的):

1. 在监听套接口上有一个连接请求已经完成

2. 发起了一个连接拆除请求

3. 一个连接拆除请求已经完成

4. 一个连接的一半已经关闭

5. 数据到达了套接口

6. 数据已从套接口上发出(即输出缓冲区有空闲时间)

7. 发生了一个异步错误

因此,当我们捕获到SIGIO信号时,我们调用recvfrom读取到达的数据报或者获取异步错误。

例如,如果一个进程既从一个TCP套接口读数据,又向其上写数据,当新数据到达或者以前所写数据得到确认后均会产生SIGIO信号,

进程无法在信号处理程序中区分这两种情况。如果在这种情况下使用SIGIO,TCP套接口应该被设置为非阻塞方式以防止read或write发生阻塞。

我们应该考虑只在监听TCP套接口上使用SIGIO,因为在监听套接口上产生SIGIO的唯一条件是一个新连接的完成。

这里找到的实际使用信号驱动I/O的程序是基于UDP的NTP(网络时间协议)服务器程序。NTP服务器的主循环从客户接收数据报并送回相应,但是

每个客户请求都要进行相当数量的处理(比我们简单的回射服务器多得多)。对于服务器来讲,很重要的一点是给每个收到的数据报记录精确的

时间戳,因为这个值要返回给客户,客户要用它来计算到达该服务器的来回时间。图22.1展示了构建这样一个UDP服务器的两种方法。

大多数UDP服务器都被设计成图中左边的方式。但是NTP服务器采用了图中右边的技术: 当一个新数据报到达时,SIGIO处理程序读得该数据报,

同时记录数据报到达的时刻,然后把它放入进程的另一个队列中,由主服务器循环移走和处理。虽然这种技巧使服务器代码变得复杂,但是它为

到达的数据报提供了精确的时间戳。

使用SIGIO的UDP回射服务器程序

0818b9ca8b590ca3270a3433284dd417.png

现在举一个类似图22.1.右边的例子:一个使用SIGIO信号接收到达的数据报的UDP服务器程序。

我们使用图8.7和8.8中同样的客户程序以及图8.3中同样的服务器程序main函数。我们做的唯一修改是dg_echo函数,下边四张图给出这些修改,图22.2给出了全局变量声明。

0818b9ca8b590ca3270a3433284dd417.png

#include "unp.h"static int sockfd;

#define QSIZE  8  /* size of input queue */

#define MAXDG  4096  /* maximum datagram size */

/* SIGIO信号处理程序将到达的数据报放入一个队列中。该队列是一个DG结构数组,我们将它处理成环形缓冲区。 */

/* 每个DG结构包括一个指向收到的数据报的指针,数据报的长度,一个指向包含客户协议地址的套接口地址结构的指针以及协议地址的大小。 */

/* 静态分配我们QSIZE个DG结构,从图22.4我们将看到dg_echo函数调用malloc给所有的数据报和套接口地址结构分配内存。 */

/* 我们还分配一个诊断用计数器cntread,不久将会解释到。

图22.3展示了当第一项指向一个150字节数据报,与其关联的套接口地址结构长度为16时,

DG结构数组的内容 */

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是主循环将处理的下一个数组元素的下标 */

/* iput是信号处理程序将要存放的下一个数组元素的下标 */  /* nqueue是主循环将要处理的队列中数据报的总数目 */

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);

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;

/* 套接口描述字保存在一个全局变量中,因为信号处理程序要用到它。已收到数据报队列被初始化 */

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设置套接口属主 */

Fcntl(sockfd, F_SETOWN, getpid());   /* 用ioctl设置信号驱动和非阻塞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对应的位 */

Sigaddset(&newmask, SIGIO);  /* the signal we want to block */

/* sigprocmask将进程当前信号掩码存入oldmask中,然后将newmask与当前的信号掩码进行逻辑或。这将阻塞SIGIO并返回当前的信号掩码 */

Sigprocmask(SIG_BLOCK, &newmask, &oldmask);

/* 进入for循环并测试nqueue计数器。只要这个计数器为0,进程就无事可做。这时我们调用sigsuspend。 */

/* 因为zeromask是一个空信号集,所有的信号将被解阻塞。sigsuspend在捕获一个信号并在其信号处理程序返回后返回 */

/* 但在返回前,sigsuspend总是将信号掩码恢复为调用它时的信号掩码值,在这里这个掩码值为newmask,所以我们能够保证sigsuspend返回后,SIGIO仍被阻塞 */

/* 这就是为什么我们能够测试nqueue标志,因为当我们测试时,SIGIO信号不可能被递交。 */

for( ; ; ) {

while (nqueue == 0)

sigsuspend(&zeromask);  /* wait for a datagram to process */

/* unblock SIGIO */

Sigprocmask(SIG_SETMASK, &oldmask, NULL);

/* 调用sigprocmask将进程的信号掩码设置为先前保存的oldmask的旧值,从而解除了SIGIO阻塞 */

Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, dg[iget].dg_sa, dg[iget].dg_salen);

/* 然后调用sendto发送应答 */

if( ++iget >= QSIZE) /* 下标iget加1,如果其值等于数据元素个数,则置iget为0,因为我们把数组当作环形缓冲区对待 */

iget = 0; /* 当修改iget时,我们不需要阻塞SIGIO,因为iget只被主循环使用,信号处理程序永远不会修改它 */

/* block SIGIO */

Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* 阻塞SIGIO,nqueue减1,我们在修改nqueue是必须阻塞SIGIO,因为主循环和信号处理程序在共享这个变量 */

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]; /* 在非阻塞的套接口上调用recvfrom,iput做下标的数组项是数据报存储的地方,如果没有可读数据报,则跳出for循环 */

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是一个计数器,记录每个信号读的数据报数 */

nqueue++; /* nqueue是主循环将要处理的数据报数 */

if(++iput >= QSIZE)

iput = 0;

}

cntread[nread]++;  /* histogram of #datagrams read per signal */

/* 信号处理程序在返回前,将与每个信号读到的数据报数目对应的计数器加1,当SIGHUP递交后,该数组的内容做为诊断信息输出 */

}

SIGHUP信号处理程序:

static void sig_hup(int signo)

{/* SIGHUP信号处理程序,它输出cntread数组的内容,cntread数组统计每个信号读到的数据报数目 */

int i;  for(i = 0; i <= QSIZE; i++)

printf("cntread[%d] = %d\n", i, cntread[i];

}

5,异步I/O模型:内核通知我们I/O操作何时完成。与信号驱动I/O不同的是 信号驱动I/O是由内核通知我们何时可以启动一个I/O. 对于服务器程序,I/O是制约系统性能最关键的因素。对于需要处理大量连接的高并发服务器程序,异步I/O几乎是不二的选择。 一般来说,服务器端的I/O主要有两种情况:一是来自网络的I/O;二是对文件(设备)的I/O。 Linux针对前者提供了epoll模型,针对后者提供了AIO模型。 1、Linux的异步I/O(AIO)      在传统的 I/O 模型中,有一个使用惟一句柄标识的 I/O 通道。在 UNIX中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。 在阻塞 I/O 中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。 本课题基于Linux在内核实现了POSIX1003.1b标准定义的异步I/O函数。基本思想是当一个进程有异步I/O请求时,就为该进程创建一个 队列来排队其所有的异步请求,并为该队列创建一个内核线程来完成队列中实际的I/O操作。异步I/O系统调用所做的工作只是将I/O请求加 入调用进程的队列,如果调用该功能的进程是第一次发出异步I/O请求,则先要为该进程创建排队异步 I/O请求的队列和相应的内核线程,然后进程直接返回而不用挂起等待I/O完成。当实际的I/O操作完成时,内核发一个信号通知进程I/O完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值