Linux 如何避免在信号处理程序中使用 printf?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16891019/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to avoid using printf in a signal handler?
提问by Yu Hao
Since printf
is not reentrant, it's not supposed to be safe to use it in a signal handler. But I've seen lots of example codes that uses printf
this way.
由于printf
不可重入,因此在信号处理程序中使用它不应该是安全的。但是我已经看到很多使用printf
这种方式的示例代码。
So my question is: when do we need to avoid using printf
in a signal handler, and is there a recommended replacement?
所以我的问题是:我们什么时候需要避免printf
在信号处理程序中使用,是否有推荐的替代品?
采纳答案by Grijesh Chauhan
You can use some flag variable, set that flag inside signal handler, and based on that flag call printf()
function in main() or other part of program during normal operation.
您可以使用一些标志变量,在信号处理程序中设置该标志,并printf()
在正常操作期间基于main() 或程序的其他部分中的该标志调用函数。
It is not safe to call all functions, such as
printf
, from within a signal handler. A useful technique is to use a signal handler to set aflag
and then check thatflag
from the main program and print a message if required.
printf
从信号处理程序中调用所有函数(例如 )是不安全的。一个有用的技术是使用信号处理程序来设置一个flag
,然后flag
从主程序中检查它并在需要时打印一条消息。
Notice in example below, signal handler ding() set a flag alarm_fired
to 1 as SIGALRM caught and in main function alarm_fired
value is examined to conditionally call printf correctly.
请注意,在下面的示例中,信号处理程序 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);
}
Reference: Beginning Linux Programming, 4th Edition, In this book exactly your code is explained (what you want), Chapter 11: Processes and Signals, page 484
参考:Beginning Linux Programming,第 4 版,本书准确地解释了您的代码(您想要什么),第 11 章:进程和信号,第 484 页
Additionally, you need to take special care in writing handler functions because they can be called asynchronously. That is, a handler might be called at any point in the program, unpredictably. If two signals arrive during a very short interval, one handler can run within another. And It is considered better practice to declare volatile sigatomic_t
, this type are always accessed atomically, avoid uncertainty about interrupting access to a variable. (read: Atomic Data Access and Signal Handlingfor detail expiation).
此外,在编写处理程序函数时需要特别小心,因为它们可以被异步调用。也就是说,处理程序可能会在程序中的任何时候被不可预测地调用。如果两个信号在很短的时间间隔内到达,一个处理程序可以在另一个处理程序中运行。并且声明被认为是更好的做法volatile sigatomic_t
,这种类型总是以原子方式访问,避免中断访问变量的不确定性。(阅读:原子数据访问和信号处理以了解详细信息)。
Read Defining Signal Handlers:to learn how to write a signal handler function that can be established with the signal()
or sigaction()
functions.
List of authorized functions in manual page, calling this function inside signal handler is safe.
阅读定义信号处理程序:了解如何编写可以使用signal()
或sigaction()
函数建立的信号处理程序函数。手册页中
的授权函数列表,在信号处理程序中调用此函数是安全的。
回答by Jonathan Leffler
The primary problem is that if the signal interrupts malloc()
or some similar function, the internal state may be temporarily inconsistent while it is moving blocks of memory between the free and used list, or other similar operations. If the code in the signal handler calls a function that then invokes malloc()
, this may completely wreck the memory management.
主要问题是,如果信号中断malloc()
或一些类似的功能,在空闲和已用列表之间移动内存块或其他类似操作时,内部状态可能会暂时不一致。如果信号处理程序中的代码调用一个函数,然后调用malloc()
,这可能会完全破坏内存管理。
The C standard takes a very conservative view of what you can do in a signal handler:
C 标准对您可以在信号处理程序中执行的操作持非常保守的观点:
ISO/IEC 9899:2011 §7.14.1.1 The
signal
function?5 If the signal occurs other than as the result of calling the
abort
orraise
function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared asvolatile sig_atomic_t
, or the signal handler calls any function in the standard library other than theabort
function, the_Exit
function, thequick_exit
function, or thesignal
function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to thesignal
function results in aSIG_ERR
return, the value oferrno
is indeterminate.252)252)If any signal is generated by an asynchronous signal handler, the behavior is undefined.
ISO/IEC 9899:2011 §7.14.1.1
signal
功能?5 如果信号不是作为调用
abort
orraise
函数的结果而发生的,那么如果信号处理程序引用任何具有静态或线程存储持续时间的对象,并且不是通过赋值以外的无锁原子对象,则行为是未定义的到声明为 的对象volatile sig_atomic_t
,或信号处理程序调用标准库中除abort
函数、_Exit
函数、quick_exit
函数或signal
第一个参数等于引起调用的信号对应的信号号的函数 之外的任何函数处理程序。此外,如果对signal
函数的这种调用导致SIG_ERR
返回,则 的值errno
是不确定的。252)252)如果异步信号处理程序生成任何信号,则行为未定义。
POSIX is a lot more generous about what you can do in a signal handler.
POSIX 对您在信号处理程序中可以做什么更加慷慨。
Signal Conceptsin the POSIX 2008 edition says:
POSIX 2008 版中的信号概念说:
If the process is multi-threaded, or if the process is single-threaded and a signal handler is executed other than as the result of:
The process calling
abort()
,raise()
,kill()
,pthread_kill()
, orsigqueue()
to generate a signal that is not blockedA pending signal being unblocked and being delivered before the call that unblocked it returns
the behavior is undefined if the signal handler refers to any object other than
errno
with static storage duration other than by assigning a value to an object declared asvolatile sig_atomic_t
, or if the signal handler calls any function defined in this standard other than one of the functions listed in the following table.The following table defines a set of functions that shall be async-signal-safe. Therefore, applications can invoke them, without restriction, from signal-catching functions:
_Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() … fcntl() pipe() sigpause() write() fdatasync() poll() sigpending()
All functions not in the above table are considered to be unsafe with respect to signals. In the presence of signals, all functions defined by this volume of POSIX.1-2008 shall behave as defined when called from or interrupted by a signal-catching function, with a single exception: when a signal interrupts an unsafe function and the signal-catching function calls an unsafe function, the behavior is undefined.
Operations which obtain the value of
errno
and operations which assign a value toerrno
shall be async-signal-safe.When a signal is delivered to a thread, if the action of that signal specifies termination, stop, or continue, the entire process shall be terminated, stopped, or continued, respectively.
如果进程是多线程的,或者如果进程是单线程的并且执行信号处理程序而不是作为以下结果:
进程调用
abort()
、raise()
、kill()
、pthread_kill()
或sigqueue()
以生成未被阻塞的信号一个挂起的信号被解除阻塞并在解除阻塞它返回的调用之前被传递
如果信号处理程序引用除
errno
静态存储持续时间以外的任何对象,而不是通过为声明为 的对象分配值volatile sig_atomic_t
,或者如果信号处理程序调用本标准中定义的任何函数,而不是在下表。下表定义了一组应为异步信号安全的函数。因此,应用程序可以不受限制地从信号捕获函数中调用它们:
_Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() … fcntl() pipe() sigpause() write() fdatasync() poll() sigpending()
所有不在上表中的函数都被认为是不安全的信号。在存在信号的情况下,本 POSIX.1-2008 卷定义的所有函数在从信号捕获函数调用或被信号捕获函数中断时都应按照定义的方式运行,只有一个例外:当信号中断不安全的函数并且信号捕获函数调用不安全的函数,行为未定义。
获取值的
errno
操作和分配值的操作errno
应该是异步信号安全的。当一个信号被传递给一个线程时,如果该信号的动作指定了终止、停止或继续,则整个进程应分别被终止、停止或继续。
However, the printf()
family of functions is notably absent from that list and may not be called safely from a signal handler.
但是,该printf()
列表中显然没有该系列函数,并且可能无法从信号处理程序中安全地调用。
The POSIX 2016update extends the list of safe functions to include, in particular, a large number of the functions from <string.h>
, which is a particularly valuable addition (or was a particularly frustrating oversight). The list is now:
的POSIX 2016更新延伸的安全功能的列表以包括,特别是,大量的功能从<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()
As a result, you either end up using write()
without the formatting support provided by printf()
et al, or you end up setting a flag that you test (periodically) in appropriate places in your code. This technique is ably demonstrated in the answerby Grijesh Chauhan.
因此,您要么最终在write()
没有printf()
等人提供的格式支持的情况下使用,要么最终在代码中的适当位置设置了一个您(定期)测试的标志。这种技术在Grijesh Chauhan的回答中得到了巧妙的展示。
Standard C functions and signal safety
标准 C 函数和信号安全
chqrlieasksan interesting question, to which I have no more than a partial answer:
How come most string functions from
<string.h>
or the character class functions from<ctype.h>
and many more C standard library functions are not in the list above? An implementation would need to be purposely evil to makestrlen()
unsafe to call from a signal handler.
为什么大多数字符串函数
<string.h>
或字符类函数<ctype.h>
以及更多 C 标准库函数不在上面的列表中?一个实现需要故意邪恶,以使strlen()
从信号处理程序调用不安全。
For many of the functions in <string.h>
, it is hard to see why they were not declared async-signal safe, and I'd agree the strlen()
is a prime example, along with strchr()
, strstr()
, etc. On the other hand, other functions such as strtok()
, strcoll()
and strxfrm()
are rather complex and are not likely to be async-signal safe. Because strtok()
retains state between calls, and the signal handler could not easily tell whether some part of the code that is using strtok()
would be messed up. The strcoll()
and strxfrm()
functions work with locale-sensitive data, and loading the locale involves all sorts of state setting.
对于很多的功能<string.h>
,这是很难理解为什么他们不声明异步信号安全,我会同意的strlen()
就是最好的例子,随着strchr()
,strstr()
等等。另一方面,其他功能,如strtok()
,strcoll()
和strxfrm()
相当复杂,不太可能是异步信号安全的。因为strtok()
在调用之间保留状态,并且信号处理程序无法轻易判断正在使用的代码的某些部分是否strtok()
会被搞砸。该strcoll()
和strxfrm()
与语言环境敏感数据职能的工作,并装载区域涉及各种状态设置的。
The functions (macros) from <ctype.h>
are all locale-sensitive, and therefore could run into the same issues as strcoll()
and strxfrm()
.
from 的函数(宏)<ctype.h>
都是语言环境敏感的,因此可能会遇到与strcoll()
和相同的问题strxfrm()
。
I find it hard to see why the mathematical functions from <math.h>
are not async-signal safe, unless it is because they could be affected by a SIGFPE (floating point exception), though about the only time I see one of those these days is for integerdivision by zero. Similar uncertainty arises from <complex.h>
, <fenv.h>
and <tgmath.h>
.
我发现很难<math.h>
理解为什么来自的数学函数不是异步信号安全的,除非是因为它们可能受到 SIGFPE(浮点异常)的影响,尽管我最近唯一一次看到其中一个是整数被零除。类似的不确定性来自<complex.h>
,<fenv.h>
和<tgmath.h>
。
Some of the functions in <stdlib.h>
could be exempted — abs()
for example. Others are specifically problematic: malloc()
and family are prime examples.
例如,<stdlib.h>
可以免除其中的某些功能abs()
。其他人特别有问题:malloc()
家庭是主要例子。
A similar assessment could be made for the other headers in Standard C (2011) used in a POSIX environment. (Standard C is so restrictive there's no interest in analyzing them in a pure Standard C environment.) Those marked 'locale-dependent' are unsafe because manipulating locales might require memory allocation, etc.
可以对 POSIX 环境中使用的标准 C (2011) 中的其他标头进行类似的评估。(标准 C 是如此严格,没有兴趣在纯标准 C 环境中分析它们。)标记为“依赖于语言环境”的那些是不安全的,因为操作语言环境可能需要内存分配等。
<assert.h>
— Probably not safe<complex.h>
— Possibly safe<ctype.h>
— Not safe<errno.h>
— Safe<fenv.h>
— Probably not safe<float.h>
— No functions<inttypes.h>
— Locale-sensitive functions (unsafe)<iso646.h>
— No functions<limits.h>
— No functions<locale.h>
— Locale-sensitive functions (unsafe)<math.h>
— Possibly safe<setjmp.h>
— Not safe<signal.h>
— Allowed<stdalign.h>
— No functions<stdarg.h>
— No functions<stdatomic.h>
— Possibly safe, probably not safe<stdbool.h>
— No functions<stddef.h>
— No functions<stdint.h>
— No functions<stdio.h>
— Not safe<stdlib.h>
— Not all safe (some are allowed; others are not)<stdnoreturn.h>
— No functions<string.h>
— Not all safe<tgmath.h>
— Possibly safe<threads.h>
— Probably not safe<time.h>
— Locale-dependent (buttime()
is explicitly allowed)<uchar.h>
— Locale-dependent<wchar.h>
— Locale-dependent<wctype.h>
— Locale-dependent
<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>
— 语言环境相关
Analyzing the POSIX headers would be …?harder in that there are a lot of them, and some functions might be safe but many won't be … but also simpler because POSIX says which functions are async-signal safe (not many of them). Note that a header like <pthread.h>
has three safe functions and many unsafe functions.
分析 POSIX 标头会......更难,因为它们有很多,有些函数可能是安全的,但很多不会......但也更简单,因为 POSIX 说哪些函数是异步信号安全的(不是很多) . 请注意,像这样的标头<pthread.h>
具有三个安全功能和许多不安全功能。
NB:Almost all of the assessment of C functions and headers in a POSIX environment is semi-educated guesswork. It is no sense a definitive statement from a standards body.
NB:在 POSIX 环境中,几乎所有对 C 函数和头文件的评估都是半知识的猜测。标准机构的明确声明毫无意义。
回答by alk
How to avoid using
printf
in a signal handler?
如何避免
printf
在信号处理程序中使用?
Always avoid it, will say: Just don't use
printf()
in signal handlers.At least on POSIX conforming systems, you can use
write(STDOUT_FILENO, ...)
instead ofprintf()
. Formatting may not be easy however: Print int from signal handler using write or async-safe functions
总是避免它,会说:只是不要
printf()
在信号处理程序中使用。至少在符合POSIX系统,你可以使用
write(STDOUT_FILENO, ...)
的替代printf()
。然而,格式化可能并不容易:使用写或异步安全函数从信号处理程序打印 int
回答by dwks
For debugging purposes, I wrote a tool which verifies that you are in fact only calling functions on the async-signal-safe
list, and prints a warning message for each unsafe function called within a signal context. While it doesn't solve the problem of wanting to call non async-safe functions from a signal context, it at least helps you find cases where you have done so accidentally.
出于调试目的,我编写了一个工具来验证您实际上只是在调用async-signal-safe
列表中的函数,并为在信号上下文中调用的每个不安全函数打印一条警告消息。虽然它不能解决想要从信号上下文调用非异步安全函数的问题,但它至少可以帮助您找到意外调用的情况。
The source code is on GitHub. It works by overloading signal/sigaction
, then temporarily hiHymaning the PLT
entries of unsafe functions; this causes calls to unsafe functions to be redirected to a wrapper.
源代码在 GitHub 上。它的工作原理是重载signal/sigaction
,然后暂时劫持PLT
不安全函数的条目;这会导致对不安全函数的调用被重定向到包装器。
回答by John Hascall
One technique which is especially useful in programs which have a select loopis to write a single byte down a pipe on receipt of a signal and then handle the signal in the select loop. Something along these lines (error handling and other details omitted for brevity):
在具有选择循环的程序中特别有用的一种技术是在接收到信号时将单个字节写入管道,然后在选择循环中处理该信号。沿着这些路线的东西(为简洁起见省略了错误处理和其他细节):
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 */
}
}
If you care whichsignal it was, then the byte down the pipe can be the signal number.
如果您关心它是哪个信号,那么管道中的字节可以是信号编号。
回答by drlolly
You can use printf in signal handlers if you are using the pthread library. unix/posix specifies that printf is atomic for threads cf Dave Butenhof reply here : https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqwNote that in order to get a clearer picture of printf output, you should run your application in a console(on linux use ctl+alt+f1 to start console 1), rather than a pseudo-tty created by the GUI.
如果您使用 pthread 库,则可以在信号处理程序中使用 printf。unix/posix 指定 printf 是线程的原子 cf Dave Butenhof 在此处回复:https://groups.google.com/forum/#! topic/comp.programming.threads/1-bU71nYgqw 请注意,为了获得更清晰的图片对于 printf 输出, 您应该在控制台中运行您的应用程序(在 linux 上使用 ctl+alt+f1 来启动控制台 1),而不是由 GUI 创建的伪 tty。