Gestión de señales

Las señales en procesos multihebra son una extensión de las señales en programas tradicionales de una sola hebra.

La gestión de señales en procesos multihebra es compartida por los niveles de proceso y hebra, y consta de lo siguiente:
  • Manejadores de señales por proceso
  • Máscaras de señal por hebra
  • Entrega única de cada señal

Manejadores de señales y máscaras de señal

Los manejadores de señales se mantienen a nivel de proceso. Se recomienda encarecidamente utilizar la subrutina sigwait al esperar señales. La subrutina sigaction no se recomienda porque la lista de manejadores de señales se mantiene a nivel de proceso y cualquier hebra del proceso puede cambiarla. Si dos hebras establecen un manejador de señales en la misma señal, la última hebra que ha llamado a la subrutina sigaction altera temporalmente el valor de la llamada de hebra anterior; y en la mayoría de los casos, el orden en el que se planifican las hebras no se puede predecir.

Las máscaras de señal se mantienen a nivel de hebra. Cada hilo puede tener su propio conjunto de señales que se bloquearán de la entrega. La subrutina sigthreadmask debe utilizarse para obtener y establecer la máscara de señal de la hebra de llamada. La subrutina sigprocmask no se debe utilizar en programas multihebra; porque podría producirse un comportamiento inesperado.

La subrutina pthread_sigmask es muy similar a la subrutina sigprocmask . Los parámetros y el uso de ambas subrutinas son idénticos. Al portar el código existente para dar soporte a la biblioteca de hebras, puede sustituir la subrutina sigprocmask por la subrutina pthread_sigmask .

Generación de señal

Las señales generadas por alguna acción atribuible a una hebra en particular, tal como una falla de hardware, se envían a la hebra que causó que se generara la señal. Las señales generadas en asociación con un ID de proceso, un ID de grupo de procesos o un suceso asíncrono (como la actividad de terminal) se envían al proceso.

  • La subrutina pthread_kill envía una señal a una hebra. Puesto que los ID de hebra identifican hebras dentro de un proceso, esta subrutina sólo puede enviar señales a hebras dentro del mismo proceso.
  • La subrutina kill (y, por lo tanto, el mandato kill ) envía una señal a un proceso. Una hebra puede enviar una señal Signal a su proceso ejecutando la llamada siguiente:
    kill(getpid(), Signal);
  • La subrutina raise no se puede utilizar para enviar una señal al proceso de la hebra de llamada. La subrutina raise envía una señal a la hebra de llamada, como en la llamada siguiente:
    pthread_kill(pthread_self(), Signal);
    Esto garantiza que la señal se envíe al emisor de la llamada de la subrutina raise . Por lo tanto, las rutinas de biblioteca escritas para programas de una sola hebra se pueden portar fácilmente a un sistema multihebra, porque la subrutina raise normalmente está pensada para enviar la señal al llamante.
  • La subrutina alarma solicita que se envíe una señal más adelante al proceso y que los estados de alarma se mantengan a nivel de proceso. Por lo tanto, la última hebra que ha llamado a la subrutina alarm altera temporalmente los valores de otras hebras del proceso. En un programa multihebra, la señal SIGALRM no se entrega necesariamente a la hebra que ha llamado a la subrutina alarm . La hebra de llamada puede incluso terminarse; y por lo tanto, no puede recibir la señal.

Manejo de señales

Los manejadores de señales se llaman dentro de la hebra a la que se entrega la señal. La biblioteca de hebras introduce las siguientes limitaciones para los manejadores de señales:
  • Los manejadores de señales pueden llamar a la subrutina Longjmp o siglongjmp sólo si la llamada correspondiente a la subrutina Setjmp o sigsetjmp se realiza en la misma hebra.

    Normalmente, un programa que desea esperar una señal instala un manejador de señales que llama a la subrutina longjmp para continuar la ejecución en el punto en el que se llama a la subrutina setjmp correspondiente. Esto no se puede realizar en un programa multihebra, porque la señal se puede entregar a una hebra distinta de la que ha llamado a la subrutina setjmp , lo que hace que la hebra incorrecta ejecute el manejador.

    Nota: El uso de longjmp desde un manejador de señales puede dar como resultado un comportamiento no definido.
  • No se puede llamar a ninguna rutina pthread desde un manejador de señales. Llamar a una rutina pthread desde un manejador de señales puede llevar a un punto muerto de la aplicación.

Para permitir que una hebra espere señales generadas de forma asíncrona, la biblioteca de hebras proporciona la subrutina sigwait . La subrutina sigwait bloquea la hebra de llamada hasta que se envía una de las señales esperadas al proceso o a la hebra. No debe haber un manejador de señales instalado en la señal esperada utilizando la subrutina sigwait .

