为什么这个 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])
....