什么是 sigaction 和信号的区别?

我正要添加一个额外的信号处理程序,我们在这里有一个应用程序,我注意到,作者已经使用 sigaction()设置其他信号处理程序。我打算用 signal()。为了遵循惯例,我应该使用 sigaction(),但是如果我从头开始写,我应该选择哪个?

72618 次浏览

我会使用信号() ,因为它更便携,至少在理论上是这样。我会投票支持任何能够提出一个现代系统的评论者,这个系统没有 POSIX 兼容层,并且支持信号()。

引自 GLIBC 文档:

可以同时使用内部的信号和 sigaction 函数 一个单一的程序,但你必须小心,因为他们可以 以稍微奇怪的方式相互作用。

Sigaction 函数比信号指定更多的信息 函数,因此信号的返回值不能表示完整的 因此,如果你使用信号 保存并稍后重新建立一个操作,它可能无法 适当地重新建立与 sigaction 建立的处理程序。

为了避免出现问题,始终使用 sigaction 保存 并恢复处理程序,如果您的程序使用 sigaction Sigaction 是更一般的,它可以适当地保存和重新建立任何 行动,无论它最初是否与 信号或信号。

在一些系统上,如果你用信号建立一个动作,然后 用 sigaction 检查它,您得到的处理程序地址可能不会 与你指定的信号相同。它甚至可能不是 适合作为有信号的动作参数使用。但是你可以依赖 这个问题从来没有发生过 在 GNU 系统上。

所以,你最好使用其中一种机制 在一个单一的程序中始终如一。

便携性注意: 基本信号功能是 ISO C 的一个特点, 而 sigaction 是 POSIX.1标准的一部分 考虑到对非 POSIX 系统的可移植性,那么您应该 使用信号函数代替。

版权所有(C)1996-2008年自由软件基金会。

授予复制、分发和/或修改此 根据 GNU自由文档许可证的条款, 版本1.2或自由软件发布的任何更新版本 基础; 没有不变部分,没有封面文本, 许可证的副本包括在 题为「 GNU自由文档许可证」的部分。

来自 signal(3)手册页:

描述

 This signal() facility is a simplified interface to the more
general sigaction(2) facility.

两者都调用相同的底层工具。你大概不应该同时操纵两个单一信号的响应,但是混合它们不应该造成任何破坏..。

它们是操作系统信号设备的不同接口。如果可能的话,人们应该更喜欢使用 sigaction 来发送信号,因为 credit ()具有实现定义的(通常容易出现竞争)行为,并且在 Windows、 OS X、 Linux 和其他 UNIX 系统上的行为不同。

有关详细信息,请参阅此 保安字条

使用 sigaction(),除非你有非常有说服力的理由不这样做。

signal()接口具有古老的特性(因此也具有可用性) ,而且它是在 C 标准中定义的。尽管如此,它还是有一些 sigaction()避免的不良特性——除非您使用明确添加到 sigaction()的标志,以允许它忠实地模拟旧的 signal()行为。

  1. 在当前处理程序执行时,signal()函数不(必然)阻止其他信号到达; sigaction()可以阻止其他信号,直到当前处理程序返回。
  2. 对于几乎所有的信号,signal()函数(通常)将信号操作重置回 SIG_DFL(默认值)。这意味着 signal()处理程序必须重新安装自己作为第一个操作。它还打开了一个漏洞窗口,从检测到信号到重新安装处理程序,在此期间,如果信号的第二个实例到达,就会发生默认行为(通常终止,有时带有偏见——又称核心转储)。
  3. signal()的确切行为因系统而异ーー而标准允许这些变化。

这些都是使用 sigaction()而不是 signal()的好理由。然而,不可否认的是,sigaction()的接口更加精确。

无论您使用哪种方法,都不要受其他信号接口的诱惑,例如 sighold() , sigignore() , sigpause() sigrelse() . 它们名义上是 sigaction()的替代品,但是它们只是勉强标准化,并且在 POSIX 中出现是为了向后兼容,而不是为了认真使用。注意,POSIX 标准表示它们在多线程程序中的行为是未定义的。

多线程程序和信号是另一个完全复杂的故事

玉米秆 观察:

signal()的 Linux 手册页面显示:

  在一个多线程进程中,< strong > signal() 的效果是不确定的。

