Вывести 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 варианта:
Блокируйте сигнал, кроме как в отдельном потоке, который вы создаете для обработки сигнала. Эта специальная тема может просто выполнить
for (;;) pause();
и с тех порpause
безопасен по асинхронному сигналу, обработчику сигнала разрешено использовать любые функции, которые он хочет; это не ограничено только асинхронно-безопасными функциями. С другой стороны, он должен иметь доступ к общим ресурсам потокобезопасным способом, поскольку теперь вы имеете дело с потоками.Напишите свой собственный код для преобразования целых чисел в десятичные строки. Это просто простой цикл использования
%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;
}