为什么这个 alarm() 系统调用不中断 read()?
Why does this alarm() syscall does not interrupt read()?
我发现了一个问题,它要求解释以下程序的行为:
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void sig_alrm(int n) {
write(2, "ALARM!\n", 7);
return;
}
int main() {
int fd[2], n;
char message[6], *s;
s = "HELLO\n";
signal(SIGALRM, sig_alrm);
pipe(fd);
if (fork() == 0) {
close(fd[1]);
alarm(3);
while ((n = read(fd[0], message, 6)) > 0);
alarm(0);
exit(0);
}
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
while (1)
write(1, s, 6);
}
它基本上是一个 parent 进程,共享管道不断地通过管道发送 HELLO\n 到 child。 child 在三秒内设置一个 SIGALRM
,它将被 sig_alrm 函数捕获,然后继续从管道无限期地读取。
如果我理解正确,SIGALRM
应该中断 read()
系统调用,导致它在到达时出错,进而导致 child 立即退出具有默认行为的警报,并且 parent 也会由于 SIGPIPE
.
而结束
问题是我试图 运行 代码,在 SIGALRM
到达 child 后,两个进程继续愉快地从管道读写。
我对信号行为有什么误解吗?
signal
的行为因平台而异,您应该改用其后继者 sigaction
, which was standardized in POSIX-1.1988。
在您的平台上,根据您的构建说明,signal()
以“可重新启动”的方式安装用户处理程序,这意味着可中断的系统调用将简单地重新启动,而不是因 EINTR 而失败。您的 read
被处理程序中断,但随后恢复。某些平台 and/or 构建语义不会这样做。
sigaction
通过提供一个标志 SA_RESTART 来解决这个歧义,它控制中断的系统调用实际上是否重新启动。它也标准化了其他历史上不同的行为。
就其价值而言,当我在较旧的 Linux 系统上 gcc 编译您的代码时未指定任何功能宏,strace 显示 signal
实际上,是根据使用 SA_RESTART 的(实时)sigaction
调用实现的,这解释了您看到的行为:
$ strace -fe trace=\!write,read ./so65742182
....
munmap(0x7f8ddffaf000, 70990) = 0
rt_sigaction(SIGALRM, {0x013370, [ALRM], SA_RESTORER|SA_RESTART, ...
# ^^^^^^^^^^
pipe([3, 4])
....
我发现了一个问题,它要求解释以下程序的行为:
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void sig_alrm(int n) {
write(2, "ALARM!\n", 7);
return;
}
int main() {
int fd[2], n;
char message[6], *s;
s = "HELLO\n";
signal(SIGALRM, sig_alrm);
pipe(fd);
if (fork() == 0) {
close(fd[1]);
alarm(3);
while ((n = read(fd[0], message, 6)) > 0);
alarm(0);
exit(0);
}
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
while (1)
write(1, s, 6);
}
它基本上是一个 parent 进程,共享管道不断地通过管道发送 HELLO\n 到 child。 child 在三秒内设置一个 SIGALRM
,它将被 sig_alrm 函数捕获,然后继续从管道无限期地读取。
如果我理解正确,SIGALRM
应该中断 read()
系统调用,导致它在到达时出错,进而导致 child 立即退出具有默认行为的警报,并且 parent 也会由于 SIGPIPE
.
问题是我试图 运行 代码,在 SIGALRM
到达 child 后,两个进程继续愉快地从管道读写。
我对信号行为有什么误解吗?
signal
的行为因平台而异,您应该改用其后继者 sigaction
, which was standardized in POSIX-1.1988。
在您的平台上,根据您的构建说明,signal()
以“可重新启动”的方式安装用户处理程序,这意味着可中断的系统调用将简单地重新启动,而不是因 EINTR 而失败。您的 read
被处理程序中断,但随后恢复。某些平台 and/or 构建语义不会这样做。
sigaction
通过提供一个标志 SA_RESTART 来解决这个歧义,它控制中断的系统调用实际上是否重新启动。它也标准化了其他历史上不同的行为。
就其价值而言,当我在较旧的 Linux 系统上 gcc 编译您的代码时未指定任何功能宏,strace 显示 signal
实际上,是根据使用 SA_RESTART 的(实时)sigaction
调用实现的,这解释了您看到的行为:
$ strace -fe trace=\!write,read ./so65742182
....
munmap(0x7f8ddffaf000, 70990) = 0
rt_sigaction(SIGALRM, {0x013370, [ALRM], SA_RESTORER|SA_RESTART, ...
# ^^^^^^^^^^
pipe([3, 4])
....