从信号处理程序返回后再次读取块

Read blocks again after returning from signal handler

我编写了一个非常小的测试程序来检查 errno 的值,当 read() 被处理的信号中断时。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

void handler(int sig){
    printf("signal: %d\n", sig);
}

int main(){
    signal(SIGINT, handler);
    char arr[10];
    read(0, arr, 10);
    perror("emsg");
    return 0;
}

根据我所知道的一切和 read(2) 上的手册页,

EINTR  The call was interrupted by a signal before any data  was  read;
              see signal(7).

read 应 return -1 并将 errno 设置为 EINTR。但是,程序的输出表明它在从信号处理程序 returning 之后再次阻塞在 read

这对我来说完全没有意义,我不知道出了什么问题。

这是我得到的输出:

$ ./a.out
^Csignal: 2
^Csignal: 2
^Csignal: 2
^Csignal: 2
hello
emsg: Success

我的问题与this one不同。后者在任何地方都没有谈论系统调用中断时会发生什么。该讨论的关键是应该使用哪个。

此外,同一线程上的 this answersignal() 在下面调用 sigaction(),那么为什么两者在系统调用情况下的行为不同?

According to everything I know and the man page on read(2),

EINTR  The call was interrupted by a signal before any data  was  read;
              see signal(7).

read should return -1 and set errno to EINTR.

你读得太多了。 read() 可以 return -1 并设置 errnoEINTR,这种组合应该被解释为是被a打断了信号(在读取任何数据之前)。甚至可以肯定地说,如果 read 由于被信号中断而失败,那么这就是期望它显现的方式。但这并不意味着 read 在阻塞时收到信号的情况下一定会失败。

However, the output of the program suggests that it blocks again on read after returning from the signal handler.

这确实是一种可能的行为。特别是,它是 signal() 和信号处理的 BSD 语义的一部分,这是 Glibc 的默认设置(受 _BSD_SOURCE 功能测试宏的约束),所以这就是您默认情况下所期望的在 Mac(因为 BSD)和大多数 Linux(因为 Glibc)上。 the manual page for Glibc's implementation of signal() 的 "Portability" 部分详细介绍了这个问题。

Additionally this answer on the same thread says that signal() calls sigaction() underneath, so why is the behaviour in case of system calls different for the two?

sigaction() 的关键在于它提供了机制来指定处理已设置处置的信号的所有细节,包括,值得注意的是,

  • (某些)系统调用是否在信号处理后恢复;
  • 是否在收到信号后重置信号配置;和
  • 信号是否在其处理程序为 运行 时被阻塞。

因此,如果 signal() 的某些实现通过调用 sigaction() 进行操作,则在随后收到信号时,对于这些或其他辅助行为没有内在的影响。这也不意味着直接通过 sigaction() 为该信号注册处理程序必须产生与通过 signal() 间接注册该信号的处理程序相同的效果——这完全取决于 sigaction() 的参数。来电者可以选择。

请特别注意上面链接的联机帮助页中的建议:

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.

(强调在原文中。)

假设您希望系统调用 not 在被 SIGINT 中断并由您的处理程序处理后恢复,您可以从 sigaction 通过避免在标志中指定 SA_RESTART。很可能,您也不想要任何其他标志,因此这意味着(例如)像这样:

// with this form, flags and signal mask are initialized by default to all-bits-zero
sigaction(SIGINT, & (struct sigaction) { .sa_handler = handler }, NULL);