Como printf
não é reentrante, não é seguro usá-lo em um manipulador de sinal. Mas eu vi muitos códigos de exemplo que usam printf
essa maneira.
Portanto, minha pergunta é: quando devemos evitar o uso printf
em um manipulador de sinal e há uma substituição recomendada?
printf
chamada naquele manipulador de sinais? Delete isso.Respostas:
Você pode usar alguma variável de sinalizador, definir esse sinalizador dentro do manipulador de sinal e com base nessa
printf()
função de chamada de sinalizador em main () ou outra parte do programa durante a operação normal.Observe no exemplo abaixo, o manipulador de sinal ding () define um sinalizador
alarm_fired
como 1 quando SIGALRM é capturado e oalarm_fired
valor da função principal é examinado para chamar condicionalmente printf corretamente.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); }
Referência: Iniciando a Programação do Linux, 4ª Edição , Neste livro exatamente o seu código é explicado (o que você deseja), Capítulo 11: Processos e Sinais, página 484
Além disso, você precisa ter cuidado especial ao escrever funções de manipulador, pois elas podem ser chamadas de forma assíncrona. Ou seja, um manipulador pode ser chamado em qualquer ponto do programa, de forma imprevisível. Se dois sinais chegarem durante um intervalo muito curto, um manipulador pode funcionar dentro de outro. E é considerada a melhor prática declarar
volatile sigatomic_t
, este tipo sempre é acessado atomicamente, evita incertezas sobre a interrupção do acesso a uma variável. (leia: Acesso Atômico a Dados e Manuseio de Sinais para expiação de detalhes).Leia Definindo Manipuladores de Sinal : para aprender como escrever uma função de manipulador de sinal que pode ser estabelecida com as funções
signal()
ousigaction()
.Lista de funções autorizadas na página de manual , chamar esta função dentro do manipulador de sinal é seguro.
fonte
volatile sigatomic_t alarm_fired;
O principal problema é que se o sinal for interrompido
malloc()
ou alguma função semelhante, o estado interno pode ficar temporariamente inconsistente enquanto move blocos de memória entre a lista livre e a lista usada ou outras operações semelhantes. Se o código no manipulador de sinal chamar uma função que então invocamalloc()
, isso pode destruir completamente o gerenciamento de memória.O padrão C tem uma visão muito conservadora do que você pode fazer em um manipulador de sinal:
POSIX é muito mais generoso sobre o que você pode fazer em um manipulador de sinal.
Signal Concepts na edição POSIX 2008 diz:
No entanto, a
printf()
família de funções está notavelmente ausente dessa lista e não pode ser chamada com segurança de um manipulador de sinal.A atualização POSIX 2016 estende a lista de funções seguras para incluir, em particular, um grande número de funções de
<string.h>
, o que é uma adição particularmente valiosa (ou foi um descuido particularmente frustrante). A lista agora é:_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()
Como resultado, você acaba usando
write()
sem o suporte de formatação fornecido porprintf()
et al, ou acaba definindo um sinalizador que testa (periodicamente) em locais apropriados em seu código. Essa técnica é habilmente demonstrada na resposta de Grijesh Chauhan .Funções C padrão e segurança de sinal
chqrlie faz uma pergunta interessante, para a qual não tenho mais do que uma resposta parcial:
Para muitas das funções em
<string.h>
, é difícil ver por que eles não foram declaradas seguras sinal assíncrono, e eu concordo ostrlen()
é um excelente exemplo, juntamente comstrchr()
,strstr()
, etc. Por outro lado, outras funções comostrtok()
,strcoll()
estrxfrm()
são bastante complexos e provavelmente não são seguros para sinais assíncronos. Porquestrtok()
retém o estado entre as chamadas, o manipulador de sinais não poderia dizer facilmente se alguma parte do código que está usandostrtok()
seria bagunçada. As funçõesstrcoll()
estrxfrm()
trabalham com dados sensíveis à localidade, e o carregamento da localidade envolve todos os tipos de configuração de estado.As funções (macros) de
<ctype.h>
são todas sensíveis à localidade e, portanto, podem ter os mesmos problemas questrcoll()
estrxfrm()
.Acho difícil ver por que as funções matemáticas de
<math.h>
não são seguras para sinais assíncronos, a menos que seja porque elas podem ser afetadas por um SIGFPE (exceção de ponto flutuante), embora seja a única vez que vejo um desses hoje em dia para inteiros divisão por zero. Incerteza semelhante surge de<complex.h>
,<fenv.h>
e<tgmath.h>
.Algumas das funções em
<stdlib.h>
podem ser isentas -abs()
por exemplo. Outros são especificamente problemáticos:malloc()
e a família são os principais exemplos.Uma avaliação semelhante poderia ser feita para os outros cabeçalhos na Norma C (2011) usados em um ambiente POSIX. (O padrão C é tão restritivo que não há interesse em analisá-los em um ambiente puro do padrão C). Aqueles marcados como 'dependentes de localidade' não são seguros porque a manipulação de localidades pode exigir alocação de memória, etc.
<assert.h>
- Provavelmente não é seguro<complex.h>
- Possivelmente seguro<ctype.h>
- Não é seguro<errno.h>
- Seguro<fenv.h>
- Provavelmente não é seguro<float.h>
- Sem funções<inttypes.h>
- Funções sensíveis à localidade (inseguro)<iso646.h>
- Sem funções<limits.h>
- Sem funções<locale.h>
- Funções sensíveis à localidade (inseguro)<math.h>
- Possivelmente seguro<setjmp.h>
- Não é seguro<signal.h>
- Permitido<stdalign.h>
- Sem funções<stdarg.h>
- Sem funções<stdatomic.h>
- Possivelmente seguro, provavelmente não seguro<stdbool.h>
- Sem funções<stddef.h>
- Sem funções<stdint.h>
- Sem funções<stdio.h>
- Não é seguro<stdlib.h>
- Nem todos são seguros (alguns são permitidos; outros não)<stdnoreturn.h>
- Sem funções<string.h>
- Nem tudo seguro<tgmath.h>
- Possivelmente seguro<threads.h>
- Provavelmente não é seguro<time.h>
- Depende da localidade (mastime()
é explicitamente permitido)<uchar.h>
- Depende da localidade<wchar.h>
- Depende da localidade<wctype.h>
- Depende da localidadeAnalisar os cabeçalhos POSIX seria ... mais difícil porque há muitos deles, e algumas funções podem ser seguras, mas muitas não ... mas também mais simples porque o POSIX diz quais funções são seguras para sinais assíncronos (não muitas delas). Observe que um cabeçalho como
<pthread.h>
tem três funções seguras e muitas funções não seguras.NB: Quase toda a avaliação das funções C e cabeçalhos em um ambiente POSIX são suposições semi-educadas. Não faz sentido uma declaração definitiva de um organismo de padrões.
fonte
<string.h>
ou de funções de classe de caractere<ctype.h>
e muitas outras funções de biblioteca padrão C não estão na lista acima? Uma implementação precisaria ser propositalmente má para tornarstrlen()
insegura a chamada de um manipulador de sinal.<ctype.h>
coisas, é específico do locale e pode causar problemas se o sinal interromper uma função de configuração do locale, mas uma vez que o local é carregado, usá-los deve ser seguro. Eu acho que, em algumas situações complexas, o carregamento dos dados de localidade pode ser feito de forma incremental, tornando as funções<ctype.h>
inseguras. A conclusão permanece: na dúvida, abstenha-se.Sempre evite, dirá: Apenas não use
printf()
em manipuladores de sinal.Pelo menos em sistemas em conformidade com POSIX, você pode usar em
write(STDOUT_FILENO, ...)
vez deprintf()
. A formatação pode não ser fácil, no entanto: Imprimir int do manipulador de sinal usando funções de gravação ou de segurança assíncronafonte
Always avoid it.
significa? Evitarprintf()
?printf()
manipuladores de sinal.2
ponto, verifique OP perguntando como evitar o usoprintf()
em manipuladores de sinal?Para fins de depuração, escrevi uma ferramenta que verifica se você está, na verdade, apenas chamando funções da
async-signal-safe
lista e imprime uma mensagem de aviso para cada função não segura chamada em um contexto de sinal. Embora não resolva o problema de querer chamar funções não seguras de forma assíncrona a partir de um contexto de sinal, pelo menos ajuda a encontrar casos em que você fez isso acidentalmente.O código-fonte está no GitHub . Ele funciona sobrecarregando e
signal/sigaction
, em seguida, sequestrando temporariamente asPLT
entradas de funções não seguras; isso faz com que as chamadas para funções não seguras sejam redirecionadas para um wrapper.fonte
Implemente seu próprio sinal assíncrono seguro
snprintf("%d
e usewrite
Não é tão ruim quanto eu pensava, como converter um int em string em C? tem várias implementações.
Uma vez que existem apenas dois tipos interessantes de dados que os manipuladores de sinais podem acessar:
sig_atomic_t
globaisint
argumento de sinalisso basicamente cobre todos os casos de uso interessantes.
O fato de
strcpy
também ser seguro para sinais torna as coisas ainda melhores.O programa POSIX abaixo imprime para padrão o número de vezes que recebeu SIGINT até agora, com o qual você pode disparar
Ctrl + C
, e o ID do sinal e.Você pode sair do programa com
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; }
Compile e execute:
gcc -std=c99 -Wall -Wextra -o main main.c ./main 1
Depois de pressionar Ctrl + C quinze vezes, o terminal mostra:
^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
onde
2
é o número do sinal paraSIGINT
.Testado no Ubuntu 18.04. GitHub upstream .
fonte
Uma técnica que é especialmente útil em programas que possuem um loop de seleção é escrever um único byte em um tubo ao receber um sinal e, em seguida, tratar o sinal no loop de seleção. Algo nessa linha (tratamento de erros e outros detalhes omitidos por questões de brevidade) :
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 */ } }
Se você se importa com qual sinal era, então o byte no cano pode ser o número do sinal.
fonte
Você pode usar printf em manipuladores de sinal se estiver usando a biblioteca pthread. unix / posix especifica que printf é atômico para tópicos cf Dave Butenhof responda aqui: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Observe que, para obter uma imagem mais clara da saída de printf, você deve executar seu aplicativo em um console (no linux, use ctl + alt + f1 para iniciar o console 1), em vez de um pseudo-tty criado pela GUI.
fonte