Поскольку printf
он не реентерабелен, его небезопасно использовать в обработчике сигналов. Но я видел множество примеров кода, в которых используется printf
этот способ.
Итак, мой вопрос: когда нам нужно избегать использования printf
в обработчике сигналов и есть ли рекомендуемая замена?
printf
вызов в этом устройстве обработки сигналов? Удалите это.Ответы:
Вы можете использовать некоторую переменную флага, установить этот флаг внутри обработчика сигнала и на основе этой
printf()
функции вызова флага в main () или другой части программы во время нормальной работы.Обратите внимание, что в приведенном ниже примере обработчик сигнала ding () устанавливает флаг
alarm_fired
в 1, поскольку SIGALRM перехвачен, и в основномalarm_fired
значение функции проверяется на предмет правильного вызова printf.static int alarm_fired = 0; void ding(int sig) // can be called asynchronously { alarm_fired = 1; // set flag } int main() { pid_t pid; printf("alarm application starting\n"); pid = fork(); switch(pid) { case -1: /* Failure */ perror("fork failed"); exit(1); case 0: /* child */ sleep(5); kill(getppid(), SIGALRM); exit(0); } /* if we get here we are the parent process */ printf("waiting for alarm to go off\n"); (void) signal(SIGALRM, ding); pause(); if (alarm_fired) // check flag to call printf printf("Ding!\n"); printf("done\n"); exit(0); }
Ссылка: Начало программирования в Linux, 4-е издание , в этой книге объясняется именно ваш код (что вы хотите), Глава 11: Процессы и сигналы, стр. 484
Кроме того, вам нужно проявлять особую осторожность при написании функций-обработчиков, потому что они могут вызываться асинхронно. То есть обработчик может быть вызван в любой момент программы непредсказуемо. Если два сигнала поступают в течение очень короткого интервала, один обработчик может работать внутри другого. И считается лучшей практикой объявить
volatile sigatomic_t
, что доступ к этому типу всегда осуществляется атомарно, чтобы избежать неуверенности в прерывании доступа к переменной. (читайте: Доступ к атомарным данным и обработка сигналов для детального истечения срока действия).Прочтите Определение обработчиков сигналов : чтобы узнать, как написать функцию обработчика сигналов, которая может быть установлена с помощью функций
signal()
илиsigaction()
.Список разрешенных функций на странице руководства , вызов этой функции внутри обработчика сигналов безопасен.
источник
volatile sigatomic_t alarm_fired;
Основная проблема заключается в том, что если сигнал прерывается
malloc()
или какая-то аналогичная функция, внутреннее состояние может быть временно несовместимым, пока он перемещает блоки памяти между свободным и используемым списком или другие подобные операции. Если код в обработчике сигнала вызывает функцию, которая затем вызываетсяmalloc()
, это может полностью нарушить управление памятью.Стандарт C придерживается очень консервативного взгляда на то, что вы можете делать в обработчике сигналов:
POSIX гораздо более щедр в отношении того, что вы можете делать в обработчике сигналов.
Signal Concepts в редакции POSIX 2008 гласит:
Однако
printf()
семейство функций заметно отсутствует в этом списке и не может быть безопасно вызвано из обработчика сигналов.2016 POSIX обновления расширяет список безопасных функций включает, в частности, большое количество функций из
<string.h>
, что является особенно ценным дополнением (или был особенно расстраивает надзор). Список теперь:_Exit() getppid() sendmsg() tcgetpgrp() _exit() getsockname() sendto() tcsendbreak() abort() getsockopt() setgid() tcsetattr() accept() getuid() setpgid() tcsetpgrp() access() htonl() setsid() time() aio_error() htons() setsockopt() timer_getoverrun() aio_return() kill() setuid() timer_gettime() aio_suspend() link() shutdown() timer_settime() alarm() linkat() sigaction() times() bind() listen() sigaddset() umask() cfgetispeed() longjmp() sigdelset() uname() cfgetospeed() lseek() sigemptyset() unlink() cfsetispeed() lstat() sigfillset() unlinkat() cfsetospeed() memccpy() sigismember() utime() chdir() memchr() siglongjmp() utimensat() chmod() memcmp() signal() utimes() chown() memcpy() sigpause() wait() clock_gettime() memmove() sigpending() waitpid() close() memset() sigprocmask() wcpcpy() connect() mkdir() sigqueue() wcpncpy() creat() mkdirat() sigset() wcscat() dup() mkfifo() sigsuspend() wcschr() dup2() mkfifoat() sleep() wcscmp() execl() mknod() sockatmark() wcscpy() execle() mknodat() socket() wcscspn() execv() ntohl() socketpair() wcslen() execve() ntohs() stat() wcsncat() faccessat() open() stpcpy() wcsncmp() fchdir() openat() stpncpy() wcsncpy() fchmod() pause() strcat() wcsnlen() fchmodat() pipe() strchr() wcspbrk() fchown() poll() strcmp() wcsrchr() fchownat() posix_trace_event() strcpy() wcsspn() fcntl() pselect() strcspn() wcsstr() fdatasync() pthread_kill() strlen() wcstok() fexecve() pthread_self() strncat() wmemchr() ffs() pthread_sigmask() strncmp() wmemcmp() fork() raise() strncpy() wmemcpy() fstat() read() strnlen() wmemmove() fstatat() readlink() strpbrk() wmemset() fsync() readlinkat() strrchr() write() ftruncate() recv() strspn() futimens() recvfrom() strstr() getegid() recvmsg() strtok_r() geteuid() rename() symlink() getgid() renameat() symlinkat() getgroups() rmdir() tcdrain() getpeername() select() tcflow() getpgrp() sem_post() tcflush() getpid() send() tcgetattr()
В результате вы либо в конечном итоге используете
write()
без поддержки форматирования, предоставляемойprintf()
и др., Либо в конечном итоге устанавливаете флаг, который вы тестируете (периодически) в соответствующих местах вашего кода. Этот метод умело продемонстрировал в ответ по Grijesh Чаухана .Стандартные функции C и безопасность сигналов
chqrlie задает интересный вопрос, на который у меня есть лишь частичный ответ:
Для многих функций
<string.h>
трудно понять, почему они не были объявлены безопасными для асинхронных сигналов, и я согласен, чтоstrlen()
это главный пример, наряду сstrchr()
,strstr()
и т. Д. С другой стороны, другие функции, такие какstrtok()
,strcoll()
иstrxfrm()
довольно сложны и вряд ли будут безопасными для асинхронных сигналов. Потому чтоstrtok()
сохраняет состояние между вызовами, а обработчик сигнала не может легко определить, будет ли какая-то часть используемого кодаstrtok()
испорчена. Функцииstrcoll()
иstrxfrm()
работают с данными, зависящими от языкового стандарта, и загрузка языкового стандарта включает в себя всевозможные настройки состояния.Все функции (макросы) из
<ctype.h>
файла зависят от языкового стандарта и поэтому могут столкнуться с теми же проблемами, чтоstrcoll()
и иstrxfrm()
.Мне трудно понять, почему математические функции из
<math.h>
не являются безопасными для асинхронных сигналов, если только это не связано с тем, что на них может повлиять SIGFPE (исключение с плавающей запятой), хотя примерно единственный раз, когда я вижу один из этих дней, это для целых чисел деление на ноль. Подобная неопределенность возникает из-за<complex.h>
,<fenv.h>
и<tgmath.h>
.Некоторые функции
<stdlib.h>
могут быть исключены,abs()
например. Другие представляют особую проблему:malloc()
и семья - яркий тому пример.Аналогичная оценка может быть сделана для других заголовков в стандарте C (2011), используемых в среде POSIX. (Стандарт C настолько ограничен, что нет никакого интереса анализировать их в чистой среде Standard C.) Те, которые помечены как «зависящие от языкового стандарта», небезопасны, потому что для управления языками может потребоваться выделение памяти и т. Д.
<assert.h>
- Наверное, небезопасно<complex.h>
- Возможно безопасно<ctype.h>
- Не безопасно<errno.h>
- Безопасно<fenv.h>
- Наверное, небезопасно<float.h>
- Нет функций<inttypes.h>
- Функции, зависящие от локали (небезопасно)<iso646.h>
- Нет функций<limits.h>
- Нет функций<locale.h>
- Функции, зависящие от локали (небезопасно)<math.h>
- Возможно безопасно<setjmp.h>
- Не безопасно<signal.h>
- Разрешается<stdalign.h>
- Нет функций<stdarg.h>
- Нет функций<stdatomic.h>
- Возможно безопасно, возможно, небезопасно<stdbool.h>
- Нет функций<stddef.h>
- Нет функций<stdint.h>
- Нет функций<stdio.h>
- Не безопасно<stdlib.h>
- Не все безопасно (одни разрешены, другие нет)<stdnoreturn.h>
- Нет функций<string.h>
- Не все безопасно<tgmath.h>
- Возможно безопасно<threads.h>
- Наверное, небезопасно<time.h>
- Зависит от локали (ноtime()
разрешено явно)<uchar.h>
- Зависит от локали<wchar.h>
- Зависит от локали<wctype.h>
- Зависит от локалиАнализ заголовков POSIX будет ... сложнее, поскольку их много, и некоторые функции могут быть безопасными, но многие - нет ... но также проще, потому что POSIX говорит, какие функции безопасны для асинхронных сигналов (не многие из них). Обратите внимание, что такой заголовок
<pthread.h>
имеет три безопасных функции и много небезопасных функций.NB: Практически вся оценка функций и заголовков C в среде POSIX - это полуобразованные догадки. Нет смысла в окончательном заявлении органа по стандартизации.
источник
<string.h>
или функций классов символов<ctype.h>
и многих других функций стандартной библиотеки C нет в списке выше? Реализация должна быть преднамеренно злой, чтобы сделатьstrlen()
вызов из обработчика сигнала небезопасным.<ctype.h>
материала, это зависит от языкового стандарта и может вызвать проблемы, если сигнал прерывает функцию установки языкового стандарта, но после загрузки языкового стандарта их использование должно быть безопасным. Я предполагаю, что в некоторых сложных ситуациях загрузка данных локали может выполняться постепенно, что делает функции<ctype.h>
небезопасными. Вывод остается: если есть сомнения, воздержитесь.Всегда избегайте этого, скажет: просто не используйте
printf()
в обработчиках сигналов.По крайней мере, в системах, совместимых с POSIX, вы можете использовать
write(STDOUT_FILENO, ...)
вместоprintf()
. Однако форматирование может быть непростым: напечатайте int из обработчика сигнала, используя функции записи или асинхронные функции.источник
Always avoid it.
значит? Избегатьprintf()
?printf()
в обработчиках сигналов.2
точки, проверьте OP с вопросом Как избежать использованияprintf()
в обработчиках сигналов?Для целей отладки я написал инструмент, который проверяет, что вы фактически вызываете только функции из
async-signal-safe
списка, и печатает предупреждающее сообщение для каждой небезопасной функции, вызываемой в контексте сигнала. Хотя он не решает проблему вызова не асинхронных функций из контекста сигнала, он, по крайней мере, помогает вам найти случаи, когда вы сделали это случайно.Исходный код находится на GitHub . Он работает путем перегрузки
signal/sigaction
, а затем временного захватаPLT
записей о небезопасных функциях; это приводит к тому, что вызовы небезопасных функций перенаправляются в оболочку.источник
Внедрите свой собственный безопасный асинхронный сигнал
snprintf("%d
и используйтеwrite
Это не так плохо, как я думал, как преобразовать int в строку в C?имеет несколько реализаций.
Поскольку есть только два интересных типа данных, к которым могут обращаться обработчики сигналов:
sig_atomic_t
глобалыint
аргумент сигналаэто в основном охватывает все интересные варианты использования.
Тот факт, что
strcpy
он также безопасен для сигналов, делает все еще лучше.Приведенная ниже программа 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: * /programming/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: /programming/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 key_str[] = "count, sigid: "; /* This is exact: * - the null after the first int will contain the space * - the null after the second int will contain the newline */ char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)]; enum { base = 10 }; char *end; end = buf; strcpy(end, key_str); end += sizeof(key_str); end = itoa_safe(global, end, base); *end++ = ' '; end = itoa_safe(sig, end, base); *end++ = '\n'; write(STDOUT_FILENO, buf, end - buf); 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 терминал показывает:
^Ccount, sigid: 0 2 ^Ccount, sigid: 1 2 ^Ccount, sigid: 2 2 ^Ccount, sigid: 3 2 ^Ccount, sigid: 4 2 ^Ccount, sigid: 5 2 ^Ccount, sigid: 6 2 ^Ccount, sigid: 7 2 ^Ccount, sigid: 8 2 ^Ccount, sigid: 9 2 ^Ccount, sigid: 10 2 ^Ccount, sigid: 11 2 ^Ccount, sigid: 12 2 ^Ccount, sigid: 13 2 ^Ccount, sigid: 14 2
где
2
- номер сигнала дляSIGINT
.Проверено на Ubuntu 18.04. GitHub вверх по течению .
источник
Один метод, который особенно полезен в программах, имеющих цикл выбора, - это записать один байт в конвейер при получении сигнала, а затем обработать сигнал в цикле выбора. Что-то в этом роде (обработка ошибок и другие детали опущены для краткости) :
static int sigPipe[2]; static void gotSig ( int num ) { write(sigPipe[1], "!", 1); } int main ( void ) { pipe(sigPipe); /* use sigaction to point signal(s) at gotSig() */ FD_SET(sigPipe[0], &readFDs); for (;;) { n = select(nFDs, &readFDs, ...); if (FD_ISSET(sigPipe[0], &readFDs)) { read(sigPipe[0], ch, 1); /* do something about the signal here */ } /* ... the rest of your select loop */ } }
Если вам интересно, какой это был сигнал, то байт по конвейеру может быть номером сигнала.
источник
Вы можете использовать printf в обработчиках сигналов, если используете библиотеку pthread. unix / posix указывает, что printf является атомарным для потоков, см. ответ Дэйва Бутенхофа здесь: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Обратите внимание, что для получения более четкой картины вывода printf, вы должны запускать свое приложение в консоли (в Linux используйте ctl + alt + f1 для запуска консоли 1), а не в псевдо-tty, созданном графическим интерфейсом.
источник