Normalmente, los programas pueden crear una hebra dedicada para esperar señales generadas de forma asíncrona. Este tipo de hebra entra en un bucle en una llamada de subrutina sigwait y maneja las señales. Se recomienda que dicho hilo bloquee todas las señales. El fragmento de código siguiente proporciona un ejemplo de una hebra de espera de señal de este tipo:
#include <pthread.h>
#include <signal.h>

static pthread_mutex_t mutex;
sigset_t set;
static int sig_cond = 0;

void *run_me(void *id)
{
       int sig;
       int err;
       sigset_t sigs;
       sigset_t oldSigSet;
       sigfillset(&sigs);
       sigthreadmask(SIG_BLOCK, &sigs, &oldSigSet);

       err = sigwait(&set, &sig);

       if(err)
       {
              /* do error code */
       }
       else
       {
              printf("SIGINT caught\n");
              pthread_mutex_lock(&mutex);
              sig_cond = 1;
              pthread_mutex_unlock(&mutex);
       }

       return;
}

main()
{
       pthread_t tid;

       sigemptyset(&set);
       sigaddset(&set, SIGINT); 
       pthread_sigmask(SIG_BLOCK, &set, 0);

       pthread_mutex_init(&mutex, NULL);

       pthread_create(&tid, NULL, run_me, (void *)1);

       while(1)
       {
              sleep(1);
              /* or so something here */

              pthread_mutex_lock(&mutex);
              if(sig_cond)
              {
                     /* do exit stuff */
                     return;
              }
              pthread_mutex_unlock(&mutex);

       }

}

Si más de una hebra llama a la subrutina sigwait , se devuelve exactamente una llamada cuando se envía una señal coincidente. Qué hilo se despierta no se puede predecir. Si una hebra va a realizar sigwait , así como el manejo de otras señales para las que no está realizando sigwait, los manejadores de señales definidos por el usuario deben bloquear las señales sigwaiter para el manejo adecuado. Tenga en cuenta que la subrutina sigwait proporciona un punto de cancelación.

Debido a que una hebra dedicada no es un manejador de señales real, puede indicar una condición a cualquier otra hebra. Es posible implementar una rutina sigwait_multiple que activaría todas las hebras a la espera de una señal específica. Cada llamante de la rutina sigwait_multiple registra un conjunto de señales. A continuación, el llamante espera en una variable de condición. Una sola hebra llama a la subrutina sigwait en la unión de todas las señales registradas. Cuando se devuelve la llamada a la subrutina sigwait , se establece el estado adecuado y se difunden las variables de condición. Los nuevos llamantes a la subrutina sigwait_multiple hacen que la llamada de subrutina sigwait pendiente se cancele y se vuelva a emitir para actualizar el conjunto de señales que se están esperando.

Entrega de señal

Una señal se entrega a una hebra, a menos que su acción se establezca en ignorar. Las reglas siguientes rigen la entrega de señales en un proceso multihebra:

  • Una señal cuya acción se establece para terminar, detener o continuar la hebra o proceso de destino respectivamente termina, detiene o continúa todo el proceso (y por lo tanto todas sus hebras). Por lo tanto, los programas de una sola hebra pueden reescribirse como programas multihebra sin cambiar su comportamiento de señal visible externamente.

    Por ejemplo, considere un mandato de usuario multihebra, como el mandato grep . Un usuario puede iniciar el mandato en su shell favorito y, a continuación, decidir detenerlo enviando una señal con el mandato kill . La señal debe detener todo el proceso que ejecuta el mandato grep .

  • Las señales generadas para una hebra específica, utilizando pthread_kill o las subrutinas raise , se entregan a dicha hebra. Si la hebra ha bloqueado la señal de la entrega, la señal se establece pendiente en la hebra hasta que la señal se desbloquea de la entrega. Si la hebra termina antes de la entrega de la señal, la señal se ignorará.
  • Las señales generadas para un proceso, utilizando la subrutina kill , por ejemplo, se entregan exactamente a una hebra del proceso. Si una o más hebras llaman a la subrutina sigwait , la señal se entrega exactamente a una de estas hebras. De lo contrario, la señal se entrega exactamente a un hilo que no bloqueó la señal de la entrega. Si ninguna hebra coincide con estas condiciones, la señal se establece pendiente en el proceso hasta que una hebra llama a la subrutina sigwait especificando esta señal o una hebra desbloquea la señal de la entrega.

Si la acción asociada con una señal pendiente (en una hebra o en un proceso) se establece en ignorar, la señal se ignora.