问题描述
环境:类似RedHat的发行版,2.6.39内核,glibc 2.12.
Environment: a RedHat-like distro, 2.6.39 kernel, glibc 2.12.
我完全希望,如果在accept()进行期间传递了一个信号,accept应该会失败,从而导致errno == EINTR.但是,我的不这样做,我想知道为什么.下面是示例程序和strace输出.
I fully expect that if a signal was delivered while accept() was in progress, accept should fail, leaving errno==EINTR. However, mine doesn't do that, and I'm wondering why. Below are the sample program, and strace output.
#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 ...>
推荐答案
就在我要发布此帖子时,strace输出中的"SA_RESTART"标志引起了我的注意. signal(2)手册页说,signal()"...使用提供BSD语义的标志调用sigaction(2)..." 从glibc2开始.
Just when I was about to post this, the "SA_RESTART" flag in strace output caught my attention. signal(2) man page says that signal() "...calls sigaction(2) using flags that supply BSD semantics..." starting from glibc2.
SA_RESTART标志"...使某些系统调用可跨信号重新启动..." ,这隐藏了重新启动用户呼叫的过程.因此,这不是对accept()特定的,其他许多系统调用也会受到影响,而不是明确列出了哪些系统调用.
The SA_RESTART flag "...makes certain system calls restartable across signals...", which hides the process of restarting a call from the user. So, this is not specific to accept(), a number of other system calls are also affected, not that there is a clear list of which ones.
因此,如果您需要对可能被系统调用阻塞的线程的信号做出反应,则应使用sigaction()设置信号处理程序,而不要使用signal().下面是经过修改的示例程序,正是该程序可以做到这一点,以供参考.
So, if you need to react to a signal from a thread that may be blocked on a system call, you should use sigaction() to set your signal handlers, and not signal(). Below is the modified sample program that does exactly that, for reference.
#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,
这篇关于为什么Linux accept()不返回EINTR?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!