Imprima int desde el controlador de señal usando funciones seguras de escritura o asíncronas

8 minutos de lectura

Quiero imprimir un número en el registro o en una terminal usando write (o cualquier función asincrónica segura) dentro de un controlador de señal. Preferiría no usar E/S almacenada en búfer.

¿Hay una manera fácil y recomendada de hacerlo?

Por ejemplo en lugar de printfabajo preferiría write (o cualquier función segura asyn).

void signal_handler(int sig)
{
  pid_t pid;
  int stat;
  int old_errno = errno;

  while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
    printf("child %d terminated\n", pid);

  errno = old_errno;
  return;
}

Imprimir cadenas es fácil. en lugar de la printf arriba puedo usar (sin imprimir pid):

write(STDOUT_FILENO, "child terminated", 16);

  • Si su programa es un poco no trivial, podría ser mucho más simple configurar un signalfd e inclúyalo en su ciclo de eventos. Entonces puedes hacer lo que quieras en respuesta a la señal.

    – KerrekSB

    28 de enero de 2013 a las 23:15

  • signalfd no es portátil; es específico de Linux. Sin embargo, ha habido una versión portátil de lo mismo durante casi toda la historia de Unix: el truco de la pipa propia.

    – R.. GitHub DEJA DE AYUDAR A ICE

    28 de enero de 2013 a las 23:19

Si realmente insiste en hacer la impresión desde un controlador de señales, básicamente tiene 2 opciones:

  1. Bloquee la señal excepto en un hilo dedicado que cree para manejar la señal. Este hilo especial puede simplemente realizar for (;;) pause(); y desde pause es async-signal-safe, el controlador de señal puede usar cualquier función que desee; no está restringido solo a funciones seguras de señal asíncrona. Por otro lado, tiene que acceder a los recursos compartidos de forma segura para subprocesos, ya que ahora se trata de subprocesos.

  2. Escriba su propio código para convertir números enteros en cadenas decimales. Es solo un ciclo simple de usar %10 y /10 para despegar el último dígito y almacenarlos en una matriz corta.

Sin embargo, recomendaría encarecidamente eliminar esta operación del controlador de señal, utilizando el truco de auto-tubo o similar.

  • Si, gracias. He comenzado a cavar truco de auto-tubería. Eso parece mucho mejor.

    usuario648129

    28 de enero de 2013 a las 23:24

  • Solo la segunda opción funciona para señales como SIGSEGV, SIGILL, etc. El truco de la canalización automática tampoco funcionará en esos casos.

    -Paul Coccoli

    17 de julio de 2015 a las 13:39

avatar de usuario
Ciro Santilli Путлер Капут 六四事

