Печать int из обработчика сигналов с использованием функций записи или асинхронного ввода

Я хочу напечатать номер в журнале или на терминале, используя write (или любую асинхронную функцию) внутри обработчика сигнала. Я бы предпочел не использовать буферизованный ввод-вывод.

Есть ли простой и рекомендуемый способ сделать это?

Например, вместо printf ниже я бы предпочел write (или любую функцию 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;
}

Печатать строки легко. Вместо printf выше я могу использовать (без печати pid):

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

Ответ 1

Если вы действительно настаиваете на выполнении печати из обработчика сигнала, у вас есть в основном 2 варианта:

  • Блокируйте сигнал, за исключением выделенного потока, который вы создаете для обработки сигнала. Этот специальный поток может просто выполнять for (;;) pause();, и поскольку pause является безопасным для асинхронного сигнала, обработчик сигнала может использовать любые функции, которые он хочет; он не ограничивается только функциями, защищенными от асинхронного сигнала. С другой стороны, он должен иметь доступ к общим ресурсам поточно-безопасным способом, поскольку теперь вы имеете дело с потоками.

  • Напишите свой собственный код для преобразования целых чисел в десятичные строки. Это просто простой цикл использования %10 и /10 для удаления последней цифры и сохранения их в короткий массив.

Тем не менее, я бы очень рекомендовал получить эту операцию из обработчика сигнала, используя трюк self-pipe или аналогичный.

Ответ 2

Реализуйте свой собственный async-signal-safe snprintf("%d и используйте write

Это не так плохо, как я думал, как преобразовать int в строку в C? имеет несколько реализаций.

Программа POSIX, приведенная ниже, считает, что количество получений SIGINT, которое вы получили до сих пор, может быть запущено с помощью Ctrl + C

Вы можете выйти из программы с помощью Ctrl + \ (SIGQUIT).

main.c:

#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:
 * /questions/40375/how-to-convert-an-int-to-string-in-c32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc 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;
}

Скомпилируйте и запустите:

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

После нажатия Ctrl + C пятнадцать раз, терминал показывает:

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

Вот связанная программа, которая создает более сложную строку формата: Как избежать использования printf в обработчике сигналов?

Проверено на Ubuntu 18.04. GitHub вверх по течению.

Ответ 3

Если вы настаиваете на использовании xprintf() внутри обработчика сигнала, вы всегда можете перевернуть свою собственную версию, которая не полагается на буферизованный ввод-вывод:

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