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.
So my question is: when do we need to avoid using printf
in a signal handler, and is there a recommended replacement?
printf is non-async-signal-safe because, as you describe, it ends up manipulating global state without synchronisation. For added fun, it's not necessarily re-entrant. In your example, the signal might be handled while the first printf is running, and the second printf could mess up the state of the first call.
The sprintf() and snprintf() functions are Async-Signal-Safe. The printf() and fprintf() functions can be used safely in multithreaded applications, as long as setlocale(3C) is not being called to change the locale.
If you want to ignore the signal specified by the first argument (i.e., pretending that the signal never happens), use SIG_IGN for the second argument. In the above two lines, SIGINT and SIGALRM are ignored.
The signal() system call just installs the handler: "signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a programmer-defined function (a "signal handler")." There you go.
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.
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.
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.
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
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 Handling for detail expiation).
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.
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.
The C standard takes a very conservative view of what you can do in a signal handler:
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.
POSIX is a lot more generous about what you can do in a signal handler.
Signal Concepts in the POSIX 2008 edition says:
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.
However, the printf()
family of functions is notably absent from that list and may not be called safely from a signal handler.
The POSIX 2016 update 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:
_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 answer by Grijesh Chauhan.
chqrlie asks an 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.
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.
The functions (macros) from <ctype.h>
are all locale-sensitive, and therefore could run into the same issues as strcoll()
and 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 integer division by zero. Similar uncertainty arises from <complex.h>
, <fenv.h>
and <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.
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.
<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 (but time()
is explicitly allowed)<uchar.h>
— Locale-dependent<wchar.h>
— Locale-dependent<wctype.h>
— Locale-dependentAnalyzing 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With