首页 > 解决方案 > 我想使用 sigaction(),但我有问题

问题描述

我想制作一个程序,如果进程收到 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

并终止进程,

但实际结果是

在此处输入图像描述

在接收 SIGQUIT 之前未打印消息“输入 SIGQUIT:”

我想知道为什么。。

标签: csignalssigaction

解决方案


首先:您引用的代码无法编译。请发布有效代码;这会增加您获得答案的机会。

我会自由地将您的问题重新表述为,

如何优雅地终止程序?

我知道,不应该重新提出问题。但是为了理智起见,我不得不说异步信号传递是 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:现在我们已经定义了进程之间的通知机制(异步信号传递),并且跳过了循环调用用户提供的函数来处理传递。

现在如果目标进程休眠了怎么办?等着什么事情发生?计时器?来自串行端口的数据可能吗?

A:让我们叫醒他吧!

这样做的效果是阻塞系统调用(如从 TCP 连接读取 - 这些在当时不存在 - 或从串行端口)被中断。当您从套接字读取或以其他方式阻塞直到某个事件发生时,调用返回非零值,全局errno变量(七十年代早期的另一个工件)设置为EINTR.

当目标进程具有多个线程且每个线程阻塞某事时,我仍然不清楚预期的行为是什么。我希望每个这样的阻塞调用都会被中断。不幸的是,行为并不一致,甚至在 Linuxen 上也不一致,更不用说我很长时间没有使用的其他 UNIXen。我不是标准律师,但仅此一项就让我逃离了这种神秘的机制。

也就是说,如果您仍然没有逃跑,请告知自己确切的定义。在我看来,最好的起点是阅读man 7 signal 手册页。虽然不容易读,但准确。

接下来,要了解在中断服务例程、err 信号处理函数中可以做什么,请阅读man 7 signal-safety 手册页。例如,您会看到以下内容都不安全

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

备择方案

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

事件驱动编程。以下是特定于 Linux 的。

在此基础上是某种形式的事件循环。GUI 工具包以这种方式工作,因此如果您的代码是这样一个更大的图片的一部分,您可以将基于文件描述符的信号通知挂钩到已经存在的东西中。

事件源在其基础上是文件描述符。套接字以这种方式工作。请参阅man signalfd如何创建文件描述符以吐出信号通知。

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

自管绝招。见这里;这通常在您的程序基于事件时使用,但您不能使用signalfd()上面特定于 Linux 的情况。


推荐阅读