因此,我认为 sigaction()是唯一可以在多线程进程中安全使用的。

这很有趣。在这种情况下,Linux 手册页比 POSIX 限制更多。 POSIX 为 signal()指定:

如果进程是多线程的,或者如果进程是单线程的,并且执行了信号处理程序,而不是作为以下结果:

  • 调用 abort()raise()kill()pthread_kill()sigqueue()以生成未阻塞的信号的进程
  • 解除阻塞的挂起信号,在调用解除阻塞并返回之前发送

如果信号处理程序引用除 errno以外的任何具有静态存储持续时间的对象,而不是通过为声明为 volatile sig_atomic_t的对象赋值,或者如果信号处理程序调用本标准中定义的任何函数(信号概念中列出的函数之一除外) ,则该行为是未定义的。

因此,POSIX 清楚地指定了多线程应用程序中 signal()的行为。

尽管如此,sigaction()在基本上所有情况下都是首选ーー可移植的多线程代码应该使用 sigaction(),除非有压倒性的理由(比如“只使用由标准 C 定义的函数”ーー而且是的,C11代码可以是多线程的)。这基本上就是这个答案的开头段落所说的。

对我来说,下面这句话足以决定:

Sigaction ()函数提供一个 更全面,更可靠 控制信号的机械装置 应用程序应该使用 sigaction () 而不是信号

Http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07

无论是从头开始还是修改旧程序,sigaction 都应该是正确的选择。

信号()是标准的 C,sigaction ()不是。

如果您能够使用任何一种方法(即,您在 POSIX 系统上) ,那么使用 sigaction () ; 它没有指定 signal ()是否重置处理程序,这意味着为了便于移植,您必须在处理程序中再次调用 signal ()。更糟糕的是,这里有一个竞赛: 如果你快速连续获得两个信号,并且在重新安装处理程序之前传递了第二个信号,那么你将会有默认的动作,这可能会终止你的进程。 另一方面,sigaction () 保证使用“可靠的”信号语义。您不需要重新安装处理程序,因为它永远不会被重新设置。使用 SA _ RESTART,您还可以获得一些系统调用来自动重新启动(这样您就不必手动检查 EINTR)。 Sigaction () 有更多选项,并且可靠,因此鼓励使用它。

Psst... 不要告诉任何人我告诉过您这些,但是 POSIX 目前有一个函数 BSD _ sign () ,它的作用类似于 sign () ,但是提供了 BSD 语义,这意味着它是可靠的。它的主要用途是移植假定信号可靠的旧应用程序,POSIX 不推荐使用它。

我还建议使用 sigaction () over information () ,并希望再添加一个点。 Sigaction ()为您提供了更多选项,比如死亡进程的 pid (可以使用 siginfo _ t struct)。

来自手册页 信号(7)

过程导向的信号可以传递给任何 当前没有阻塞信号的线程之一。 如果多个线程的信号未阻塞,则 内核选择一个任意的线程来传递信号。

我认为 讯号(2)签订协定(2)都存在这个“问题”。 因此,要小心使用信号和 pthread。

讯号(2)似乎在 Linux 下面用 glibc 调用 签订协定(2)

简而言之:

sigaction()(参见 给你给你)很好,定义良好,但是是一个 POSIX 函数,因此它只能在 Linux 或 POSIX 系统上工作。signal()(参见 给你给你)很糟糕,定义也很差,但是它是 C 标准函数,所以它可以在任何情况下工作。

Linux 手册页对此有什么要说的吗?

man 2 signal(参见 在这里)指出:

signal()的行为在不同的 UNIX 版本中有所不同,在不同的 Linux 版本中也有所不同。避免使用它: 改用 sigaction(2)。请参阅下面的便携性。

便携性