Implemente su propia seguridad de señal asíncrona snprintf("%d y use write

No es tan malo como pensaba, ¿Cómo convertir un int a string en C? tiene varias implementaciones.

El programa POSIX a continuación cuenta para calcular la cantidad de veces que recibió SIGINT hasta el momento, que puede activar con Ctrl + C.

Puede salir del programa con Ctrl + \ (SIGQUIT).

C Principal:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * https://stackoverflow.com/questions/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: https://stackoverflow.com/questions/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char buf[ITOA_SAFE_STRLEN(sig_atomic_t)];
    enum { base = 10 };
    char *end;
    end = itoa_safe(global, buf, base);
    *end = '\n';
    write(STDOUT_FILENO, buf, end - buf + 1);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

Compilar y ejecutar:

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

Después de presionar Ctrl + C quince veces, la terminal muestra:

^C0
^C1
^C2
^C3
^C4
^C5
^C6
^C7
^C8
^C9
^C10
^C11
^C12
^C13
^C14

Aquí hay un programa relacionado que crea una cadena de formato más compleja: ¿Cómo evitar usar printf en un controlador de señal?

Probado en Ubuntu 18.04. GitHub ascendente.

  • Este programa no funciona como se anuncia. El programa regresa sin la capacidad de presionar control-c.

    – Deanie

    10 de enero de 2019 a las 16:20

  • @Deanie gracias por los comentarios. Probé nuevamente en Ubuntu 18.04 y funcionó nuevamente. Proporcione su versión de distribución / GCC.

    – Ciro Santilli Путлер Капут 六四事

    10 de enero de 2019 a las 16:56

  • gcc version 8.2.1 20181127 (GCC) y Linux unknown 4.19.8-arch1-1-ARCH #1 SMP PREEMPT Sat Dec 8 13:49:11 UTC 2018 x86_64 GNU/Linux. Cuando lo ejecuto, el símbolo del sistema regresa… ¡pronto!

    – Deanie

    11 de enero de 2019 a las 2:21

  • @Deanie gracias. ¿Y cuál es tu distribución? Ubuntu 18.04 aquí.

    – Ciro Santilli Путлер Капут 六四事

    11 de enero de 2019 a las 3:59


  • 4.19.8-arch1-1-ARCH Que es Arch Linux 4.19.8

    – Deanie

    11 de enero de 2019 a las 19:38

Si insiste en usar xprintf() dentro de un controlador de señal, siempre puede crear su propia versión que no dependa de la E/S almacenada en búfer:

#include <stdarg.h> /* vsnprintf() */

void myprintf(FILE *fp, char *fmt, ...)
{
char buff[512];
int rc,fd;
va_list argh;
va_start (argh, fmt);

rc = vsnprintf(buff, sizeof buff, fmt, argh);
if (rc < 0  || rc >= sizeof buff) {
        rc = sprintf(buff, "Argh!: %d:\n", rc);
        }

if (!fp) fp = stderr;
fd = fileno(fp);
if (fd < 0) return;
if (rc > 0)  write(fd, buff, rc);
return;
}

  • Desafortunadamente, vsnprintf tampoco es necesario que sea seguro para la señal asíncrona. Uno pensaría que esto sería solo un problema teórico que no importa en las implementaciones del mundo real, pero la implementación de glibc definitivamente es no async-signal-safe ya que utiliza malloc.

    – R.. GitHub DEJA DE AYUDAR A ICE

    29 de enero de 2013 a las 0:04

  • También, fileno no es seguro para señales asíncronas. Es necesario para obtener un bloqueo en el archivo, y podría estropearse gravemente si el código interrumpido estuviera en medio del bloqueo/desbloqueo del archivo. Afortunadamente, su uso de fileno es falso e innecesario. stderr es el descriptor de archivo número 2. Si prefiere no usar “números mágicos”, STDERR_FILENO está disponible.

    – R.. GitHub DEJA DE AYUDAR A ICE

    29 de enero de 2013 a las 0:05

  • Ah, estoy corregido entonces. Tal vez entonces cree su propia función snprintf() (async safe…). (La última vez que verifiqué la implementación de Apache fue 50KLOC…) Por cierto: sabía sobre STDERR_FILENO; era solo cargo-cult, programación, supongo) Básicamente, solo queda el truco de la auto-tubería.

    – salvaje

    29 de enero de 2013 a las 0:06


  • Por cierto: ¿podría haber una solución para empujar todo el va_arg? contenido en una tubería (hasta PIPE_BUFF, obviamente)? Requeriría una copia profunda, y los “argumentos” pueden o no ser válidos en el momento del procesamiento, me doy cuenta de eso.

    – salvaje

    29 de enero de 2013 a las 0:15


  • No creo que haya ninguna forma de hacer que eso funcione, ya que C no proporciona ninguna forma de “construir” un nuevo va_list programáticamente. La forma canónica de “emular” async-signal-safe printf además de uno que no es seguro, sería crear un subproceso dedicado por adelantado que sea responsable de realizar printf peticiones. El manejador de señales pasaría un puntero a una estructura de control que contiene el va_list puntero sobre una canalización automática y esperaría en otra canalización automática para sincronizar la finalización de la operación.

    – R.. GitHub DEJA DE AYUDAR A ICE

    29 de enero de 2013 a las 0:25

¿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