信号驱动式I/O
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
对一个套接字使用信号驱动式I/O
- 建立SIGIO信号的信号处理函数。
- 设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。(因该在设置套接字属主之前建立信号处理函数,因为在调用fcntl后调用signal之前有较小的机会产生SIGIO信号,此时信号被丢弃)
- 开启该套接字的信号驱动式I/O,通常通过使用fcntl的F_SETFL命令打开O_ASYNC标志完成。
UDP套接字的SIGIO信号
在UDP上使用信号驱动式I/O是简单的。SIGIO信号在发生以下事件时产生:
- 数据报到达套接字
- 套接字上发生异步错误
因此当捕获对于某个UDP套接字的SIGIO信号时,我们调用recvfrom或者读入到达的数据报,或者获取发生的异步错误(发生异步错误的前提是udp套接字已连接)
TCP套接字的SIGIO信号
不幸的是,信号驱动式I/O对于TCP套接字近乎无用。问题在于该信号产生的过于频繁,并且它的出现并没有告诉我们发生了什么事情。下列条件均导致对于一个TCP套接字产生SIGIO信号(假设该套接字的信号驱动式I/O已经开启):
- 监听套接字上某个连接请求已经完成
- 某个断连请求已经发起
- 某个断连请求已经完成
- 某个连接之半已经关闭
- 数据到达套接字
- 数据已经从套接字发送走
- 发生某个异步错误
如果一个进程既读又写一个tcp套接字(此时应该设置成非阻塞套接字,防止read或write阻塞),那么当有新数据到达时或数据写出前SIGIO信号均会产生,而且信号处理函数无法区分这两种情况。
应该只对监听TCP套接字使用SIGIO,因为对于监听套接字产生SIGIO的唯一条件是某个新的连接已完成。
使用SIGIO的UDP回射服务器程序
当一个新数据报到达时,SIGIO处理函数读入该数据报,同时记录它到达的时刻,然后将它置于进程内核的另一个队列中,以便服务器循环移走并处理(下图)

client:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include <fcntl.h>
#define MAXLINE 1024
#define SERV_PORT 3333 #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while ()
typedef struct sockaddr SA; void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + ]; while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), , pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, , NULL, NULL); recvline[n] = ; /* null terminate */
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if (argc != )
ERR_EXIT("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, ); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit();
}
server:
#include "unp.h" static int sockfd; #define QSIZE 8 /* size of input queue */
#define MAXDG 4096 /* max datagram size */ 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; /* length of sockaddr{} */
} DG;
static DG dg[QSIZE]; /* queue of datagrams to process */
static long cntread[QSIZE+]; /* diagnostic counter */ 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 i;
const int on = ;
sigset_t zeromask, newmask, oldmask; sockfd = sockfd_arg;
clilen = clilen_arg; for (i = ; 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 = ; signal(SIGHUP, sig_hup);
signal(SIGIO, sig_io);
fcntl(sockfd, F_SETOWN, getpid());
ioctl(sockfd, FIOASYNC, &on);
ioctl(sockfd, FIONBIO, &on); sigemptyset(&zeromask); /* init three signal sets */
sigemptyset(&oldmask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGIO); /* signal we want to block */ sigprocmask(SIG_BLOCK, &newmask, &oldmask);
for ( ; ; ) {
while (nqueue == )
sigsuspend(&zeromask); /* wait for datagram to process */ /* 4unblock SIGIO */
sigprocmask(SIG_SETMASK, &oldmask, NULL); sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, ,
dg[iget].dg_sa, dg[iget].dg_salen); if (++iget >= QSIZE)
iget = ; /* 4block SIGIO */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
nqueue--;
}
}
static void
sig_io(int signo)
{
ssize_t len;
int nread;
DG *ptr; for (nread = ; ; ) {
if (nqueue >= QSIZE)
ERR_EXIT("receive overflow"); ptr = &dg[iput];
ptr->dg_salen = clilen;
len = recvfrom(sockfd, ptr->dg_data, MAXDG, ,
ptr->dg_sa, &ptr->dg_salen);
if (len < ) {
if (errno == EWOULDBLOCK)
break; /* all done; no more queued to read */
else
ERR_EXIT("recvfrom error");
}
ptr->dg_len = len; nread++;
nqueue++;
if (++iput >= QSIZE)
iput = ; }
cntread[nread]++; /* histogram of # datagrams read per signal */
}
static void
sig_hup(int signo)
{
int i; for (i = ; i <= QSIZE; i++)
printf("cntread[%d] = %ld\n", i, cntread[i]);
}
已收取数据报队列
SIGIO信号处理函数把到达的数据报放入一个队列。该队列是一个DG结构数组,我们把它作为一个环形缓冲区处理。每个DG结构包括指向所收取数据报的一个指针,该数据报的长度,指向含有客户协议地址的某个套接字地址结构的一个指针,该协议地址的大小。静态分配QSIZE个DG结构,dg_echo函数调用malloc动态分配所有数据报和套接字地址结构的内存空间。我们还分配一个诊断用计数器cntread。下图展示了这个DG结构数组,其中假设第一个元素指向一个150字节的数据报,与它关联的套接字地址结构长度为16. 
数组下标
iget是主循环将处理的下一个数组元素的下标,iput是信号处理函数将存放到的下一个数组元素的下标,nqueue是队列中供主循环处理的数据报的总数。
初始化已接收数据报队列
把套接字描述符保存在一个全局变量中,因为信号处理函数需要它。初始化已接收数据报队列。
建立信号处理函数并设置套接字标志
为SIGHUP(用于诊断目的)和SIGIO建立信号处理函数。使用fcntl设置套接字的属主,使用ioctl设置信号驱动和非阻塞式I/O标志。
初始化信号集
初始化三个信号集:zeromask(从不改变),oldmask(记录我们阻塞SIGIO时原来的信号掩码)和newmask。使用sigaddset打开newmask中与SIGIO对应的位。
阻塞SIGIO并等待有事可做
调用sigprocmask把进程的当前信号掩码保存到oldmask中,然后把newmask逻辑或到当前信号掩码。这将阻塞SIGIO并返回当前信号掩码。接着进入for循环,并测试nqueue计数器。只要该计数器为0,进程就无事可做,这时我们可以调用sigsuspend。该POSIX函数先内部保存当前信号掩码,再把当前信号掩码设置为它的参数(zeromask)。既然zeromask是一个空信号集,因而所有信号都被开通。sigsuspend在进程捕获一个信号并且该信号的处理函数返回之后才返回。(它是一个不寻常的函数,因为它总是返回EINTR错误)。在返回之前sigsuspend总是把当前信号掩码恢复为调用时刻的值,在本例中就是newmask的值,从而确保sigsuspend返回之后SIGIO继续被阻塞。这时我们可以测试计数器nqueue的理由,因为我们知道测试它时SIGIO信号不可能被递交。
解阻塞SIGIO并发送应答
调用sigprocmask把进程的信号掩码设置为先前保存的值(oldmask),从而解除SIGIO的阻塞。然后调用sendto发送应答。递增iget下标,若其值等于DG结构数组元素数目则将其值置回0。因为我们把该数组作为环形缓冲区对待。注意:修改iget时我们不必阻塞SIGIO,因为只有主循环使用这个下标,信号处理函数从不改动它。
阻塞SIGIO
阻塞SIGIO,递减nqueue。修改nqueue时我们必须阻塞SIGIO,因为它是主循环和信号处理函数共同使用的变量。我们在循环顶部测试nqueue时也需要SIGIO阻塞着。
我们也可以去掉for循环内的两个sigprocmask调用,整个循环期间SIGIO一直阻塞,从而降低了信号处理函数的及时性,数据报不应该因此变动而丢失(假设套接字缓冲区足够大),但是SIGIO信号向进程的递交将在整个阻塞期间一直被拖延。
然而当一个数据报到达导致SIGIO被递交,它的信号处理函数读入该数据报并把它放到供主循环读取的队列中,然而在信号处理函数执行期间,另有两个数据包到达,这一点意味着如果我们在信号处理函数执行(期间确保该信号被阻塞),期间该信号又发生了2次,那么它实际只被递交1次。让我们考虑下述情形。一个数据报到达导致SIGIO被递交。它的信号处理函数读入该数据报并把它放到供主循环读取的队列中。然而在信号处理函数执行期间,另有两个数据报到达,导致SIGIO再产生两次。由于SIGIO被阻塞,当他的信号处理函数返回时,该处理函数仅仅再被调用一次。该信号处理函数的第二次执行读入第二个数据报,第三个数据报则仍然留在套接字接收队列中。第三个数据报被读入的前提条件时由第四个数据报到达。当第四个数据报到达时,被读入并放到供主循环读取的队列中的是第三个而不是第四个数据报。
既然信号时不排队的,开启信号驱动式I/O的描述符通常也被设置为非阻塞式。这个前提下,我们把SIGIO信号处理函数编写成在一个循环中执行读入操作,知道该操作返回EWOULDBLOCK时菜结束循环。
检查队列溢出
如果DG结构数组队列已满,进程就终止。
读入数据报
在非阻塞套接字上调用recvfrom。下标为iput的数组元素用于存放读入的数据报。如果没有可读的数据报,那就break出for循环。
递增计数器和下标
nread是一个计量每次信号递交读入数据报数目的诊断计数器。nqueue是有待主循环处理的数据报数目。
在信号处理函数返回之前,递增与每次信号递交读入数据报数目对应的计数器。当SIGHUP信号被递交时。
信号驱动式I/O的更多相关文章
- UNIX网络编程——信号驱动式I/O
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程. 针对一个套接字使用信号驱动式I/O,要求进程执行以下3个步骤: 建立SIGIO信号的信号处理函数. 设置 ...
- udp套接字使用信号驱动式I/O
信号驱动式I/O的本质就是:进程预先告知内核当某个描写叙述符发生事件时,内核会向该进程发送SIGIO信号通知进程,进程可在信号处理函数中进行处理 进程能够通过fcntl打开O_ASYNC标志或ioct ...
- UNP学习笔记(第二十五章 信号驱动式I/O)
信号驱动式I/O是指进程预先告知内核,使得当某个描述符发生某事时,内核使用信号通知相关进程. 套接字的信号驱动式I/O 针对一个套接字使用信号驱动式I/O(SIGIO)要求进程执行以下3个步骤: 1. ...
- IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO
最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...
- UDP信号驱动IO
SIGIO信号 信号驱动式I/O不适用于TCP套接字, 因为产生的信号过于频繁且不能准确判断信号产生的原因. 设置信号驱动需把sockfd的非阻塞与信号驱动属性都打开 server sockfd单独提 ...
- 🍛 餐厅吃饭版理解 IO 模型:阻塞 / 非阻塞 / IO 复用 / 信号驱动 / 异步
IO 概念 一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核 (kernel).当一个 read 操作发生时,它会经历两个阶段: 通过 read 系统调用 ...
- 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll
关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...
- UNIX网络编程读书笔记:I/O模型(阻塞、非阻塞、I/O复用、信号驱动、异步)
I/O模型 UNIX下可用的5种I/O模型: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 对于一个套接口上的 ...
- 【网络IO系列】IO的五种模型,BIO、NIO、AIO、IO多路复用、 信号驱动IO
前言 在上一篇文章中,我们了解了操作系统中内核程序和用户程序之间的区别和联系,还提到了内核空间和用户空间,当我们需要读取一条数据的时候,首先需要发请求告诉内核,我需要什么数据,等内核准备好数据之后 , ...
随机推荐
- 【坑】Maven [ERROR] 不再支持源选项 5。请使用 6 或更高版本
在pom.xml文件中添加如下代码: 注意:jdk使用自己下载的版本,我的是13 <properties> <project.build.sourceEncoding>UTF- ...
- 线程join方法详解
执行逻辑:在当前代码块(比如main方法)中的线程A执行了join方法, 那么当代码块(main)执行到join方法时,会停止继续向下执行,一直到线程A执行完毕, main方法才会继续向下执行. 代码 ...
- js-06-字符串
一.查找字符串的字符串 a:indexOf:没有查询到返回值为-1: b:lastIndexoOf:查找到的为重复的最后一个: c:search 查找: var str="good good ...
- SpringCloud之Feign:REST客户端
在Spring Cloud集群中,各个角色的通信基于REST服务,在调用服务时,需要使用REST客户端,常用,除了使用Spring自带的RestTemplate,也可使用另一个REST客户端:Feig ...
- Go交叉编译(Go语言Mac/Linux/Windows下交叉编译)
Go交叉编译(Go语言Mac/Linux/Windows下交叉编译) 2019/11/21 Chenxin 在很多时候,由于开发的方便,会有这样的场景出现,使用Mac开发或使用Windows开发,需要 ...
- 闲话复数(1) | 不现实的虚数 i 为什么虚?它长成什么样?
原文 | https://mp.weixin.qq.com/s/y-Nb3S508UZuf_0GtRuNaQ 复数的英文是complex number,直译是复杂的数.最早接触复数大概是在高中时期,只 ...
- openstack Glance安装与配置
一.实验目的: 1.理解glance镜像服务在OpenStack框架中的作用 2.掌握glance服务安装的基本方法 3.掌握glance的配置基本方法 二.实验步骤: 1.在controller节点 ...
- JS原型链与instanceof底层原理
一.问题: instanceof 可以判断一个引用是否属于某构造函数: 另外,还可以在继承关系中用来判断一个实例是否属于它的父类型. 老师说:instanceof的判断逻辑是: 从当前引用的proto ...
- 剑指Offer-42.和为S的两个数字(C++/Java)
题目: 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的. 输出描述: 对应每个测试案例,输出两个数,小的先输出. 分析: ...
- 使用ES6新特性async await进行异步处理
我们往往在项目中会遇到这样的业务需求,就是首先先进行一个ajax请求,然后再进行下一个ajax请求,而下一个请求需要使用上一个请求得到的数据,请求少了还好说,如果多了,就要一层一层的嵌套,就好像有点c ...