¿Cuál es la diferencia entre sigaction y señal?

13 minutos de lectura

avatar de usuario
mateo smith

Estaba a punto de agregar un controlador de señal adicional a una aplicación que tenemos aquí y noté que el autor había usado sigaction() para configurar los otros controladores de señal. iba a usar signal(). Para seguir la convención debo usar sigaction() pero si estuviera escribiendo desde cero, ¿cuál debería elegir?

avatar de usuario
jonathan leffler

Usar sigaction() a menos que tenga razones muy convincentes para no hacerlo.

Él signal() La interfaz tiene la antigüedad (y por lo tanto la disponibilidad) a su favor, y está definida en el estándar C. Sin embargo, tiene una serie de características indeseables que sigaction() evita, a menos que use las banderas agregadas explícitamente a sigaction() para permitirle simular fielmente el antiguo signal() comportamiento.

  1. Él signal() la función no bloquea (necesariamente) la llegada de otras señales mientras se ejecuta el controlador actual; sigaction() puede bloquear otras señales hasta que regrese el controlador actual.
  2. Él signal() La función (generalmente) restablece la acción de la señal de nuevo a SIG_DFL (predeterminado) para casi todas las señales. Esto significa que el signal() handler debe reinstalarse como su primera acción. También abre una ventana de vulnerabilidad entre el momento en que se detecta la señal y se reinstala el controlador, durante el cual, si llega una segunda instancia de la señal, se produce el comportamiento predeterminado (generalmente termina, a veces con perjuicio, también conocido como volcado del núcleo).
  3. El comportamiento exacto de signal() varía entre los sistemas, y los estándares permiten esas variaciones.

Estas son generalmente buenas razones para usar sigaction() en vez de signal(). Sin embargo, la interfaz de sigaction() es innegablemente más complicado.

Cualquiera de los dos que use, no se deje tentar por las interfaces de señal alternativas como
sighold(),
sigignore(),
sigpause() y
sigrelse(). Son nominalmente alternativas a sigaction(), pero apenas están estandarizados y están presentes en POSIX por compatibilidad con versiones anteriores en lugar de para un uso serio. Tenga en cuenta que el estándar POSIX dice que su comportamiento en programas de subprocesos múltiples no está definido.

Los programas y señales de subprocesos múltiples son otra historia complicada. AFAIK, ambos signal() y sigaction() están bien en aplicaciones de subprocesos múltiples.

Cornstalks observa:

La página del manual de Linux para signal() dice:

  Los efectos de signal() en un proceso de subprocesos múltiples no están especificados.

Por lo tanto, creo sigaction() es el único que se puede utilizar de forma segura en un proceso de subprocesos múltiples.

Eso es interesante. La página del manual de Linux es más restrictiva que POSIX en este caso. POSIX especifica para signal():

Si el proceso es de subprocesos múltiples, o si el proceso es de un solo subproceso y se ejecuta un controlador de señal que no sea como resultado de:

  • El proceso llamando abort(), raise(), kill(), pthread_kill()o sigqueue() para generar una señal que no esté bloqueada
  • Una señal pendiente que se desbloquea y se entrega antes de que regrese la llamada que la desbloqueó

el comportamiento no está definido si el controlador de señal se refiere a cualquier objeto que no sea errno con una duración de almacenamiento estática distinta de la asignación de un valor a un objeto declarado como volatile sig_atomic_to si el manejador de señales llama a cualquier función definida en este estándar que no sea una de las funciones enumeradas en Conceptos de señales.

Entonces POSIX especifica claramente el comportamiento de signal() en una aplicación de subprocesos múltiples.

Sin embargo, sigaction() es preferible en prácticamente todas las circunstancias, y el código portátil de subprocesos múltiples debe usar sigaction() a menos que haya una razón abrumadora por la que no pueda (como “solo usar funciones definidas por el estándar C”, y sí, el código C11 puede ser de subprocesos múltiples). Que es básicamente lo que también dice el párrafo inicial de esta respuesta.

  • Esta descripción de signal es en realidad del comportamiento de Unix System V. POSIX permite este comportamiento o el comportamiento BSD mucho más sensato, pero dado que no puede estar seguro de cuál obtendrá, es mejor usar sigaction.

    – R.. GitHub DEJA DE AYUDAR A ICE

    15 de agosto de 2011 a las 4:08

  • a menos que use las banderas agregadas explícitamente a sigaction() para permitirle simular fielmente el antiguo comportamiento de la señal(). ¿Qué banderas serían esas (específicamente)?

    – ChristianCuevas

    30 de octubre de 2014 a las 1:20


  • @AlexFritz: Principalmente SA_RESETHANDademás SA_NODEFER.

    –Jonathan Leffler

    30 de octubre de 2014 a las 1:28

  • @BulatM. Si no puedes usar sigaction()entonces está esencialmente obligado a usar la especificación C estándar para signal(). Sin embargo, eso le da un conjunto extremadamente pobre de opciones para lo que puede hacer. Puede: modificar (alcance del archivo) variables de tipo volatile sig_atomic_t; llame a una de las funciones de ‘salida rápida’ (_Exit(), quick_exit()) o abort(); llamar signal() con el número de señal actual como argumento de señal; devolver. Y eso es. No se garantiza que cualquier otra cosa sea portátil. Eso es tan estricto que la mayoría de las personas ignoran esas reglas, pero el código resultante es dudoso.

    –Jonathan Leffler

    27/10/2016 a las 16:14

  • Excelente sigaction() demostración de GCC ellos mismos: gnu.org/software/libc/manual/html_node/…; y excelente signal() demostración de GCC ellos mismos: gnu.org/software/libc/manual/html_node/…. Note que en el signal demo evitan cambiar el controlador de ignorar (SIG_IGN) si eso es lo que se configuró previamente intencionalmente.

    – Gabriel grapas

    12 de noviembre de 2019 a las 3:21


