环境:类似RedHat的发行版,2.6.39内核,glibc 2.12。
我完全希望如果在accept()正在进行时传递信号,则接受应该失败,留下errno == EINTR。但是,我的不这样做,我想知道为什么。下面是示例程序和strace输出。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
static void sigh(int);
int main(int argc, char ** argv) {
int s;
struct sockaddr_in sin;
if ((s = socket(AF_INET, SOCK_STREAM, 0))<0) {
perror("socket");
return 1;
}
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
if (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
perror("bind");
return 1;
}
if (listen(s, 5)) {
perror("listen");
}
signal(SIGQUIT, sigh);
while (1) {
socklen_t sl = sizeof(struct sockaddr_in);
int rc = accept(s, (struct sockaddr*)&sin, &sl);
if (rc<0) {
if (errno == EINTR) {
printf("accept restarted\n");
continue;
}
perror("accept");
return 1;
}
printf("accepted fd %d\n", rc);
close(rc);
}
}
void sigh(int s) {
signal(s, sigh);
unsigned char p[100];
int i = 0;
while (s) {
p[i++] = '0'+(s%10);
s/=10;
}
write(1, "sig ", 4);
for (i--; i>=0; i--) {
write(1, &p[i], 1);
}
write(1, "\n", 1);
}
strace输出:
execve("./accept", ["./accept"], [/* 57 vars */]) = 0
<skipped>
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 5) = 0
rt_sigaction(SIGQUIT, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, {SIG_DFL, [], 0}, 8) = 0
accept(3, 0x7fffe3e3c500, [16]) = ? ERESTARTSYS (To be restarted)
--- SIGQUIT (Quit) @ 0 (0) ---
rt_sigaction(SIGQUIT, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, 8) = 0
write(1, "sig ", 4sig ) = 4
write(1, "3", 13) = 1
write(1, "\n", 1
) = 1
rt_sigreturn(0x1) = 43
accept(3, ^C <unfinished ...>
答案 0 :(得分:1)
就在我即将发布此消息时,strace输出中的“SA_RESTART”标志引起了我的注意。 signal(2)手册页说signal()“...使用提供BSD语义的标志来调用sigaction(2)......”从glibc2开始。
SA_RESTART标志“...使某些系统调用可以跨信号重新启动......”,它隐藏了重新启动用户呼叫的过程。因此,这不是特定于accept(),其他一些系统调用也会受到影响,而不是有明确的列表。
因此,如果您需要对可能在系统调用中阻塞的线程的信号做出反应,则应使用sigaction()来设置信号处理程序,而不是signal()。以下是经过修改的示例程序,仅供参考。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
static void sigh(int);
static struct sigaction sa;
int main(int argc, char ** argv) {
int s;
struct sockaddr_in sin;
if ((s = socket(AF_INET, SOCK_STREAM, 0))<0) {
perror("socket");
return 1;
}
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
if (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
perror("bind");
return 1;
}
if (listen(s, 5)) {
perror("listen");
}
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sigh;
sigemptyset(&sa.sa_mask);
sigaction(SIGQUIT, &sa, 0);
while (1) {
socklen_t sl = sizeof(struct sockaddr_in);
int rc = accept(s, (struct sockaddr*)&sin, &sl);
if (rc<0) {
if (errno == EINTR) {
printf("accept restarted\n");
continue;
}
perror("accept");
return 1;
}
printf("accepted fd %d\n", rc);
close(rc);
}
}
void sigh(int s) {
sigaction(SIGQUIT, &sa, 0);
unsigned char p[100];
int i = 0;
while (s) {
p[i++] = '0'+(s%10);
s/=10;
}
write(1, "sig ", 4);
for (i--; i>=0; i--) {
write(1, &p[i], 1);
}
write(1, "\n", 1);
}
并且strace:
execve("./accept", ["./accept"], [/* 57 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 5) = 0
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0
accept(3, 0x7fffb626be90, [16]) = ? ERESTARTSYS (To be restarted)
--- SIGQUIT (Quit) @ 0 (0) ---
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0
write(1, "sig ", 4) = 4
write(1, "3", 13) = 1
write(1, "\n", 1) = 1
rt_sigreturn(0x1) = -1 EINTR (Interrupted system call)
write(1, "accept restarted\n", 17) = 17
accept(3,
答案 1 :(得分:1)
在Unix Network Programming本书中,有一节说明:
我们使用术语“慢速系统调用”来描述
accept
,我们使用 这个术语适用于任何可以永久阻止的系统调用。那就是 系统调用永远不会返回。大多数网络功能都属于 这个类别。例如,无法保证服务器的呼叫 如果没有将连接的客户端,则accept
将返回 到服务器。同样,我们的服务器调用图5.3中的read
如果客户端从不发送服务器的回送线,则永远不会返回。 慢速系统调用的其他示例是管道和管道的读写 终端设备。一个值得注意的例外是磁盘I / O,通常是这样 返回调用者(假设没有灾难性的硬件故障)。此处适用的基本规则是当进程被阻止时 一个缓慢的系统调用,该过程捕获信号和信号 处理程序返回,系统调用可以返回
EINTR
的错误。一些 内核自动重启一些中断的系统调用。对于 可移植性,当我们编写一个捕获信号的程序时(大多数 并发服务器捕获SIGCHLD
),我们必须为慢速系统做好准备 致电返回EINTR
。便携性问题是由 前面使用的限定词“can”和“some”,以及事实 支持POSIXSA_RESTART
标志是可选的。即使是 实现支持SA_RESTART
标志,并非全部中断 系统调用可以自动重启。大多数伯克利派生的 例如,实现从不自动重启select,和 其中一些实现从不重启accept
或recvfrom
。