sigaction和signal之间有什么区别?

时间:2008-10-23 23:15:15

标签: c signals posix

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

9 个答案:

答案 0 :(得分:148)

使用sigaction()除非您有非常令人信服的理由不这样做。

signal()接口有其优先权(因此可用性),它在C标准中定义。然而,它有许多sigaction()避免的不良特性 - 除非您使用明确添加到sigaction()的标志,以允许它忠实地模拟旧的signal()行为。

  1. 当前处理程序执行时,signal()函数不一定(必然)阻止其他信号到达; sigaction()可以阻止其他信号,直到当前处理程序返回。
  2. signal()函数(通常)将信号操作重置为SIG_DFL(默认),几乎所有信号都可以。这意味着signal()处理程序必须重新安装自己作为其第一个操作。它还会在检测到信号和重新安装处理程序之间打开一个漏洞窗口,在此期间,如果信号的第二个实例到达,则会发生默认行为(通常是终止,有时带有偏见 - 也称为核心转储)。 / LI>
  3. signal()的确切行为因系统而异 - 标准允许这些变化。
  4. 这些通常是使用sigaction()代替signal()的充分理由。但是,sigaction()的界面无疑更加繁琐。

    无论你使用哪两个,都不要受到替代信号接口的诱惑,例如 sighold()sigignore()sigpause()sigrelse()。 它们名义上是sigaction()的替代品,但它们只是几乎没有标准化,并且存在于POSIX中以便向后兼容而不是严格使用。请注意,POSIX标准表明它们在多线程程序中的行为是未定义的。

    多线程程序和信号是另一个复杂的故事。 AFAIK,signal()sigaction()在多线程应用程序中都可以。

    Cornstalks observes

      

    signal()的Linux手册页说:

         

       signal() 在多线程流程中的影响尚未明确。

         

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

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

      

    如果进程是多线程的,或者进程是单线程的,并且执行信号处理程序而不是由于:

         
        
    • 调用abort()raise()kill()pthread_kill()sigqueue()以生成未阻止信号的流程
    •   
    • 未解除阻止并在取消阻止的呼叫之前传递的待处理信号将返回
    •   
         

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

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

    尽管如此,sigaction()在所有情况下都是首选 - 并且可移植的多线程代码应该使用sigaction(),除非它有不可理解的原因(例如“仅使用定义的函数”通过标准C“ - 是的,C11代码可以是多线程的。基本上这个答案的开头部分也是如此。

答案 1 :(得分:5)

它们是OS信号设施的不同接口。人们应该更喜欢使用sigaction来发出信号,因为signal()具有实现定义(通常易于竞争)的行为,并且在Windows,OS X,Linux和其他UNIX系统上表现不同。

有关详细信息,请参阅此security note

答案 2 :(得分:4)

对我来说,下面的这一行足以决定:

  

sigaction()函数提供了一个   更全面可靠   控制信号的机制;新   应用程序应该使用sigaction()   而不是signal()

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

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

答案 3 :(得分:3)

signal()是标准C,sigaction()不是。

如果你能够使用(也就是说,你在POSIX系统上),那么使用sigaction();未指定signal()是否重置处理程序,这意味着要进行处理,您必须在处理程序内再次调用signal()。更糟糕的是,有一场比赛:如果您快速连续获得两个信号,而第二个信号在您重新安装处理程序之前交付,您将拥有默认操作,这可能会杀死您的进程。 另一方面, sigaction()保证使用“可靠”的信号语义。您无需重新安装处理程序,因为它永远不会被重置。使用SA_RESTART,您还可以获得一些系统调用以自动重启(因此您无需手动检查EINTR)。 sigaction()有更多选项且可靠,因此鼓励使用它。

Psst ...不要告诉任何我告诉过你的人,但POSIX目前有一个函数bsd_signal(),它的作用类似于signal()但是给出了BSD语义,这意味着它是可靠的。它的主要用途是移植假定可靠信号的旧应用程序,POSIX不建议使用它。

答案 4 :(得分:2)

来自 signal(3) 手册页:

  

说明

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

两者都调用相同的底层工具。你可能不应该用两者来操纵单个信号的响应,但混合它们不应该导致任何破坏......

答案 5 :(得分:1)

我使用signal()因为它至少在理论上更便携。我会投票给任何能够提出没有POSIX兼容层且支持signal()的现代系统的评论者。

引用GLIBC documentation

  

可以同时使用信号和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自由文档许可证”的部分。

  

答案 6 :(得分:1)

我还建议在signal()上使用sigaction(),并希望再添加一个点。 sigaction()为您提供了更多选项,例如死亡进程的pid(可能使用siginfo_t结构)。

答案 7 :(得分:0)

从手册页signal(7)

  

过程控制信号可以传递给任何     当前未阻止信号的线程之一。     如果多个线程之一的信号畅通无阻,则     内核选择向其传递信号的任意线程。

我想说这个“问题”存在于signal(2)sigaction(2)中。 因此,请注意信号和pthread。

...和signal(2)似乎在使用glibc的Linux下调用了sigaction(2)

答案 8 :(得分:0)

简而言之:

sigaction()很好并且定义明确,但是它是Linux函数,因此仅在Linux上有效。 signal()不好并且定义不清,但是它是C标准函数,因此可以在任何东西上使用。

Linux手册页对此有何评论?

man 2 signal(见online here)指出:

  

signal()的行为在UNIX版本之间有所不同,并且在历史上在Linux的不同版本中也有所不同。避免使用它:改为使用sigaction(2)。请参见下面的可移植性。

     

可移植性         signal()的唯一可移植用法是将信号的处置设置为         SIG_DFL或SIG_IGN。使用signal()建立一个         信号处理程序因系统而异(并且POSIX.1明确允许         这种变化); 请勿将其用于此目的。

换句话说:不要使用signal()。请改用sigaction()

GCC怎么看?

  

兼容性注意:如上文对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


    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);
  …
}

要注意的主要链接:

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

另请参见:

  1. TutorialsPoint C ++信号处理[具有出色的演示代码]:https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm