信号驱动io
来源:互联网 发布:身份证找回淘宝账号 编辑:程序博客网 时间:2024/04/29 16:41
5.4 信号驱动I/O
使用信号驱动I/O时,当网络套接字可读后,内核通过发送SIGIO信号通知应用进程,于是应用可以开始读取数据。有时也称此方式为异步I/O。但是严格讲,该方式并不能算真正的异步I/O,因为实际读取数据到应用进程缓存的工作仍然是由应用自己负责的。
5.4.1 信号驱动I/O模型
图5-5 信号驱动I/O
首先允许套接字使用信号驱动I/O模式,并且通过sigaction系统调用注册一个SIGIO信号处理程序。当有数据到达后,系统向应用进程交付一个SIGIO信号,然后既可以如图中所示那样在信号处理程序中读取套接字数据,然后通知主循环处理逻辑,也可以直接通知主循环逻辑,让主程序进行读取操作。
无论采用上面描述的哪种方式读取数据,应用进程都不会因为尚无数据达到而被阻塞,应用主循环逻辑可以继续执行其他功能,直到收到通知后去读取数据或者处理已经在信号处理程序中读取完毕的数据。
5.4.2 设置套接字允许信号驱动I/O
为了让套接字描述符可以工作于信号驱动I/O模式,应用进程必须完成如下三步设置:
1.注册SIGIO信号处理程序。(安装信号处理器)
2.使用fcntl的F_SETOWN命令,设置套接字所有者。(设置套接字的所有者)
3.使用fcntl的F_SETFL命令,置O_ASYNC标志,允许套接字信号驱动I/O。(允许这个套接字进行信号输入输出)
注意,必须保证在设置套接字所有者之前,向系统注册信号处理程序,否则就有可能在fcntl调用后,信号处理程序注册前内核向应用交付SIGIO信号,导致应用丢失此信号。下面的程序片段描述了怎样为套接字设置信号驱动I/O:
sigaction 函数:
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
该函数会按照参数signum指定的信号编号来设置该信号的处理函数,signum可指定SIGK
ILL和SIGSTOP以外的所有信号,如果参数act不是NULL指针,则用来设置新的信号处理方式。
sigaction 结构体:
struct sigaction
{
void (*sa_handler)(int);//信号处理函数
sigset_t sa_mask;//用来设置在处理该信号时暂时将sa_mask指定的信号搁置
int sa_flags;//用来设置信号处理的其他相关操作
void (*sa_restorer)(void);//这个参数没有使用
}
sa_flags的值为:
A——NOCLDSTOP 如果参数signum为SIGCHILD,则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA——RESETHAND:但调用新的信号处理函数前,将这个信号处理方式改
为系统默认方式。
SA——NOMASK/SA——NODEFER:在处理这个信号没有结束前,不理会这个信号的再次到
来。
以上的几个信号可以用“|”or运算组合起来使用。
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
#ifdef __USE_BSD
long int __tm_gmtoff;
__const char *__tm_zone;
#endif
};
sigmptyset函数:
#include
int sigemptyset(sigset_t *set);
用来将参数set信号集初始化并清空;
执行成功则返回0,如果有错误则返回-1;
函数的实现:
static inline void sigemptyset(sigset_t,*set)
{
switch(_NSIG_WORDS){
default:
memset(set,0,sizeof(sigse_t));
break;
case 2:set->sig[1]=0;
case 1:set->sig[0]=0;
break;
}
}
sigset_t
信号集级信号集操作函数:信号集被定义为一种数据类型:
typedef struct
{
unsigned long sig[_NSIG_WORDS];
}sigset_t;
static inline void sigaddset(sigset_t *set ,int _sig)
{
asm ("bfset %0{%1,#1}"
:"+od" (*set)
:"id"((_sig-1^31)
:"cc"));
}//没有看懂这段代码的意思,函数的功能是将_sig 代表的信号
加入到set信号集里面。
int sigprocmask(int how,sigset_t *set,sigset_t *oldset)
{
int error;
spin_lock_irq(¤t->sighand->siglock);
if(oldset)
*oldset=current->blocked;
error=0;
switch(how)
{
case SIG_BLOCK:
sigorsets(¤t->blocked,¤t->blocked,set);
break;
case SIG_UNBLOCK:
signandsets(¤t->blocked,¤t->blocked,set);
break;
case SIG_SETMASK:
current->blocked=*set;
break;
default:
error=-EINVAL;
}
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
return error;
}
//该函数用来设置信号掩码,指定那些信号需要被阻塞。
void do_sigio(int sig)
{
/* SIGIO处理 */
}
...
int sockfd; /* 套接字 */
struct sigaction sigio_action;
...
sigio_action.sa_handler = do_sigio; /* 信号处理程序,这是个指针调用函数 */
if (sigaction (SIGIO, &sigio_action, NULL) == -1)
perror ("Failed to set SIGIO");
fcntl (sockfd, F_SETOWN, getpid()); /* 设置套接字所有者以接收SIGIO */
if ((flags = fcntl (sockfd, F_GETFL, 0)) < 0)
bail("F_GETFL error");
flags |= O_ASYNC | O_NONBLOCK; /* 设置信号驱动和非阻塞模式 */
if (fcntl (sockfd, F_SETFL, flags) < 0)
bail ("F_SETFL error");
通常,在UDP编程中使用信号驱动I/O,此时SIGIO信号产生于下面两种情况:
套接字收到一个数据报。
套接字上发生了异步错误。
因此,当应用因为收到一个UDP数据报而产生的SIGIO时,要么可以调用recvfrom读取该数据报,要么得到一个异步错误。
对于TCP编程,信号驱动I/O就没有太大意义了,因为对于流式套接字而言,有很多情况都可以导致SIGIO产生,而应用又无法区分是什么具体情况导致该信号产生的。例如:
监听套接字完成了一个连接请求。
收到了一个断连请求。
断连操作完成。
套接字收到数据。
有数据从套接字发出。
5.4.3信号驱动I/O示例
本节使用信号驱动I/O模式重写数据报套接字一节中的日期时间程序。本例通过信号处理程序读取套接字收到的UDP数据报,然后将数据报存入一队列,再通知应用主循环从队列中取出数据报并进行处理。图5-6描述了本例程序的工作模式。
图5-6 信号驱动I/O的应用
由图5-6可知,SIGIO信号处理程序负责从套接字接收缓存中将已收到的UDP数据报拷贝到应用自身的一个队列,而应用主程序从该队列中取出下一个数据报进行处理。为了简化设计,这里用一个数组req_queue来表示该数据报队列,数组元素都是如下结构体实例:
struct request { /* 客户端请求 */
char *reqstr; /* 客户端请求日期时间字符串指针 */
size_t reqlen; /* 客户端请求日期时间字符串长度 */
struct sockaddr_in *peer; /* 客户端internet地址指针 */
socklen_t sklen;
};
图5-7 信号驱动I/O的数据报应用队列
程序5.3列出了服务器udpsrvsig.c的源程序
1 /*
2 * udpserver.c --liub
3 *
4 * DESCRIPTION: this program act as a datagram server, which
5 * receives datagram in SIGIO handler from a datagram client,
6 * and the received datagram is saved in a queue, later the main
7 * loop will fetch each datagram from this queue and process it.
8 *
9 * NOTE: the datagram server get the server ip address and
10 * portnumber from the command line, otherwise it uses the default one,
11 * namely any ip is ok.
12 *
13 * Usuage: udpserver ip portnumber
14 */
15 #include
16 #include
17 #include
18 #include
19 #include
20 #include
21 #include
22 #include
23 #include
24 #include
25 #include
26 #include
27
28 #define QUESIZE 16 /* 队列大小 */
29 #define BUFSIZE 512
30
31 struct request { /* client request string */
32 char *reqstr;
33 size_t reqlen; /* client request string len */
34 struct sockaddr_in *peer; /* client sending this request */
35 socklen_t sklen;
36 };
37
38 static struct request req_queue[QUESIZE];
39
40 static int idx_in;
41 static int idx_out;
42 static int nqueue;
43
44 int s; /* 服务器端套接字 */
45 struct sockaddr_in peer_addr; /* 客户端internet地址 */
46 static socklen_t socklen = sizeof(peer_addr);
47
48 static void bail(const char *on_what){
49 fputs(strerror(errno), stderr);
50 fputs(": ", stderr);
51 fputs(on_what, stderr);
52 fputc('/n', stderr);
53 exit(1);
54 }
55
56 /* SIGIO信号处理函数*/
57 void do_sigio (int signum)
58 {
59 int z;
60 struct request *p_req;
61 char buf[BUFSIZE];
62
63 for(;;){
64 /* 保存下一个客户端请求数据报的位置索引 */
65 p_req = &req_queue[idx_in];
66
67 if(nqueue >= QUESIZE){
68 write(STDOUT_FILENO, "request queue is full!/n", 23);
69 return;
70 }
71
72 z = recvfrom(s, /* 服务器套接字 */
73 p_req->reqstr, /* 接收缓存位置指针 */
74 BUFSIZE,
75 0,
76 (struct sockaddr *)p_req->peer, /* 客户端地址指针 */
77 &socklen);
78
79 if(z < 0) {
80 /* 设置服务器套接字于非阻塞模式,因此当无新数据
* 可读时,recvfrom立刻返回,并且错误代码设置为
81 * EWOULDBLOCK
82 */
83 if(errno == EWOULDBLOCK)
84 break;
85 else{
86 write (STDOUT_FILENO, "recvfrom error!/n", 16);
87 exit(1);
88 }
89 }
90 p_req->reqstr[z]=0;
91 p_req->reqlen = z;
92 nqueue++;
93
94 if(++idx_in >= QUESIZE)
95 idx_in = 0;
96 }
97 }
98
99 void init_queue()
100 {
101 for(int i = 0; i < QUESIZE; i++){
102 if((req_queue[i].reqstr = malloc(BUFSIZE)) == NULL ||
103 (req_queue[i].peer = malloc(socklen)) == NULL )
104 bail("init_queue");
105 req_queue[i].sklen = socklen;
106 }
107
108 idx_in = idx_out = nqueue = 0;
109 }
110 /* 注册SIGIO信号处理程序 */
111 static void install_sigio()
112 {
113 struct sigaction sigio_action;
114
115 memset(&sigio_action, 0, sizeof(sigio_action));
116 sigio_action.sa_flags = 0;
117
118 sigio_action.sa_handler = do_sigio;
119
120 if (sigaction(SIGIO, &sigio_action, NULL) == -1)
121 perror("Failed to set SIGIO");
122 }
123 /* 设置套接字为信号驱动I/O和非阻塞模式 */
124 void set_sockopt(int s, int flags)
125 {
126 fcntl(s, F_SETOWN, getpid());
127 if((flags = fcntl(s, F_GETFL, 0)) < 0)
128 bail("F_GETFL error");
129 flags |= O_ASYNC | O_NONBLOCK;
130 if(fcntl(s, F_SETFL, flags) < 0)
131 bail("F_SETFL error");
132 }
133
134 int main(int argc, char **argv){
135 int z;
136 char * srvr_addr = NULL;
137 int len_inet;
138 int portnumber;
139 int flags;
140
141 struct sockaddr_in srvaddr; /* 服务器internet地址 */
142 char dtfmt[BUFSIZE]; /* 日期-时间结果 */
143 time_t td; /* 当前时间和日期 */
144 struct tm tv; /* 日期时间结构体 */
145 sigset_t zeromask, newmask, oldmask;
146 struct request *p_req;
147
148 /*
149 * 若命令行提供了作为服务器地址和端口的参数,则使用参数
150 * 作为地址和端口,否则使用缺省的地址和端口
151 */
152 if(argc > 2){
153 srvr_addr = argv[1];
154
155 if((portnumber = atoi(argv[2]))<0){
156 fprintf(stderr, "Usage: %s portnumber/a/n", argv[0]);
157 exit(1);
158 }
159 }else{
160 srvr_addr = "0";
161 portnumber = 9000;
162 }
163
164 /* 创建数据报套接字 */
165 s = socket(AF_INET, SOCK_DGRAM, 0);
166 if(s == -1)
167 bail("socket()");
168
169 init_queue(); /* 初始化应用数据报接收队列 */
170 install_sigio(); /* 注册SIGIO信号处理程序 */
171 set_sockopt(s, flags); /* 设置非阻塞和SIGIO驱动I/O模式 */
172
173 /* 初始化套接字地址 */
174 memset(&srvaddr, 0, sizeof srvaddr);
175 srvaddr.sin_family = PF_INET;
176 srvaddr.sin_port = htons(portnumber);
177 if(!inet_aton(srvr_addr, &srvaddr.sin_addr))
178 bail("bad address.");
179
180 len_inet = sizeof(srvaddr);
181
182 /*
183 * 绑定套接字到指定地址和端口,于是客户端可以连接
184 * 到该服务器
185 */
186 z = bind(s, (struct sockaddr *)&srvaddr, len_inet);
187 if(z == -1)
188 bail("bind()");
189
190 sigemptyset(&zeromask);
191 sigemptyset(&newmask);
192 sigemptyset(&oldmask);
193 sigaddset(&newmask, SIGIO);
194
195 sigprocmask(SIG_BLOCK, &newmask, &oldmask);
196 for(;;) {
197 while(nqueue == 0)
198 /* unblock all and waiting for any signal */
199 sigsuspend(&zeromask);
200
201 /* 尽可能早解除对SIGIO 的阻塞 */
202 sigprocmask(SIG_SETMASK, &oldmask, NULL);
203
204 if(idx_out > QUESIZE)
205 idx_out = 0;
206
207 p_req = &req_queue[idx_out++];
208 /* 获得当前日期和时间 */
209 time(&td);
210 tv =*localtime(&td);
211
212 /*
213 * 根据获得的日期时间格式字符串要求,获得当前日期
214 * 和时间并格式化结果
215 */
216 strftime(dtfmt, /* formatted result */
217 sizeof dtfmt,
218 p_req->reqstr, /* 输入的日期时间格式串 */
219 &tv);
220
221 /* 将格式化结果返回给客户端 */
222 z = sendto(s, /* 服务器套接字 */
223 dtfmt, /* 返回结果 */
224 strlen(dtfmt),
225 0,
226 (struct sockaddr *)(p_req->peer), /* 对方地址 */
227 p_req->sklen);
228
229 if(z < 0)
230 bail("sendto()");
231
232 /* 更新临界变量,必须阻塞SIGIO信号 */
233 sigprocmask(SIG_BLOCK, &newmask, &oldmask);
234 nqueue--;
235 }
236
237 close(s);
238 return 0;
239 }
程序5.3 使用信号驱动
- 信号驱动io
- 信号驱动io
- 信号驱动式io小结
- Linux——信号驱动IO
- UNPv1第二十二章:信号驱动IO
- 网络模型:阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO
- IO五种模型(阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO)
- linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)
- 异步信号驱动IO实现回射服务器
- 信号驱动IO的UDP回射服务器
- 信号驱动IO的编程模型和多路复用IO的编程模型
- Linux C 网络编程——6. IO阻塞、非阻塞、复用、信号驱动、异步驱动
- Linux C 网络编程——6. IO阻塞、非阻塞、复用、信号驱动、异步驱动
- 字符设备驱动第八课----异步通知(信号驱动IO)
- linux基础——linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)
- 基于信号驱动式IO的监听套接字(O_ASYNC,O_NONBLOCK)
- 基于信号驱动式IO的监听套接字(O_ASYNC,O_NONBLOCK)
- Linux IO技术体系(阻塞 非阻塞 同步 异步 多路IO复用select poll epoll 信号驱动 异步)
- SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
- writev与readv
- 诗人之死
- 不要对知识与技术的遗忘感到焦虑
- 小谈c#数据库存取图片的三种方式
- 信号驱动io
- IE7编码解析错误导致页面变成白板
- pthread 互斥实例
- Apche LoadModule报错
- mysql笔记4
- 有关宏的使用总结 - 1 (VS编译器)
- strlen与sizeof的区别
- 显著改善桌面性能的200+行Kernel补丁
- 一个完整的Installshield安装程序实例—---基本设置二