¿Cuál es la diferencia entre sigaction y signal?

143

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 utilizado sigaction()para configurar los otros controladores de señal. Iba a usar signal(). Para seguir la convención que debo usar, sigaction()pero si escribo desde cero, ¿cuál debo elegir?

Matthew Smith
fuente

Respuestas:

167

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

La signal()interfaz tiene antigüedad (y por lo tanto disponibilidad) a su favor, y se define en el estándar C. Sin embargo, tiene una serie de características indeseables que se sigaction()evitan, a menos que use las marcas agregadas explícitamente sigaction()para permitirle simular fielmente el signal()comportamiento anterior.

  1. La signal()función no (necesariamente) bloquea 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. La signal()función (generalmente) restablece la acción de la señal a SIG_DFL(predeterminada) para casi todas las señales. Esto significa que el signal()controlador 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 prejuicio, también conocido como volcado del núcleo).
  3. El comportamiento exacto de signal()varía entre sistemas, y los estándares permiten esas variaciones.

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

Cualquiera de los dos que utiliza, no se deje tentar por las interfaces de señal alternativos tales como sighold(), sigignore(), sigpause()y sigrelse(). Son nominalmente alternativas a sigaction(), pero apenas están estandarizadas y están presentes en POSIX por compatibilidad con versiones anteriores en lugar de 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 de manual de Linux signal()dice:

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

Por lo tanto, creo que sigaction()es lo único que se puede usar 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 subprocesos simples y se ejecuta un controlador de señal que no sea como resultado de:

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

el comportamiento no está definido si el controlador de señal se refiere a cualquier objeto que no sea errnocon una duración de almacenamiento estático que no sea mediante la asignación de un valor a un objeto declarado como volatile sig_atomic_t, o si el controlador de señal llama a cualquier función definida en este estándar que no sea una de las funciones enumeradas en Conceptos de señal .

Por lo tanto, POSIX especifica claramente el comportamiento de signal()una aplicación multiproceso.

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

Jonathan Leffler
fuente
12
Esta descripción signales en realidad del comportamiento de Unix System V. POSIX permite este comportamiento o el comportamiento BSD mucho más sensato, pero como no puede estar seguro de cuál obtendrá, es mejor usarlo sigaction.
R .. GitHub DEJA DE AYUDAR AL HIELO
1
a menos que use las banderas agregadas explícitamente a sigaction () para permitirle simular fielmente el comportamiento anterior de signal (). ¿Qué banderas serían esas (específicamente)?
ChristianCuevas
@AlexFritz: Principalmente SA_RESETHAND, también SA_NODEFER.
Jonathan Leffler
2
@BulatM. Si no puede usar sigaction(), entonces está esencialmente obligado a usar la especificación Estándar C para signal(). Sin embargo, eso le brinda un conjunto de opciones extremadamente empobrecido 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; regreso. Y eso es. No se garantiza que cualquier otra cosa sea portátil. Eso es tan estricto que la mayoría de la gente ignora esas reglas, pero el código resultante es dudoso.
Jonathan Leffler
1
Excelente sigaction()demostración de los propios GCC: gnu.org/software/libc/manual/html_node/… ; y excelente signal()demostración de los propios GCC: gnu.org/software/libc/manual/html_node/… . Tenga en cuenta que en la signaldemostración evitan cambiar el controlador de ignore ( SIG_IGN) si eso es lo que se configuró previamente intencionalmente.
Gabriel Staples
8

Para mí, esta línea de abajo fue suficiente para decidir:

La función sigaction () proporciona un mecanismo más completo y confiable para controlar señales; las nuevas aplicaciones deben 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.

San
fuente
5

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 la señal () 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.

Consulte esta nota de seguridad para más detalles.

ididak
fuente
1
Acabo de mirar el código fuente de glibc y la señal () solo llama a sigaction (). También vea arriba donde la página de manual de MacOS dice lo mismo.
bmdhacks
Es bueno saber eso. Solo he visto que los manejadores de señales solían cerrar las cosas ordenadamente antes de salir, por lo que generalmente no confiaría en el comportamiento relacionado con la reinstalación del manejador.
Matthew Smith
5

la señal () es estándar C, sigaction () no lo es.

Si puede usar cualquiera de los dos (es decir, está en un sistema POSIX), use sigaction (); no se especifica si signal () restablece el controlador, lo que significa que para ser portátil debe llamar a signal () nuevamente dentro del controlador. Lo que es peor es que hay una carrera: si obtienes dos señales en rápida sucesión y la segunda se entrega antes de reinstalar el controlador, tendrás la acción predeterminada, que probablemente va a matar tu proceso. sigaction () , por otro lado, está garantizado para usar semántica de señal "confiable". No necesita reinstalar el controlador, ya que nunca se restablecerá. Con SA_RESTART, también puede obtener algunas llamadas del sistema para reiniciar automáticamente (por lo que no tiene que verificar manualmente si hay EINTR). sigaction () tiene más opciones y es confiable, 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 señal () 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.

