我想使用 sigaction(),但我遇到了问题

I want to use sigaction(), but I've a problem

我想制作一个程序 如果进程收到 SIGQUIT,打印一些消息并终止进程。 这是我的代码,

void signal_handler(int num)
{
  printf(Received SIGQUIT\n");
  kill(getpid(), SIGINT);
}

int main()
{
  struct sigaction sig;
  sig.sa_handler = signal_handler;
  sigemptyset(&sig.sa_mask);
  sig.sa_flags = 0;
  sigaction(SIGQUIT, &sig, NULL);

  for(;;) {
    printf("Input SIGQUIT : ");
  }

  return 0;
}

我的预期结果是

输入 SIGQUIT: ^\ 收到 SIGQUIT

并终止进程,

但实际结果是

enter image description here

在收到 SIGQUIT

之前未打印消息 "Input SIGQUIT:"

我想知道为什么..

首先:您引用的代码无法编译。请post验证码; 这会增加您获得答案的机会。

我可以自由地将您的问题重新表述为

如何优雅地终止程序?

我知道,不应该重新表述问题。但为了理智,我有 传播 异步信号传递是错误的说法 那是 UNIX 诞生时 发明。请继续阅读以下内容 对原因和一些替代方案的一些解释。但首先,

TL;DR: 不使用异步信号传递。很难得到 异步信号传递对-有很多陷阱(感谢 @Shawn 征求意见)。而是同步等待信号 到达。这是它的样子,

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


int main(void)
{
    int error;

    // setup set of signals that are meant to terminate us
    sigset_t termination_signals;
    sigemptyset(&termination_signals);
    sigaddset(&termination_signals, SIGTERM);
    sigaddset(&termination_signals, SIGINT);
    sigaddset(&termination_signals, SIGQUIT);

    // block asynchronous delivery for those
    error = sigprocmask(SIG_BLOCK, &termination_signals, NULL);
    if (error) {
        perror("sigprocmask(SIGTERM|SIGINT|SIGQUIT)");
        exit(1);
    }

    // wait for one of these signals to arrive. EINTR handling is
    // always good to have in larger programs. for example, libraries
    // might make use of signals in their own weird way - thereby
    // disturbing their users most impolitely by interrupting every
    // operation they synchronously wait for.
    while (1) {
        int sig;
        error = sigwait(&termination_signals, &sig);
        if (error && errno == EINTR) {
            perror("sigwait");
            continue;
        }

        printf("received termination signal %d\n", sig);
        break;
    }

    return 0;
}

异步信号传递

信号是穷人的通知。这就像扔整数 过程中的值(具有预定义的含义)。本来,像他们 正在发明 UNIX,他们必须想出一个通知机制 在进程之间使用。他们这样做的方式类似于 当时流行的通知机制:中断。 (虽然这可能 听起来很愤世嫉俗,我敢打赌我离得不远。)

在你真正决定走那条路之前要问自己的问题:

  1. 我真的知道我需要它吗?
  2. 有什么后果?
  3. 有其他选择吗?

虽然我无法帮助 1.,但这里有一些关于 2. 和 3. 的信息

异步信号处理的后果

异步信号传递导致您的程序进入 某种形式的并行性:信号处理程序 中断 正常 程序流程。这种中断可能不会发生在时间 节目是为他们准备的。

中断机制与您可能知道的相当 最高级别的多线程。这两者的共同点 可能是,“如果你不小心你就死了,但你可能 甚至不知道。竞争条件。

异步信号传递与 多线程,除了增加死亡率。

好像这还不够,还有中断系统的概念 调用.

基本原理是这样的(取自肯·汤普森和丹尼斯·里奇之间的假设对话,就在大纪元之前),

Q: now we have defined a notification mechnism between processes (asynchronous signal delivery), and jumped through hoops to call a user supplied function to handle delivery.

Now what if the target process sleeps? Waits for something to happen? Timer? Data from a serial port maybe?

A: let's wake him up!

这样做的效果是阻止系统调用(比如从 TCP 连接 - 这些当时还没有 - ,或者来自 串行端口)被中断。当您从套接字读取时,或者 否则阻塞直到某些事件发生,调用 return 非零, 使用全局 errno 变量(早期的另一个神器 七十年代)被设置为 EINTR.

我仍然不清楚当 目标进程有多个线程,每个线程阻塞 某物。我希望 每个 这样的阻塞调用都会被中断。 不幸的是行为不一致,甚至在 Linuxen 上也不一致,让 单独其他我很久没用过的 UNIXen。我不是一个 标准律师,但仅此一项就让我 运行 远离这个奥术 机制。

也就是说,如果您还没有运行离开,请通知您自己 关于确切的定义。在我看来,最好的起点是 是阅读 man 7 signal 手册 页。不容易 虽然阅读,但准确。

接下来,要知道在中断服务程序中可以做什么,err 信号处理函数,通读 man 7 signal-safety 手动 页。你会 看到,例如,以下 none 是安全的:

  • 没有printf()。你在信号处理程序中使用它,所以请 不。 printf() 的 None 个兄弟姐妹,这样的 fprintf() 是安全的 任何一个。 None 个 <stdio.h>
  • <pthread.h> 不安全。您不能修改线程安全数据 结构,因为你会无意中锁定互斥量或信号(双关语 预期的)条件变量,或使用其他一些线程 机制.

备选方案

同步等待信号。我上面引用的代码就是这样做的。

事件驱动编程。以下是Linux具体的

其基础是某种形式的事件循环。图形用户界面工具包 以这种方式工作,所以如果你的代码是这样一个更大的画面的一部分,你 可以将 基于文件描述符的信号通知 挂钩到某物中 那已经存在了。

事件源本质上是文件描述符。套接字以这种方式工作。见 man signalfd 如何创建一个文件描述符来吐出信号通知。

事件循环在其基础上使用以下系统调用之一 (从最简单到最强大的顺序),

自管技巧。参见here;这通常在您的程序基于事件时使用,但您不能使用上面的 Linux-特定 signalfd()