signal()唯一可移植的用途是将信号的配置设置为 使用 signal()建立 信号处理程序因系统而异(并且 POSIX.1明确允许 不要将其用于此目的。

换句话说: 做 没有使用 signal()。使用 sigaction()代替!

这一立场在下一行中得到了重申:

POSIX.1通过指定 sigaction(2)解决了可移植性问题,sigaction(2)在调用信号处理程序时提供了对语义的显式控制; 使用那个接口而不是 signal()

海湾合作委员会怎么看?

来自 https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling(加重语气) :

兼容性注意: 如上所述,对于 signal如果可能的话,应该避免这个函数。 sigaction是首选的方法

因此,如果 Linux 和 GCC 都说不要使用 signal(),而是使用 sigaction(),那么就会产生一个问题: 我们到底要怎么使用这个令人困惑的 sigaction()! ?

用法例子:

在这里阅读海湾合作委员会的优秀 signal()例子: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

他们优秀的 sigaction()例子在这里: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

在阅读了这些页面之后,我想出了 sigaction()的以下技巧:

1. sigaction(),因为它是连接信号处理器的正确方法,如上所述:

#include <errno.h>  // errno
#include <signal.h> // sigaction()
#include <stdio.h>  // printf()
#include <string.h> // strerror()


// Format: const char *, unsigned int, const char *
#define LOG_LOCATION __FILE__, __LINE__, __func__
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "


/// @brief      Callback function to handle termination signals, such as
///             Ctrl + C
/// @param[in]  signal  Signal number of the signal being handled by this
///             callback function
/// @return     None
static void termination_handler(const int signal)
{
switch (signal)
{
case SIGINT:
printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
break;
case SIGTERM:
printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n",
signal);
break;
case SIGHUP:
printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
break;
default:
printf("\nUnk signal (%i) caught.\n", signal);
break;
}


// DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.


    

exit(signal);
}


/// @brief      Set a new signal handler action for a given signal
/// @details    Only update the signals with our custom handler if they are NOT
///     set to "signal ignore" (`SIG_IGN`), which means they are currently
///     intentionally ignored. GCC recommends this "because non-job-control
///     shells often ignore certain signals when starting children, and it is
///     important for children to respect this." See
///     https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
///     and
///     https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
///     Note that termination signals can be found here:
///     https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in]  signal  Signal to set to this action
/// @param[in]  action  Pointer to sigaction struct, including the callback
///     function inside it, to attach to this signal
/// @return     None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
struct sigaction old_action;


// check current signal handler action to see if it's set to SIGNAL IGNORE
sigaction(signal, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
// set new signal handler action to what we want
int ret_code = sigaction(signal, action, NULL);
if (ret_code == -1)
{
printf(LOG_FORMAT_STR "sigaction failed when setting signal to "
"%i; errno = %i: %s\n",
LOG_LOCATION, signal, errno, strerror(errno));
}
}
}


int main(int argc, char *argv[])
{
//...


// Register callbacks to handle kill signals; prefer the Linux function
// `sigaction()` over the C function `signal()`: "It is better to use
// sigaction if it is available since the results are much more reliable."
// Source:
// https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
// and
// https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
// See here for official gcc `sigaction()` demo, which this code is modeled
// after:
// https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html


// Set up the structure to specify the new action, per GCC's demo.
struct sigaction new_action;
new_action.sa_handler = termination_handler; // set callback function
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;


// SIGINT: ie: Ctrl + C kill signal
set_sigaction(SIGINT, &new_action);
// SIGTERM: termination signal--the default generated by `kill` and
// `killall`
set_sigaction(SIGTERM, &new_action);
// SIGHUP: "hang-up" signal due to lost connection
set_sigaction(SIGHUP, &new_action);


//...
}

2.而对于 signal()来说,即使它不是一个附加信号处理程序的好方法,如上所述,知道如何使用它仍然是很好的。

下面是海湾合作委员会的演示代码复制粘贴,因为它大约是最好的,因为它将得到:

#include <signal.h>


void
termination_handler (int signum)
{
struct temp_file *p;


for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}


int
main (void)
{
…
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, termination_handler) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
…
}

应注意的主要环节:

  1. 标准信号: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
  2. 终止信号: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  3. 基本信号处理,包括正式的 GCC signal()使用示例: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  4. 官方 GCC sigaction()用法示例: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  5. 信号集,包括 sigemptyset()sigfillset(); 我仍然不明白这些确切的,但知道他们是重要的: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

参见:

  1. 我在 “如何手动发送任何信号到任何正在运行的进程”和“如何在您的程序中捕获任何信号”(例如: 在 Bash 中)上的回答。
  2. TutorialsPoint C + + 信号处理[具有优秀的演示代码] : https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  3. Https://www.tutorialspoint.com/c_standard_library/signal_h.htm