Stampa int dal gestore di segnale usando funzioni di scrittura o asincrone

Voglio stampare un numero in log o su un terminale usando write (o qualsiasi funzione async-safe) all’interno di un gestore di segnale. Preferirei non utilizzare l’I / O bufferizzato.

C’è un modo facile e consigliato per farlo?

Ad esempio al posto di printf , di seguito preferisco write (o qualsiasi funzione asyn safe).

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

Stampare le stringhe è facile. Al posto del printf sopra posso usare (senza stampare pid ):

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

    Se si vuole davvero fare la stampa da un gestore di segnale, in pratica si hanno 2 opzioni:

    1. Blocca il segnale tranne in un thread dedicato creato per gestire il segnale. Questa discussione speciale può semplicemente eseguire for (;;) pause(); e poiché la pause è asincrona al segnale, il gestore del segnale può utilizzare qualsiasi funzione voglia; non è limitato solo alle funzioni sicure del segnale asincrono. D’altra parte, deve accedere alle risorse condivise in un modo sicuro per i thread, dal momento che ora si occupano di thread.

    2. Scrivi il tuo codice per convertire gli interi in stringhe decimali. È solo un semplice ciclo di utilizzo di %10 e /10 per rimuovere l’ultima cifra e archiviarli su una matrice breve.

    Tuttavia, consiglio vivamente di ottenere questa operazione dal gestore del segnale, usando il trucco self-pipe o simili.

    Implementa il tuo snprintf("%d async-signal-safe snprintf("%d e usa write

    Non è così male come pensavo, come convertire un int in una stringa in C? ha diverse implementazioni.

    Il programma POSIX di seguito conta per stdout il numero di volte in cui ha ricevuto SIGINT fino ad ora, che puoi triggersre con Ctrl + C

    Puoi uscire dal programma con Ctrl + \ (SIGQUIT).

    main.c:

     #define _XOPEN_SOURCE 700 #include  #include  #include  #include  #include  #include  #include  #include  /* 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; } 

    Compilare ed eseguire:

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

    Dopo aver premuto Ctrl + C quindici volte, il terminale mostra:

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

    Ecco un programma correlato che crea una stringa di formato più complessa: come evitare di usare printf in un gestore di segnale?

    Testato su Ubuntu 18.04. GitHub a monte .

    Se si insiste a utilizzare xprintf () all’interno di un gestore di segnale, è sempre ansible eseguire il rollover della propria versione che non si basa su I / O bufferizzati:

     #include  /* 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; }