【问题标题】:Read blocks again after returning from signal handler [duplicate]从信号处理程序返回后再次读取块[重复]
【发布时间】:2019-12-13 08:05:32
【问题描述】:

我编写了一个非常小的测试程序来检查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 应该返回-1 并将errno 设置为EINTR。但是,程序的输出表明它在从信号处理程序返回后再次阻塞 read

这对我来说完全没有意义,我无法弄清楚出了什么问题。

这是我得到的输出:

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

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

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

【问题讨论】:

  • 使用signal 设置的信号在不同的类 Unix 系统之间可能有不同的行为。因此,您应该始终使用 sigaction 来指定所需的行为。
  • 在一个稍微相关的说明中,errno 的值几乎适用于所有系统调用未指定,除非之前的函数确实失败了。你总是应该在检查errnoperror 做的)之前检查失败。
  • 您也不能从信号处理程序中安全地调用printf()Footnote 188 of the C11 standard 甚至声明“因此,信号处理程序通常不能调用标准库函数。” POSIX allows you to safely call async-signal-safe functions。 Linux 上的异​​步信号安全函数列表可以在the signal-safety man page 上找到。请注意,printf() 不在这两个列表中。
  • @AndrewHenle 正如我所说,这只是一个示例程序,旨在演示 perror() 的使用。
  • 好的,根据this answersignal 在调用sigaction() 之前将sa_flags 设置为SA_RESTART。从而重启read()。投票以重复结束。

标签: c signals system-calls errno


【解决方案1】:

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

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

read 应该返回-1 并将errno 设置为EINTR

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

但是,程序的输出表明它再次阻塞 从信号处理程序返回后读取。

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

此外,同一线程上的这个答案说signal() 调用 sigaction() 在下面,那么为什么是系统情况下的行为 两者的调用不同?

sigaction() 的关键在于它提供了一种机制,用于指定处理已设置处置的信号的所有细节,尤其是,

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

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

特别注意上面链接的手册页中的建议:

signal() 的唯一可移植用途是设置信号的处置 到SIG_DFLSIG_IGN。语义当 使用signal() 建立信号处理程序因系统而异(POSIX.1 明确允许这种变化); 请勿将其用于此目的

(强调原文。)

假设您希望系统调用在被SIGINT 中断并由您的处理程序处理后恢复,您可以通过避免在标志中指定SA_RESTARTsigaction 获得它。最有可能的是,您也不想要任何其他标志,所以这意味着(例如)这样的事情:

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

【讨论】:

    猜你喜欢
    • 2012-01-08
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 2019-06-01
    • 2015-08-10
    • 1970-01-01
    • 2017-04-02
    • 1970-01-01
    相关资源
    最近更新 更多