如何避免在信号处理程序中使用printf?

由于printf不是可重入的,因此在信号处理程序中使用它不是安全的。 但是我已经看到很多使用printf的示例代码。

所以我的问题是:什么时候我们需要避免在信号处理程序中使用printf ,是否有推荐的replace?

您可以使用一些标志variables,在信号处理程序中设置该标志,并在正常操作期间基于该标志调用main()或程序的其他部分中的printf()函数。

从信号处理程序中调用所有函数(如printf是不安全的。 一个有用的技术是使用信号处理程序设置一个flag ,然后从主程序中检查该flag ,并在需要时打印消息。

注意在下面的例子中,signal handler 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页

另外,你需要特别注意编写处理函数,因为它们可以被asynchronous调用。 也就是说,程序中的任何一点都可能被调用,这是不可预测的。 如果两个信号在很短的时间间隔内到达,则一个处理器可以在另一个处理器内运行 而且,声明volatile sigatomic_t被认为是更好的做法,这种types总是以primefaces方式访问,避免中断访问variables的不确定性。 (阅读: primefaces数据访问和信号处理详细清除)。

阅读定义信号处理程序 :了解如何编写可以使用signal()sigaction()函数build立的信号处理程序函数。
手册页中授权function列表,在信号处理程序中调用该函数是安全的。

主要的问题是,如果信号中断malloc()或类似的函数,内部状态可能暂时不一致,而在free列表和used列表之间移动内存块,或者其他类似的操作。 如果信号处理程序中的代码调用一个函数,然后调用malloc() ,这可能会完全破坏内存pipe理。

C标准对你在信号处理程序中可以做的非常保守的看法:

ISO / IEC 9899:2011§7.14.1.1 signalfunction

¶5如果信号不是由于调用abortraise函数而发生的,则如果信号处理程序引用静态或线程存储持续时间不是非lockingprimefaces对象的任何对象,而是通过分配对声明为volatile sig_atomic_t的对象的值,或者信号处理程序调用标准库中除abort函数, _Exit函数, quick_exit函数或第一个参数的signal函数以外的quick_exit函数,引起调用处理程序的信号。 而且,如果对signal函数的这种调用导致SIG_ERR返回,则errno的值是不确定的。 252)

如果任何信号由asynchronous信号处理程序生成,则行为是不确定的。

POSIX在信号处理程序中可以做的更加慷慨。

Signal Concepts说:

如果进程是multithreading的,或者进程是单线程的,并且执行信号处理程序的结果不是如此:

  • 调用abort()raise()kill()pthread_kill()sigqueue()来产生一个没有被阻塞的信号

  • 待解决的信号被解除阻塞并在解除阻塞的呼叫返回之前被传递

如果信号处理程序引用了具有静态存储持续时间的errno以外的任何对象,而不是通过为声明为volatile sig_atomic_t的对象volatile sig_atomic_t ,或者信号处理程序调用了本标准中定义的任何函数下表中列出的function。

下表定义了一组应该是asynchronous信号安全的函数。 因此,应用程序可以从信号捕获function中无限制地调用它们:

 _Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() abort() fstat() pthread_kill() sigset() accept() fstatat() pthread_self() sigsuspend() access() fsync() pthread_sigmask() sleep() aio_error() ftruncate() raise() sockatmark() aio_return() futimens() read() socket() aio_suspend() getegid() readlink() socketpair() alarm() geteuid() readlinkat() stat() bind() getgid() recv() symlink() cfgetispeed() getgroups() recvfrom() symlinkat() cfgetospeed() getpeername() recvmsg() tcdrain() cfsetispeed() getpgrp() rename() tcflow() cfsetospeed() getpid() renameat() tcflush() chdir() getppid() rmdir() tcgetattr() chmod() getsockname() select() tcgetpgrp() chown() getsockopt() sem_post() tcsendbreak() clock_gettime() getuid() send() tcsetattr() close() kill() sendmsg() tcsetpgrp() connect() link() sendto() time() creat() linkat() setgid() timer_getoverrun() dup() listen() setpgid() timer_gettime() dup2() lseek() setsid() timer_settime() execl() lstat() setsockopt() times() execle() mkdir() setuid() umask() execv() mkdirat() shutdown() uname() execve() mkfifo() sigaction() unlink() faccessat() mkfifoat() sigaddset() unlinkat() fchdir() mknod() sigdelset() utime() fchmod() mknodat() sigemptyset() utimensat() fchmodat() open() sigfillset() utimes() fchown() openat() sigismember() wait() fchownat() pause() signal() waitpid() fcntl() pipe() sigpause() write() fdatasync() poll() sigpending() 

所有不在上表中的function都被认为是不安全的。 在信号出现的情况下,POSIX.1-2008定义的所有function应该在被信号捕获function调用或中断时定义,只有一个例外:当一个信号中断一个不安全的function,捕捉函数调用一个不安全的函数,行为是未定义的。

获取errno值的操作和为errno赋值的操作应该是asynchronous信号安全的。

当信号传递给线程时,如果该信号的动作指定终止,停止或继续,那么整个过程将分别终止,停止或继续。

但是, printf()系列显然不在该列表中,并且可能不会从信号处理程序安全地调用。

