如何避免在信号处理程序中使用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
signal
function¶5如果信号不是由于调用
abort
或raise
函数而发生的,则如果信号处理程序引用静态或线程存储持续时间不是非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
?
-
总是避免它,会说:只是不要在信号处理程序中使用
printf()
。 -
至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。
- 在terminal中使用ctrl-x时发送哪个信号?
- PyQt4.QtCore.pyqtSignal对象没有属性“连接”
- .NET 4.5 WebSockets vs SignalR
- 赶上Ctrl + C / SIGINT并在python中优雅退出多进程
- 从SignalR调用特定的客户端
- 在不locking互斥锁的情况下调用pthread_cond_signal
- 通过使用BookSleeve的ConnectionUtils.Connect(),使用SignalR和Redis messagebus进行故障转移
- Linux中的multithreading信号处理
- 在Linux上如何执行asynchronous信号处理程序?