Huzzefakhan
fuente
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 (consulte POSIX ).
Jonathan Leffler
4

En breve:

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

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

man 2 signal(Véalo en línea aquí ) declara:

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. Evite su uso: use sigaction(2)en su lugar. Ver 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 utiliza la señal () para establecer un controlador de señal varía entre sistemas (y POSIX.1 permite explícitamente esta variación); No lo use para este propósito.

En otras palabras: no usar signal(). Use en su sigaction()lugar!

¿Qué piensa el CCG?

Nota de compatibilidad: como se dijo anteriormente para signal, esta función debe evitarse cuando sea posible. sigactionEs 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 no usar signal(), sino usar sigaction()en su lugar, eso plantea la pregunta: ¿cómo diablos usamos esta sigaction()cosa confusa ?

Ejemplos de uso:

Lea el EXCELENTE signal()ejemplo de GCC 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 /programming/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 signal(), aunque no es una buena forma de adjuntar un controlador de señal, como se describió 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 va a 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
    1. Señales de terminación: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Manejo básico de señales, incluido el signal()ejemplo oficial de uso de GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. sigaction()Ejemplo de uso oficial de GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Conjuntos de señales, incluidos sigemptyset()y sigfillset(); Todavía no los entiendo exactamente, pero sé que son importantes: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Ver también:

  1. TutorialsPoint C ++ Signal Handling [con excelente código de demostración]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm
Gabriel Staples
fuente
2

Desde la signal(3)página del manual:

DESCRIPCIÓN

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

Ambos invocan la misma facilidad subyacente. Presumiblemente no deberías manipular la respuesta de una sola señal con ambas, pero mezclarlas no debería hacer que nada se rompa ...

dmckee --- gatito ex moderador
fuente
¡Eso no está en mi página de manual! Todo lo que obtengo es "DESCRIPCIÓN La llamada al sistema signal () instala un nuevo controlador de señal para la señal con el número de signo". Necesito actualizar al útil paquete de páginas de manual.
Matthew Smith
1
Eso está fuera de las páginas de Mac OS X 10.5.
dmckee --- gatito ex moderador el
También verificado del código fuente de glibc. signal () solo llama a sigaction ()
bmdhacks
2
Sin embargo, esto no es cierto en todas las implementaciones de señal. Si desea exigir un comportamiento de "sigaction", no confíe en esta suposición.
Ben Burns
1

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

pradyumna kaushik
fuente
0

Usaría signal () ya que es más portátil, al menos en teoría. Voy a votar a cualquier comentarista que pueda idear un sistema moderno que no tenga una capa de compatibilidad POSIX y admita señal ().

Citando de la documentación GLIBC :

Es posible usar las funciones de señal y de seguir en un solo programa, pero debe tener cuidado porque pueden interactuar de maneras ligeramente extrañas.

La función sigaction especifica más información que la función de señal, por lo que el valor de retorno de la señal no puede expresar el rango completo de posibilidades de sigaction. Por lo tanto, si usa la señal para guardar y luego restablecer una acción, es posible que no pueda restablecer correctamente un controlador que se estableció con sigaction.

Para evitar tener problemas como resultado, use siempre sigaction para guardar y restaurar un controlador si su programa usa siempre sigaction. Como sigaction es más general, puede guardar y restablecer correctamente cualquier acción, independientemente de si se estableció originalmente con señal o sigaction.

En algunos sistemas, si establece una acción con señal y luego la examina con la siguiente sección, la dirección del controlador que obtenga puede no ser la misma que especificó con la señal. Puede que ni siquiera sea adecuado para su uso como argumento de acción con señal. Pero puede confiar en usarlo como argumento para seguirction. Este problema nunca ocurre en el sistema GNU.

Por lo tanto, es mejor usar uno u otro de los mecanismos de manera consistente dentro de un solo programa.

Nota de portabilidad: La función de señal básica es una característica de ISO C, mientras que sigaction es parte del estándar POSIX.1. Si le preocupa la portabilidad a sistemas que no son POSIX, entonces debe usar la función de señal en su lugar.

Copyright (C) 1996-2008 Free Software Foundation, Inc.

Se otorga permiso para copiar, distribuir y / o modificar este documento bajo los términos de la Licencia de Documentación Libre de GNU, Versión 1.2 o cualquier versión posterior publicada por la Free Software Foundation; sin secciones invariantes, sin textos de portada y sin textos de contraportada. Se incluye una copia de la licencia en la sección titulada "Licencia de documentación libre de GNU".

bmdhacks
fuente
0

Desde la señal de la página de manual (7)

Se puede entregar una señal dirigida al proceso a cualquiera de los hilos que actualmente no tienen la señal bloqueada. Si más de uno de los hilos tiene la señal desbloqueada, entonces el núcleo elige un hilo arbitrario al que entregar la señal.

Y yo diría que este "problema" existe para la señal (2) y sigaction (2) . Así que ten cuidado con las señales y subprocesos.

... y la señal (2) parece llamar a sigaction (2) debajo en Linux con glibc.

robert.berger
fuente