因此,您最终将不使用printf()等提供的格式化支持来使用write() ,或者最终设置一个标志(您定期)在代码中的适当位置进行testing。 这个技巧在Grijesh Chauhan的回答中得到了很好的certificate。


标准Cfunction和信号安全

chrrlie 问一个有趣的问题,我只有一个部分的答案:

大多数string函数如何来自<string.h>或来自<ctype.h>的字符类函数以及更多C标准库函数不在上面的列表中? 一个实现需要有意地使邪恶的strlen()不安全的调用信号处理程序。

对于<string.h>许多函数,很难看出为什么它们没有被声明为asynchronous信号安全,我同意strlen()是一个很好的例子, strchr()strstr()另一方面,其他函数如strtok()strcoll()strxfrm()相当复杂,不太可能是asynchronous信号安全的。 因为strtok()在调用之间保持状态,信号处理程序不能轻易地判断使用strtok()的代码的一部分是否会被搞乱。 strcoll()strxfrm()函数与区域设置敏感的数据一起工作,加载语言环境涉及各种状态设置。

来自<ctype.h>的函数(macros)都是语言环境敏感的,因此可能遇到与strcoll()strxfrm()相同的问题。

我发现很难明白为什么<math.h>中的math函数不是asynchronous信号安全的,除非是因为它们可能受到SIGFPE(浮点exception)的影响,尽pipe我只看到其中的一个这几天是用零除整数除。 类似的不确定性来自<complex.h><fenv.h><tgmath.h>

<stdlib.h>一些函数可以被豁免 – 例如abs() 。 还有一些是特别有问题的: malloc()和family是最好的例子。

对于在POSIX环境中使用的标准C(2011)中的其他头文件,也可以进行类似的评估。 (标准C是如此的严格,在纯粹的标准C环境下分析它们是没有意义的。)那些标记为“依赖于语言环境”的标记是不安全的,因为操作语言环境可能需要内存分配等。

  • <assert.h>可能不安全
  • <complex.h>可能安全
  • <ctype.h> – 不安全
  • <errno.h> – 安全
  • <fenv.h>可能不安全
  • <float.h> – 没有function
  • <inttypes.h> – 区域设置敏感function(不安全)
  • <iso646.h> – 没有function
  • <limits.h> – 没有function
  • <locale.h> – 区域设置敏感function(不安全)
  • <math.h>可能安全
  • <setjmp.h> – 不安全
  • <signal.h> – 允许
  • <stdalign.h> – 没有function
  • <stdarg.h> – 没有function
  • <stdatomic.h>可能安全,可能不安全
  • <stdbool.h> – 没有function
  • <stddef.h> – 没有function
  • <stdint.h> – 没有function
  • <stdio.h> – 不安全
  • <stdlib.h> – 并非全部安全(有些是允许的,有些则不是)
  • <stdnoreturn.h> – 没有function
  • <string.h><string.h>安全的
  • <tgmath.h>可能安全
  • <threads.h>可能不安全
  • <time.h> – 依赖于区域设置(但明确允许使用time()
  • <uchar.h> – 与语言环境有关
  • <wchar.h> – 与语言环境有关
  • <wctype.h> – 区域依赖

分析POSIX头文件会更困难,因为有很多这样的函数,并且一些函数可能是安全的,但很多不会是…但也更简单,因为POSIX指出哪些函数是asynchronous信号安全的(不是很多)。 请注意像<pthread.h>这样的头文件有三个安全函数和许多不安全的函数。

注意:在POSIX环境中几乎所有对C函数和头文件的评估都是半认知猜测。 标准组织的确定性声明是毫无意义的。

如何避免在信号处理程序中使用printf

  1. 总是避免它,会说:只是不要在信号处理程序中使用printf()

  2. 至less在符合POSIX的系统上,可以使用write(STDOUT_FILENO, ...)而不是printf() 。 格式化可能并不容易: 使用写入或asynchronous安全function从信号处理程序中打印int

为了进行debugging,我编写了一个工具来validation您实际上只调用async-signal-safe列表上的函数,并为信号上下文中调用的每个不安全函数打印警告消息。 虽然它不能解决从信号上下文中调用非asynchronous安全函数的问题,但至less可以帮助您find意外发生的情况。

源代码在GitHub上 。 它通过超载signal/sigaction ,然后暂时劫持不安全函数的PLT条目; 这导致调用不安全的函数被redirect到包装。

一种在具有select循环的程序中特别有用的技术是在收到信号时在pipe道上写下单个字节,然后在select循环中处理信号。 一些沿着这些线(error handling和其他细节,为简洁起见)

 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 */ } } 

如果你关心的是哪个信号,那么pipe道中的字节可以是信号编号。

如果您正在使用pthread库,则可以在信号处理程序中使用printf。 unix / posix指定printf是线程的primefacescf Dave Butenhof在这里回复: https ://groups.google.com/forum/#! topic /comp.programming.threads/1-bU71nYgqw 请注意,为了获得更清晰的图片的printf输出, 你应该在控制台运行你的应用程序 (在linux上用ctl + alt + f1启动控制台1),而不是由GUI创build的伪tty。