我正准备向我们这里的应用程序添加一个额外的信号处理程序,我注意到作者曾经sigaction()
设置过其他信号处理程序。我打算用signal()
. 为了遵循约定,我应该使用sigaction()
,但如果我从头开始编写,我应该选择哪个?
9 回答
sigaction()
除非您有非常令人信服的理由不这样做,否则请使用。
该signal()
接口有利于它的古老(因此可用性),它是在 C 标准中定义的。然而,它有一些sigaction()
可以避免的不良特征——除非你使用明确添加的标志sigaction()
来让它忠实地模拟旧的signal()
行为。
- 当当前处理程序正在执行时,该
signal()
函数不会(必然)阻止其他信号到达;sigaction()
可以阻塞其他信号,直到当前处理程序返回。 - 该
signal()
功能(通常)将几乎所有信号的信号操作重置回SIG_DFL
(默认)。这意味着signal()
处理程序必须重新安装自身作为其第一个操作。它还会在检测到信号和重新安装处理程序之间打开一个漏洞窗口,在此期间,如果信号的第二个实例到达,则会发生默认行为(通常终止,有时带有偏见 - 也称为核心转储)。 - 系统之间的确切行为
signal()
不同——标准允许这些变化。
这些通常是使用sigaction()
而不是signal()
. 然而,sigaction()
不可否认的界面更加繁琐。
无论您使用这两者中的哪一个,都不要被替代信号接口所诱惑,
例如
sighold()
、
和
。它们名义上是. 请注意,POSIX 标准表示它们在多线程程序中的行为是未定义的。sigignore()
sigpause()
sigrelse()
sigaction()
多线程程序和信号是另一个复杂的故事。 AFAIK,在多线程应用程序中都可以signal()
。sigaction()
Linux手册页
signal()
说:
signal()
在多线程进程中的影响是未指定的。因此,我认为
sigaction()
是唯一可以在多线程进程中安全使用的。
那很有意思。在这种情况下,Linux 手册页比 POSIX 更严格。POSIX 指定signal()
:
如果进程是多线程的,或者如果进程是单线程的并且执行了信号处理程序,而不是作为以下结果:
- 进程调用
abort()
,raise()
,kill()
,pthread_kill()
, 或sigqueue()
生成未被阻塞的信号- 一个挂起的信号在解除阻塞的调用返回之前被解除阻塞并被传递
如果信号处理程序引用的对象不是
errno
具有静态存储持续时间的任何对象,而不是通过将值分配给声明为的对象volatile sig_atomic_t
,或者如果信号处理程序调用本标准中定义的任何函数而不是列出的函数之一,则行为未定义信号概念。
因此 POSIX 明确规定signal()
了多线程应用程序中的行为。
尽管如此,sigaction()
基本上在所有情况下都是首选——并且应该使用可移植的多线程代码,sigaction()
除非有压倒性的理由不能这样做(例如“只使用标准 C 定义的函数”——是的,C11 代码可以是多线程的-线程)。这基本上也是这个答案的开头段落所说的。
简而言之:
sigaction()
很好并且定义明确,但它是一个 Linux 函数,因此它只能在 Linux 上运行。signal()
不好且定义不明确,但它是 C 标准函数,因此它适用于任何东西。
Linux 手册页对此有何评论?
man 2 signal
(在这里在线查看)状态:
signal() 的行为因 UNIX 版本而异,并且在历史上因 Linux 的不同版本而异。避免使用:
sigaction(2)
改为使用。请参阅下面的可移植性。
可移植性 signal() 的唯一可移植用途是将信号的处置设置为 SIG_DFL 或 SIG_IGN。使用 signal() 建立信号处理程序时的语义因系统而异(POSIX.1 明确允许这种变化);不要将其用于此目的。
换句话说:不要使用signal()
. 改用sigaction()
!
海合会怎么想?
兼容性说明:如上所述
signal
,应尽可能避免使用此功能。sigaction
是首选方法。
来源:https ://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
所以,如果 Linux 和 GCC 都说不使用signal()
,而是使用sigaction()
,这就引出了一个问题:我们到底是如何使用这个令人困惑sigaction()
的东西的!?
用法示例:
在此处阅读 GCC 的优秀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()
#define LOG_LOCATION __FILE__, __LINE__, __func__ // Format: const char *, unsigned int, const char *
#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;\n"
" 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()
,即使它不是附加信号处理程序的好方法,如上所述,知道如何使用它仍然很好。
这是复制粘贴的 GCC 演示代码,因为它几乎与它将获得的一样好:
#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);
…
}
要注意的主要链接:
- 标准信号:https ://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
- 终止信号:https ://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
- 基本信号处理,包括官方 GCC
signal()
使用示例:https ://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling - 官方 GCC
sigaction()
使用示例:https ://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html - 信号集,包括
sigemptyset()
和sigfillset()
;我仍然不完全理解这些,但知道它们很重要:https ://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html
也可以看看:
对我来说,下面这行足以决定:
sigaction() 函数提供了更全面、更可靠的信号控制机制;新应用程序应该使用 sigaction() 而不是 signal()
http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07
无论您是从头开始还是修改旧程序,sigaction 都应该是正确的选择。
它们是操作系统信号设施的不同接口。如果可能的话,应该更喜欢使用 sigaction 来发出信号,因为 signal() 具有实现定义的(通常是竞争倾向)行为,并且在 Windows、OS X、Linux 和其他 UNIX 系统上表现不同。
有关详细信息,请参阅此安全说明。
signal() 是标准 C, sigaction() 不是。
如果您能够使用其中任何一个(也就是说,您在 POSIX 系统上),那么使用 sigaction(); 未指定 signal() 是否重置处理程序,这意味着要便携,您必须在处理程序内再次调用 signal()。更糟糕的是有一场竞赛:如果你连续快速收到两个信号,而第二个是在你重新安装处理程序之前传递的,你将获得默认操作,这可能会终止你的进程。 另一方面,sigaction()保证使用“可靠”的信号语义。您无需重新安装处理程序,因为它永远不会被重置。使用 SA_RESTART,您还可以获得一些系统调用以自动重新启动(因此您不必手动检查 EINTR)。 签名()有更多选择并且可靠,因此鼓励使用它。
Psst ...不要告诉任何人我告诉过你这个,但是 POSIX 目前有一个函数 bsd_signal() ,它的作用类似于 signal() 但提供 BSD 语义,这意味着它是可靠的。它的主要用途是移植假设可靠信号的旧应用程序,POSIX 不建议使用它。
从signal(3)
手册页:
描述
This signal() facility is a simplified interface to the more general sigaction(2) facility.
两者都调用相同的基础设施。您大概不应该用两个信号来操纵单个信号的响应,但是将它们混合不应导致任何中断...
我还建议使用 sigaction() 而不是 signal() 并想再补充一点。sigaction() 为您提供更多选项,例如死亡进程的 pid(可能使用 siginfo_t 结构)。
我会使用 signal() 因为它更便携,至少在理论上。我会投票支持任何能够提出没有 POSIX 兼容层并支持 signal() 的现代系统的评论者。
引用GLIBC 文档:
可以在单个程序中同时使用 signal 和 sigaction 函数,但您必须小心,因为它们可能会以稍微奇怪的方式进行交互。
sigaction 函数比 signal 函数指定了更多的信息,因此 signal 的返回值无法表达 sigaction 的全部可能性。因此,如果您使用信号保存并稍后重新建立操作,它可能无法正确重新建立使用 sigaction 建立的处理程序。
为避免出现问题,如果您的程序完全使用 sigaction,请始终使用 sigaction 来保存和恢复处理程序。由于 sigaction 更通用,它可以正确保存和重新建立任何动作,无论它最初是用信号还是 sigaction 建立的。
在某些系统上,如果您使用信号建立一个操作,然后使用 sigaction 对其进行检查,那么您获得的处理程序地址可能与您使用信号指定的地址不同。它甚至可能不适合用作带有信号的动作参数。但是您可以依靠将其用作 sigaction 的参数。这个问题在 GNU 系统上永远不会发生。
因此,您最好在单个程序中始终如一地使用其中一种机制。
可移植性注意:基本信号功能是 ISO C 的一个特性,而 sigaction 是 POSIX.1 标准的一部分。如果您担心对非 POSIX 系统的可移植性,那么您应该改用信号函数。
版权所有 (C) 1996-2008 Free Software Foundation, Inc.
根据 GNU 自由文档许可证 1.2 版或自由软件基金会发布的任何更高版本的条款,允许复制、分发和/或修改本文档;没有不变的部分,没有封面文本,也没有封底文本。许可证的副本包含在标题为“GNU 自由文档许可证”的部分中。
来自手册页信号(7)
进程导向的信号可以被传递到当前没有阻塞信号的任何一个线程。如果多个线程的信号被解除阻塞,那么内核选择一个任意线程来传递信号。
我会说这个“问题”存在于signal(2)和sigaction(2)中。所以要小心信号和pthreads。
...并且signal(2)似乎在 Linux 中使用 glibc调用sigaction(2) 。