avatar de usuario
grapas gabriel

En breve:

sigaction() es bueno y está bien definido, pero es una función de Linux, por lo que solo funciona en Linux. signal() es malo y está mal definido, pero es una función estándar de C, por lo que funciona en cualquier cosa.

¿Qué tienen que decir las páginas man de Linux al respecto?

man 2 signal (Míralo en línea aquí) afirma:

El comportamiento de la señal () varía según las versiones de UNIX y también ha variado históricamente entre las diferentes versiones de Linux. Evitar su uso: usar sigaction(2) en cambio. Consulte Portabilidad a continuación.

Portabilidad El único uso portátil de signal() es establecer la disposición de una señal en SIG_DFL o SIG_IGN. La semántica cuando se usa signal() para establecer un controlador de señal varía según los sistemas (y POSIX.1 permite explícitamente esta variación); no lo use para este propósito.

En otras palabras: no use signal(). Usar sigaction() ¡en cambio!

¿Qué opina GCC?

Nota de compatibilidad: como se dijo anteriormente para signalesta función debe evitarse cuando sea posible. sigaction es el método preferido.

Fuente: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Entonces, si tanto Linux como GCC dicen que no se use signal()pero para usar sigaction() en cambio, eso plantea la pregunta: ¿cómo diablos usamos este confuso sigaction() ¿¡cosa!?

Ejemplos de uso:

Leer EXCELENTE de GCC signal() ejemplo aquí: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Y su EXCELENTE sigaction() ejemplo aquí: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Después de leer esas páginas, se me ocurrió la siguiente técnica para sigaction():

1. sigaction()ya que es la forma correcta de adjuntar un controlador de señal, como se describe anteriormente:

#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. Y para signal()aunque no es una buena manera de adjuntar un controlador de señal, como se describe anteriormente, es bueno saber cómo usarlo.

Aquí está el código de demostración de GCC copiado y pegado, ya que es tan bueno como lo será:

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

Los principales enlaces a tener en cuenta:

  1. Señales estándar: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
  2. Señales de terminación: https://www.gnu.org/software/libc/manual/html_node/Terminación-Señales.html#Terminación-Señales
  3. Manejo básico de señales, incluido GCC oficial signal() ejemplo de uso: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  4. CCG oficial sigaction() ejemplo de uso: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  5. Conjuntos de señales, incluidos sigemptyset() y sigfillset(); Todavía no entiendo esto exactamente, pero sé que son importantes: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Ver también:

  1. mi respuesta en “Cómo enviar manualmente cualquier señal a cualquier proceso en ejecución” y “Cómo atrapar cualquier señal en su programa” (por ejemplo, en Bash).
  2. TutorialesManejo de señales de Point C++ [with excellent demo code]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  3. https://www.tutorialspoint.com/c_standard_library/signal_h.htm

Para mí, esta línea a continuación fue suficiente para decidir:

La función sigaction() proporciona un mecanismo más completo y confiable para controlar señales; las nuevas aplicaciones deberían usar sigaction() en lugar de signal()

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

Ya sea que esté comenzando desde cero o modificando un programa antiguo, sigaction debería ser la opción correcta.

Son diferentes interfaces para las instalaciones de señal del sistema operativo. Uno debería preferir usar sigaction para señalar si es posible, ya que signal() tiene un comportamiento definido por la implementación (a menudo propenso a la carrera) y se comporta de manera diferente en Windows, OS X, Linux y otros sistemas UNIX.

Mira esto nota de seguridad para detalles.

signal() es C estándar, sigaction() no lo es.

Si puede usar cualquiera de los dos (es decir, está en un sistema POSIX), use sigaction(); no se especifica si la señal () reinicia el controlador, lo que significa que para ser portátil debe llamar a la señal () nuevamente dentro del controlador. Lo que es peor es que hay una carrera: si recibe dos señales en rápida sucesión, y la segunda se entrega antes de que reinstale el controlador, tendrá la acción predeterminada, que probablemente será eliminar su proceso.
seguiracción(), por otro lado, se garantiza que utilizará una semántica de señal “confiable”. No necesita reinstalar el controlador, ya que nunca se restablecerá. Con SA_RESTART, también puede hacer que algunas llamadas al sistema se reinicien automáticamente (para que no tenga que verificar manualmente el EINTR).
seguiracción() tiene más opciones y es fiable, por lo que se recomienda su uso.

Psst… no le digas a nadie que te dije esto, pero POSIX actualmente tiene una función bsd_signal() que actúa como signal() pero proporciona semántica BSD, lo que significa que es confiable. Su uso principal es para portar aplicaciones antiguas que asumían señales confiables, y POSIX no recomienda su uso.

  • POSIX no tiene una función bsd_signal() — algunas implementaciones de POSIX pueden tener la función, pero POSIX en sí no contiene dicha función (ver POSIX).

    –Jonathan Leffler

    13 de agosto de 2019 a las 16:22

avatar de usuario
greg hewgill

Desde el signal(3) página man:

DESCRIPCIÓN

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

Ambos invocan la misma instalación subyacente. Presumiblemente, no debería manipular la respuesta de una sola señal con ambos, pero mezclarlos no debería causar que nada se rompa …

  • POSIX no tiene una función bsd_signal() — algunas implementaciones de POSIX pueden tener la función, pero POSIX en sí no contiene dicha función (ver POSIX).

    –Jonathan Leffler

    13 de agosto de 2019 a las 16:22

avatar de usuario
pradyumna kaushik

También sugeriría usar sigaction() sobre signal() y me gustaría agregar un punto más. sigaction() le brinda más opciones, como pid del proceso que murió (posible usando la estructura siginfo_t).

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad