Вывести int из обработчика сигнала, используя функции записи или асинхронной безопасности

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

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

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

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

4 ответа

Решение

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

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

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

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

Реализуйте свой собственный 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:
 * https://stackru.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://stackru.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 вверх по течению.

Вы можете использовать функции обработки строк (например,strcat), чтобы построить строку, а затемwriteэто за один раз к нужному файловому дескриптору (например,STDERR_FILENOдля стандартной ошибки).

Для преобразования целых чисел (до 64 бит, со знаком или без знака) в строки я использую следующие функции (C99), которые поддерживают минимальные флаги форматирования и общие системы счисления (8, 10 и 16).

      #include <stdbool.h>
#include <inttypes.h>

#define STRIMAX_LEN 21 // = ceil(log10(INTMAX_MAX)) + 2
#define STRUMAX_LEN 25 // = ceil(log8(UINTMAX_MAX)) + 3

static int strimax(intmax_t x,
                   char buf[static STRIMAX_LEN],
                   const char mode[restrict static 1]) {
    /* safe absolute value */
    uintmax_t ux = (x == INTMAX_MIN) ? (uintmax_t)INTMAX_MAX + 1
                                     : (uintmax_t)imaxabs(x);

    /* parse mode */
    bool zero_pad = false;
    bool space_sign = false;
    bool force_sign = false;
    for(const char *c = mode; '\0' != *c; ++c)
        switch(*c) {
            case '0': zero_pad = true; break;
            case '+': force_sign = true; break;
            case ' ': space_sign = true; break;
            case 'd': break; // decimal (always)
        }

    int n = 0;
    char sign = (x < 0) ? '-' : (force_sign ? '+' : ' ');
    buf[STRIMAX_LEN - ++n] = '\0'; // NUL-terminate
    do { buf[STRIMAX_LEN - ++n] = '0' + ux % 10; } while(ux /= 10);
    if(zero_pad) while(n < STRIMAX_LEN - 1) buf[STRIMAX_LEN - ++n] = '0';
    if(x < 0 || force_sign || space_sign) buf[STRIMAX_LEN - ++n] = sign;

    return STRIMAX_LEN - n;
}

static int strumax(uintmax_t ux,
                   char buf[static STRUMAX_LEN],
                   const char mode[restrict static 1]) {
    static const char lbytes[] = "0123456789abcdefx";
    static const char ubytes[] = "0123456789ABCDEFX";

    /* parse mode */
    int base = 10; // default is decimal
    int izero = 4;
    bool zero_pad = false;
    bool alternate = false;
    const char *bytes = lbytes;
    for(const char *c = mode; '\0' != *c; ++c)
        switch(*c) {
            case '#': alternate = true; if(base == 8) izero = 1; break;
            case '0': zero_pad = true; break;
            case 'd': base = 10; izero = 4; break;
            case 'o': base = 8; izero = (alternate ? 1 : 2); break;
            case 'x': base = 16; izero = 8; break;
            case 'X': base = 16; izero = 8; bytes = ubytes; break;
        }

    int n = 0;
    buf[STRUMAX_LEN - ++n] = '\0'; // NUL-terminate
    do { buf[STRUMAX_LEN - ++n] = bytes[ux % base]; } while(ux /= base);
    if(zero_pad) while(n < STRUMAX_LEN - izero) buf[STRUMAX_LEN - ++n] = '0';
    if(alternate && base == 16) {
        buf[STRUMAX_LEN - ++n] = bytes[base];
        buf[STRUMAX_LEN - ++n] = '0';
    } else if(alternate && base == 8 && '0' != buf[STRUMAX_LEN - n])
        buf[STRUMAX_LEN - ++n] = '0';

    return STRUMAX_LEN - n;
}

Их можно использовать так:

      #include <unistd.h>

int main (void) {
    char buf[STRIMAX_LEN]; int buf_off;
    buf_off = strimax(12345,buf,"+");
    write(STDERR_FILENO,buf + buf_off,STRIMAX_LEN - buf_off);
}

который выводит:

      +12345

Если вы настаиваете на использовании 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;
}
Другие вопросы по тегам