I'm a newbie in c development. Recently, I noticed a problem when I was learning multi-threaded development, when I set a signal in the main thread of Action and when I try to block the signal action set by the main thread in the child thread, I find that it does not work.
Here is a brief description of the code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
void *thread_start(void *_arg) {
sleep(2);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR2);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
printf("child-thread executed\n");
while (true) {
sleep(1);
}
return NULL;
}
void sig_handler(int _sig) {
printf("executed\n");
}
int main(int argc, char *argv[]) {
pthread_t t_id;
int s = pthread_create(&t_id, NULL, thread_start, NULL);
if (s != 0) {
char *msg = strerror(s);
printf("%s\n", msg);
}
printf("main-thread executed, create [%lu]\n", t_id);
signal(SIGUSR2, sig_handler);
while (true) {
sleep(1);
}
return EXIT_SUCCESS;
}
The signal mask is a per-thread property, a thread will inherit whatever the parent has at time of thread creation but, after that, it controls its own copy.
In other words, blocking a signal in a thread only affects the delivery of signals for that thread, not for any other.
In any case, even if it were shared (it's not), you would have a potential race condition since you start the child thread before setting up the signal in the main thread. Hence it would be indeterminate as to whether the order was "parent sets up signal, then child blocks" or vice versa. But, as stated, that's irrelevant due to the thread-specific nature of the signal mask.
If you want a thread to control the signal mask of another thread, you will need to use some form of inter-thread communication to let the other thread do it itself.
As I wrote in a comment, any USR1 signal sent to the process will be delivered using the main thread. It's output will not tell you exactly what happened, so it is not really a good way to test threads and signal masks. Additionally, it uses printf() in a signal handler, which may or may not work: printf() is not an async-signal safe function, so it must not be used in a signal handler.
Here is a better example:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* This function writes a message directly to standard error,
without using the stderr stream. This is async-signal safe.
Returns 0 if success, errno error code if an error occurs.
errno is kept unchanged. */
static int write_stderr(const char *msg)
{
const char *end = msg;
const int saved_errno = errno;
int retval = 0;
ssize_t n;
/* If msg is non-NULL, find the string-terminating '\0'. */
if (msg)
while (*end)
end++;
/* Write the message to standard error. */
while (msg < end) {
n = write(STDERR_FILENO, msg, (size_t)(end - msg));
if (n > 0) {
msg += n;
} else
if (n != 0) {
/* Bug, should not occur */
retval = EIO;
break;
} else
if (errno != EINTR) {
retval = errno;
break;
}
}
/* Paranoid check that exactly the message was written */
if (!retval)
if (msg != end)
retval = EIO;
errno = saved_errno;
return retval;
}
static volatile sig_atomic_t done = 0;
pthread_t main_thread;
pthread_t other_thread;
static void signal_handler(int signum)
{
const pthread_t id = pthread_self();
const char *thread = (id == main_thread) ? "Main thread" :
(id == other_thread) ? "Other thread" : "Unknown thread";
const char *event = (signum == SIGHUP) ? "HUP" :
(signum == SIGUSR1) ? "USR1" :
(signum == SIGINT) ? "INT" :
(signum == SIGTERM) ? "TERM" : "Unknown signal";
if (signum == SIGTERM || signum == SIGINT)
done = 1;
write_stderr(thread);
write_stderr(": ");
write_stderr(event);
write_stderr(".\n");
}
static int install_handler(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = signal_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return -1;
return 0;
}
void *other(void *unused __attribute__((unused)))
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
while (!done)
sleep(1);
return NULL;
}
int main(void)
{
pthread_attr_t attrs;
sigset_t mask;
int result;
main_thread = pthread_self();
other_thread = pthread_self(); /* Just to initialize it to a sane value */
/* Install HUP, USR1, INT, and TERM signal handlers. */
if (install_handler(SIGHUP) ||
install_handler(SIGUSR1) ||
install_handler(SIGINT) ||
install_handler(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Create the other thread. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2*PTHREAD_STACK_MIN);
result = pthread_create(&other_thread, &attrs, other, NULL);
pthread_attr_destroy(&attrs);
if (result) {
fprintf(stderr, "Cannot create a thread: %s.\n", strerror(result));
return EXIT_FAILURE;
}
/* This thread blocks SIGUSR1. */
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
/* Ready to handle signals. */
printf("Send a HUP, USR1, or TERM signal to process %d.\n", (int)getpid());
fflush(stdout);
while (!done)
sleep(1);
pthread_join(other_thread, NULL);
return EXIT_SUCCESS;
}
Save it as e.g. example.c, and compile and run using
gcc -Wall -O2 example.c -pthread -o exprog
./exprog
It will block the USR1 signal in the main thread, and HUP and TERM in the other thread. It will also catch the INT signal (Ctrl+C), which is not blocked in either thread. When you send it the INT or TERM signal, the program will exit.
If you send the program the USR1 signal, you'll see that it will always be delivered using the other thread.
If you send the program a HUP signal, you'll see that it will always be delivered using the main thread.
If you send the program a TERM signal, it too will be delivered using the main thread, but it will also cause the program to exit (nicely).
If you send the program an INT signal, it will be delivered using one of the threads. It depends on several factors whether you'll always see it being delivered using the same thread or not, but at least in theory, it can be delivered using either thread. This signal too will cause the program to exit (nicely).
Related
In the following code, what I am expecting is the console prints ten SIGCHLD caught. I've already queued up the SIGCHLD by setting sa_flags to SA_SIGINFO and using sa_sigaction instead of sa_handler. However, it seems some of the SIGCHLD are lost. Why?
I'm thinking fork() might be interrupted by SIGCHLD so I use SA_RESTART to restart the fork(). I run the same piece of code on different computers. On my MacBook, it says [1] 24481 illegal hardware instruction. On the other Linux computer, less than 10 SIGCHLD caught are printed.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define CHECK(syscall, msg) do { \
if ((syscall) == -1) { \
perror(msg); \
} \
} while(0)
void catch(int signo, siginfo_t *info, void *context) {
if (signo == SIGCHLD) {
printf("SIGCHLD caught\n");
}
}
int main () {
sigset_t new_set;
sigemptyset(&new_set);
sigaddset(&new_set, SIGCHLD);
struct sigaction act;
act.sa_sigaction = catch;
act.sa_mask = new_set;
act.sa_flags = SA_SIGINFO | SA_RESTART;
CHECK(sigaction(SIGCHLD, &act, NULL), "sigaction error");
int pid, i;
for (i = 0; i < 10; i++) {
pid = fork();
if (!pid) return;
}
while (1);
}
SIGCHLD is a standard signal, which means multiple occurrences of it get collapsed into one. Linux kernel maintains a bitset for standard signals, one bit per signal and supports queuing exactly one associated siginfo_t.
Fix:
void catch(int signo, siginfo_t*, void*) {
int status;
pid_t pid;
if(signo == SIGCHLD) {
while((pid = waitpid(-1, &status, WNOHANG)) > 0)
printf("child %u terminated.\n", (unsigned)pid);
}
}
Also note, that you do not need to explicitly block the signal you handle because it is automatically blocked for you, unless SA_NODEFER flag is used.
And, pedantically, only a limited number of async-signal safe functions (see man signal-safety) can be used in a signal handler, printf is not one of those.
Learning about signals, and I was wondering about the subtle differences between the process signal mask, a blocked signal set, a signal handler, and a blocked signal.
The questions involve (on Debian):
sigprocmask(2)
sigsetops(3) related functions
Each process has it's own signal mask (a long which contains the signals being blocked). And a signal set can be obtained by calling sigprocmask(2) with a NULL argument for the *set variable, will result in the old process mask to be put into *oldset, unchanged:
#include <string.h>
#include <signal.h>
void show_signals(const sigset_t exmask)
{
int exsignals[43];
exsignals[0] = SIGABRT;
exsignals[1] = SIGALRM;
exsignals[2] = SIGBUS;
exsignals[3] = SIGCHLD;
exsignals[4] = SIGCONT;
#ifdef SIGEMT
exsignals[5] = SIGEMT;
#else
exsignals[5] = -1;
#endif
exsignals[6] = SIGFPE;
#ifdef SIGFREEZE
exsignals[7] = SIGFREEZE;
#else
exsignals[7] = -1;
#endif
exsignals[8] = SIGHUP;
exsignals[9] = SIGILL;
#ifdef SIGINFO
exsignals[10] = SIGINFO;
#else
exsignals[10] = -1;
#endif
exsignals[11] = SIGINT;
exsignals[12] = SIGIO;
exsignals[13] = SIGIOT;
#ifdef SIGJVM1
exsignals[14] = SIGJVM1;
#else
exsignals[14] = -1;
#endif
#ifdef SIGJVM2
exsignals[15] = SIGJVM2;
#else
exsignals[15] = -1;
#endif
exsignals[16] = SIGKILL;
#ifdef SIGLOST
exsignals[17] = SIGLOST;
#else
exsignals[17] = -1;
#endif
#ifdef SIGLWP
exsignals[18] = SIGLWP;
#else
exsignals[18] = -1;
#endif
exsignals[19] = SIGPIPE;
exsignals[20] = SIGPOLL;
exsignals[21] = SIGPROF;
exsignals[22] = SIGPWR;
exsignals[23] = SIGQUIT;
exsignals[24] = SIGSEGV;
exsignals[25] = SIGSTKFLT;
exsignals[26] = SIGSTOP;
exsignals[27] = SIGSYS;
exsignals[28] = SIGTERM;
#ifdef SIGTHAW
exsignals[29] = SIGTHAW;
#else
exsignals[29] = -1;
#endif
#ifdef SIGTHR
exsignals[30] = SIGTHR;
#else
exsignals[30] = -1;
#endif
exsignals[31] = SIGTRAP;
exsignals[32] = SIGTSTP;
exsignals[33] = SIGTTIN;
exsignals[34] = SIGTTOU;
exsignals[35] = SIGURG;
exsignals[36] = SIGUSR1;
exsignals[37] = SIGUSR2;
exsignals[38] = SIGVTALRM;
#ifdef SIGWAITING
exsignals[39] = SIGWAITING;
#else
exsignals[39] = -1;
#endif
exsignals[40] = SIGWINCH;
exsignals[41] = SIGXCPU;
exsignals[42] = SIGXFSZ;
#ifdef SIGXRES
exsignals[43] = SIGXRES;
#else
exsignals[43] = -1;
#endif
int exsignals_n = 0;
for (;exsignals_n < 43; exsignals_n++) {
if (exsignals[exsignals_n] == -1) continue;
static char *exsignal_name;
exsignal_name = strsignal(exsignals[exsignals_n]);
switch(sigismember(&exmask, exsignals[exsignals_n]))
{
case 0: break;
case 1: printf("YES %s\n", exsignal_name); break;
case -1: printf("could not obtain signal\n"); break;
default: printf("UNEXPECTED for %s return\n", exsignal_name); break;
}
}
}
const sigset_t getmask(void)
{
static sigset_t retmask;
if ((sigprocmask(SIG_SETMASK, NULL, &retmask)) == -1)
printf("could not obtain process signal mask\n");
return retmask;
}
At the beginning of my program, I realize that the process signal mask, has not blocked any signals. I then place a signal handler into the program.
static void sig_abrt(int signo)
{
printf("Caught SIGABRT\n");
}
int main(void)
{
show_signals(getmask());
signal(SIGABRT, sig_abrt);
show_signals(getmask());
return 0;
}
So now there is a signal handler for SIGABRT, but if I were to call sigprocmask(2) again, as above, SIGABRT will not be in the process signal mask. I tried checking with sigismember(3), but the process signal mask will only be modified once I have called sigaddset(3) or another function which modifies the signal mask.
If I block SIGABRT with sigaddset(3), will the signal handler sig_abrt not receive the call when the SIGABRT is delivered? Does it mean that the signal mask affects which signals are delivered? What is the difference?
Also, is there a way to block a signal in a process without using the sigsetops(3) and sigprocmask(2) functions?
Each process has it's [sic] own signal mask (a long which contains the signals being blocked)
Well, no. The signal mask is actually thread-specific. (In a multithreaded program, you must use pthread_sigmask() to manipulate the signal mask for the current thread; in a single-threaded program, you can use sigprocmask().)
Also, it's not "a long". It is of type sigset_t, which might be an array, structure, or union type. In any case, one should consider it simply as an unordered bit set, one bit per signal.
So now there is a signal handler for SIGABRT, but SIGABRT will not be in the process signal mask.
Correct. Whether or not you have assigned a signal handler or not, does not affect the signal mask at all.
If I block SIGABRT with sigaddset(3), will the signal handler sig_abrt not receive the call when the SIGABRT is delivered? Does it mean that the signal mask affects which signals are delivered? What is the difference?
If all your threads block SIGABRT, it will not be delivered until either the signal is unblocked (removed from the signal mask). If the signal is consumed using sigwait(), sigwaitinfo(), or sigtimedwait(), the signal handler will not be invoked at all.
A short summary:
Signals can be directed to a process group (kill() with pid == 0 or pid == -pgid), a specific process (pid), or a specific thread in a specific process (pthread_kill() within the same process, tgkill system call in Linux in general).
If a signal is directed to a process group, each process in that group receives "a copy" of the signal.
The signal mask defines whether signals are blocked, or delivered immediately.
In each process, each signal
can have a signal handler, or
be ignored (SIG_IGN "handler"), or
have the default disposition (ignored (Ign), terminates the process with (Core) or without (Term) a core dump; or it can stop (Stop) or continue (Cont) the execution of the target thread or process). See man 7 signal for details.
If some, but not all threads, block a signal, and the signal is not targeted to a specific thread, the kernel directs the signal to one of the threads that are not blocking the signal (at random).
There are two ways of catching a signal:
Using a signal handler. The signal gets delivered to a signal handler only when the signal is not blocked. If the signal is blocked, the delivery of the signal is pending until not blocked (or caught by the other option below).
sigwait(), sigwaitinfo(), or sigtimedwait(). These functions check if any signals are pending, and if so, "catch" it. The set of signals they check is defined by a function parameter of sigset_t type.
When the kernel sends/forwards a signal to a process, it first checks if the process has a thread that is not blocking that signal. If there is such a thread, it delivers it via that thread. (If the signal has a signal handler, that signal handler gets invoked in that thread; otherwise, the effect is dictated by the signal disposition.)
If the signal is blocked, the kernel leaves it pending for the process.
If the process calls sigwait(), sigwaitinfo(), or sigtimedwait() with the pending signal in the specified signals set, it receives the information on that signal, and the signal is caught. (It will no longer be pending, and it will not cause a signal handler to be invoked; it is "consumed".)
If the process changes its signal mask, so that the pending signal becomes unblocked, it is delivered by the kernel (just as if it was sent at that point in time).
Also, is there a way to block a signal in a process without using the sigsetops(3) and sigprocmask(2) functions?
No. (You can implement your own sigsetops() and a syscall wrapper for sigprocmask(), but that's about it.)
Here is an example program, example.c, you can use for exploring signal handlers, catching signals, and the signal mask, in a single-threaded process:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
/* Async-signal safe write-to-standard error function.
Keeps errno unchanged. Do not use stderr otherwise!
*/
static int wrerrpp(const char *ptr, const char *end)
{
const int saved_errno = errno;
ssize_t chars;
while (ptr < end) {
chars = write(STDERR_FILENO, ptr, (size_t)(end - ptr));
if (chars > 0)
ptr += chars;
else
if (chars != -1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR) {
const int retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
/* Write the supplied string to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerr(const char *ptr)
{
if (!ptr)
return 0;
else {
const char *end = ptr;
/* strlen() is not async-signal safe, so
find the end of the string the hard way. */
while (*end)
end++;
return wrerrpp(ptr, end);
}
}
/* Write the supplied long to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerrnum(const long value)
{
unsigned long u = (value < 0) ? (unsigned long)-value : (unsigned long)value;
char buf[40];
char *ptr = buf + sizeof buf;
char *const end = buf + sizeof buf;
do {
*(--ptr) = '0' + (u % 10uL);
u /= 10uL;
} while (u > 0uL);
if (value < 0)
*(--ptr) = '-';
return wrerrpp(ptr, end);
}
/* Async-signal safe variant of strsignal().
Only covers a small subset of all signals.
Returns NULL if the signal name is not known. */
static const char *signal_name(const int signum)
{
switch (signum) {
case SIGHUP: return "HUP";
case SIGINT: return "INT";
case SIGQUIT: return "QUIT";
case SIGKILL: return "KILL";
case SIGSEGV: return "SEGV";
case SIGTERM: return "TERM";
case SIGUSR1: return "USR1";
case SIGUSR2: return "USR2";
case SIGCHLD: return "CHLD";
case SIGCONT: return "CONT";
case SIGSTOP: return "STOP";
default: return NULL;
}
}
/* Signal handler that reports its delivery immediately,
but does nothing else.
*/
static void report_signal(int signum, siginfo_t *info, void *ctx)
{
const char *sname = signal_name(signum);
wrerr("report_signal(): Received signal ");
if (sname)
wrerr(sname);
else
wrerrnum(signum);
if (info->si_pid) {
wrerr(" from process ");
wrerrnum(info->si_pid);
wrerr(".\n");
} else
wrerr(" from kernel or terminal.\n");
}
/* Install report_signal() handler.
*/
static int install_report_signal(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_sigaction = report_signal;
act.sa_flags = SA_SIGINFO;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
sigset_t mask;
siginfo_t info;
const char *name;
int signum;
if (install_report_signal(SIGINT) ||
install_report_signal(SIGCONT)) {
const char *errmsg = strerror(errno);
wrerr("Cannot install signal handlers: ");
wrerr(errmsg);
wrerr(".\n");
return EXIT_FAILURE;
}
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_SETMASK, &mask, NULL);
printf("Process %ld is ready to receive signals! Run\n", (long)getpid());
printf("\tkill -USR1 %ld\n", (long)getpid());
printf("\tkill -USR2 %ld\n", (long)getpid());
printf("\tkill -HUP %ld\n", (long)getpid());
printf("\tkill -TERM %ld\n", (long)getpid());
printf("in another terminal; press Ctrl+C in this terminal; or press Ctrl+Z and run\n");
printf("\tfg\n");
printf("in this terminal.\n");
fflush(stdout);
/* Almost same as blocked mask, just without SIGUSR1 and SIGUSR2. */
sigemptyset(&mask);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
do {
do {
signum = sigwaitinfo(&mask, &info);
} while (signum == -1 && errno == EINTR);
if (signum == -1) {
const char *errmsg = strerror(errno);
wrerr("sigwaitinfo(): ");
wrerr(errmsg);
wrerr(".\n");
return EXIT_FAILURE;
}
name = signal_name(signum);
if (name)
printf("main(): Received signal %s from ", name);
else
printf("main(): Received signal %d from ", signum);
if (info.si_pid == 0)
printf("kernel or terminal.\n");
else
printf("process %ld.\n", (long)info.si_pid);
fflush(stdout);
} while (signum != SIGTERM);
return EXIT_SUCCESS;
}
Compile it using for example
gcc -Wall -O2 example.c -o example
I suggest you prepare two terminals. In one terminal, run the compiled program, using
./example
and observe its output. It will be something like
Process 843 is ready to receive signals! Run
kill -USR1 843
kill -USR2 843
kill -HUP 843
kill -TERM 843
in another terminal; press Ctrl+C in this terminal; or press Ctrl+Z and run
fg
in this terminal.
The KILL and STOP signals cannot be caught. KILL will always kill the process, and STOP will always stop ("pause") the process.
If you press Ctrl+C in that terminal, the kernel will send an INT signal to the process. (This will be delivered via the report_signal() signal handler.)
If you press Ctrl+Z in that terminal, the kernel will send a STOP signal to the process. The shell detects this, pushing ./example under job control, and lets you input new shell commands. The fg command brings ./example back to foreground, with the shell sending it the CONT signal, so that ./example will continue execution.
USR1 and USR2 signals are blocked, so they are never delivered to the report_signal() signal handler.
HUP and TERM signals are also blocked, but they are received by the main thread via sigwaitinfo().
The program exits, when it receives a TERM signal.
I written the below code to handle signals in separate thread to forcefully cleanup some resources and exit the complete process.
Here is the brief note about the below code.
When the signal is received, set volatile sig_atomic_t sig_set_flag = 1; inside signal handler.
In signal_handler_thread, checking sig_set_flag value in a loop.
if(sig_set_flag==1) send notifications like "i am going down" from signal_handler_thread and call exit(0); from the thread.
Signals can be received by any thread in a process. So i am setting the global variable.
I have 2 questions.
1) This implementation is fine? or i have to block the signals for the main thread and handle only by the spawned thread ?
2) How to block a signal to the main process and handle it in a thread?
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
/*
* Set this variable if any signal is received
*/
volatile sig_atomic_t sig_set_flag = 0;
pthread_mutex_t cleanup_mutex;
/*
* Resource cleanup function.
*/
int cleaup_resources() {
pthread_mutex_lock(&cleanup_mutex);
/*
* Send notification to all the clients.
* Delete all the temp files
*/
printf("Notified to clients.Exiting process\n");
pthread_mutex_unlock(&cleanup_mutex);
return 0;
}
/*
* Signal handler thread
*/
void sig_term_handler(int sig_num) {
sig_set_flag = sig_num;
}
/*
* Signal handler thread routine
*/
void *signal_handler_thread(void * args) {
while(1) {
if(sig_set_flag != 0) {
printf("%s : Signal flag is set for sig_no %d\n",__func__,sig_set_flag);
cleaup_resources();
break;
}
usleep(5);
}
exit(0);
}
int main()
{
int loop_count,status;
pthread_t tid;
pid_t pid;
struct sigaction sig;
sig.sa_handler = &sig_term_handler;
sig.sa_flags = 0;
sigaction(SIGTERM, &sig, NULL);
/*
* Spawn a thread to monitor signals.
* If signal received, Exit the process.
*/
pthread_create(&tid, NULL, signal_handler_thread, NULL);
while(1) {
printf("Some time consuming task in progress... PID = %d\n",getpid());
pid = fork();
if(pid == 0) {
sleep(100);
return 0;
} else {
waitpid(pid, &status, 0);
loop_count++;
if( loop_count>=10)
break;
}
}
cleaup_resources();
exit(0);
}
Note:I know signals will interrupt the some system calls and EINTR will be set. Unfortunately some system calls (i.e) waitpid() will not be interrupted. So i spawned a thread to handle this scenario.
1) Your implementation seems to be correct. signal() and sigaction() register a handler function for the whole process, so it doesn't matter you call them in the main thread or in the spawned thread.
2) To block a signal in the main thread, and handle it in a thread, you have to design, not a handler function, but a handler thread, using sigwait() or sigwaitinfo(). So the thread will wait for the signals and the program execution won't be interrupted.
In this case, you have to block process-wide signals in all the threads, including the main thread. If it is not blocked, the signal will have the default behavior on the program.
You have to use pthread_sigmask() to block one or more signals. An example of code to block SIGTERM:
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGTERM);
pthread_sigmask(SIG_BLOCK,&set,NULL);
When a thread is created, it inherits of the blocked signals of the creator thread.
I modified your code to show you how to use sigwaitinfo() and pthread_sigmask():
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
pthread_mutex_t cleanup_mutex;
/*
* Resource cleanup function.
*/
int cleaup_resources() {
pthread_mutex_lock(&cleanup_mutex);
/*
* Send notification to all the clients.
* Delete all the temp files
*/
printf("Notified to clients.Exiting process\n");
pthread_mutex_unlock(&cleanup_mutex);
return 0;
}
/*
* Signal handler thread routine
*/
void *signal_handler_thread(void * args) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
siginfo_t info;
while(1) {
sigwaitinfo(&set,&info);
if(info.si_signo == SIGINT){
printf("\nSIGINT received\n");
cleaup_resources();
exit(0);
}
}
}
int main()
{
int loop_count,status;
pthread_t tid;
pid_t pid;
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
pthread_sigmask(SIG_BLOCK,&set,NULL);
// The new thread will inherit the blocked
// signals from the thread that create it:
pthread_create(&tid, NULL, signal_handler_thread, NULL);
while(1) {
printf("Some time consuming task in progress... PID = %d\n",getpid());
pid = fork();
if(pid == 0) {
sleep(100);
return 0;
} else {
waitpid(pid, &status, 0);
loop_count++;
if( loop_count>=10)
break;
}
}
cleaup_resources();
exit(0);
}
Also, be careful of the fork(), from the tests I have done, the child process will inherit of the blocked signals.
If I setup and signal handler for SIGABRT and meanwhile I have a thread that waits on sigwait() for SIGABRT to come (I have a blocked SIGABRT in other threads by pthread_sigmask).
So which one will be processed first ? Signal handler or sigwait() ?
[I am facing some issues that sigwait() is get blocked for ever. I am debugging it currently]
main()
{
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGABRT);
sigprocmask(SIG_BLOCK, &signal_set, NULL);
// Dont deliver SIGABORT while running this thread and it's kids.
pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
pthread_create(&tAbortWaitThread, NULL, WaitForAbortThread, NULL);
..
Create all other threads
...
}
static void* WaitForAbortThread(void* v)
{
sigset_t signal_set;
int stat;
int sig;
sigfillset( &signal_set);
pthread_sigmask( SIG_BLOCK, &signal_set, NULL ); // Dont want any signals
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGABRT); // Add only SIGABRT
// This thread while executing , will handle the SIGABORT signal via signal handler.
pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
stat= sigwait( &signal_set, &sig ); // lets wait for signal handled in CatchAbort().
while (stat == -1)
{
stat= sigwait( &signal_set, &sig );
}
TellAllThreadsWeAreGoingDown();
sleep(10);
return null;
}
// Abort signal handler executed via sigaction().
static void CatchAbort(int i, siginfo_t* info, void* v)
{
sleep(20); // Dont return , hold on till the other threads are down.
}
Here at sigwait(), i will come to know that SIGABRT is received. I will tell other threads about it. Then will hold abort signal handler so that process is not terminated.
I wanted to know the interaction of sigwait() and the signal handler.
From sigwait() documentation :
The sigwait() function suspends execution of the calling thread until
one of the signals specified in the signal set becomes pending.
A pending signal means a blocked signal waiting to be delivered to one of the thread/process. Therefore, you need not to unblock the signal like you did with your pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL) call.
This should work :
static void* WaitForAbortThread(void* v){
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGABRT);
sigwait( &signal_set, &sig );
TellAllThreadsWeAreGoingDown();
sleep(10);
return null;
}
I got some information from this <link>
It says :
To allow a thread to wait for asynchronously generated signals, the threads library provides the sigwait subroutine. The sigwait subroutine blocks the calling thread until one of the awaited signals is sent to the process or to the thread. There must not be a signal handler installed on the awaited signal using the sigwait subroutine.
I will remove the sigaction() handler and try only sigwait().
From the code snippet you've posted, it seems you got the use of sigwait() wrong. AFAIU, you need WaitForAbortThread like below:
sigemptyset( &signal_set); // change it from sigfillset()
for (;;) {
stat = sigwait(&signal_set, &sig);
if (sig == SIGABRT) {
printf("here's sigbart.. do whatever you want.\n");
pthread_kill(tid, signal); // thread id and signal
}
}
I don't think pthread_sigmask() is really needed. Since you only want to handle SIGABRT, first init signal_set as empty then simply add SIGABRT, then jump into the infinite loop, sigwait will wait for the particular signal that you're looking for, you check the signal if it's SIGABRT, if yes - do whatever you want. NOTE the uses of pthread_kill(), use it to sent any signal to other threads specified via tid and the signal you want to sent, make sure you know the tid of other threads you want to sent signal. Hope this will help!
I know this question is about a year old, but I often use a pattern, which solves exactly this issue using pthreads and signals. It is a little length but takes care of any issues I am aware of.
I recently used in combination with a library wrapped with SWIG and called from within Python. An annoying issue was that my IRQ thread waiting for SIGINT using sigwait never received the SIGINT signal. The same library worked perfectly when called from Matlab, which didn't capture the SIGINT signal.
The solution was to install a signal handler
#define _NTHREADS 8
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>
#include <linux/unistd.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h> // strerror
#define CallErr(fun, arg) { if ((fun arg)<0) \
FailErr(#fun) }
#define CallErrExit(fun, arg, ret) { if ((fun arg)<0) \
FailErrExit(#fun,ret) }
#define FailErrExit(msg,ret) { \
(void)fprintf(stderr, "FAILED: %s(errno=%d strerror=%s)\n", \
msg, errno, strerror(errno)); \
(void)fflush(stderr); \
return ret; }
#define FailErr(msg) { \
(void)fprintf(stderr, "FAILED: %s(errno=%d strerror=%s)\n", \
msg, errno, strerror(errno)); \
(void)fflush(stderr);}
typedef struct thread_arg {
int cpu_id;
int thread_id;
} thread_arg_t;
static jmp_buf jmp_env;
static struct sigaction act;
static struct sigaction oact;
size_t exitnow = 0;
pthread_mutex_t exit_mutex;
pthread_attr_t attr;
pthread_t pids[_NTHREADS];
pid_t tids[_NTHREADS+1];
static volatile int status[_NTHREADS]; // 0: suspended, 1: interrupted, 2: success
sigset_t mask;
static pid_t gettid( void );
static void *thread_function(void *arg);
static void signalHandler(int);
int main() {
cpu_set_t cpuset;
int nproc;
int i;
thread_arg_t thread_args[_NTHREADS];
int id;
CPU_ZERO( &cpuset );
CallErr(sched_getaffinity,
(gettid(), sizeof( cpu_set_t ), &cpuset));
nproc = CPU_COUNT(&cpuset);
for (i=0 ; i < _NTHREADS ; i++) {
thread_args[i].cpu_id = i % nproc;
thread_args[i].thread_id = i;
status[i] = 0;
}
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_mutex_init(&exit_mutex, NULL);
// We pray for no locks on buffers and setbuf will work, if not we
// need to use filelock() on on FILE* access, tricky
setbuf(stdout, NULL);
setbuf(stderr, NULL);
act.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT;
act.sa_handler = signalHandler;
sigemptyset(&act.sa_mask);
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
if (setjmp(jmp_env)) {
if (gettid()==tids[0]) {
// Main Thread
printf("main thread: waiting for clients to terminate\n");
for (i = 0; i < _NTHREADS; i++) {
CallErr(pthread_join, (pids[i], NULL));
if (status[i] == 1)
printf("thread %d: terminated\n",i+1);
}
// On linux this can be done immediate after creation
CallErr(pthread_attr_destroy, (&attr));
CallErr(pthread_mutex_destroy, (&exit_mutex));
return 0;
}
else {
// Should never happen
printf("worker thread received signal");
}
return -1;
}
// Install handler
CallErr(sigaction, (SIGINT, &act, &oact));
// Block SIGINT
CallErr(pthread_sigmask, (SIG_BLOCK, &mask, NULL));
tids[0] = gettid();
srand ( time(NULL) );
for (i = 0; i < _NTHREADS; i++) {
// Inherits main threads signal handler, they are blocking
CallErr(pthread_create,
(&pids[i], &attr, thread_function,
(void *)&thread_args[i]));
}
if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL)) {
fprintf(stderr, "main thread: can't block SIGINT");
}
printf("Infinite loop started - CTRL-C to exit\n");
for (i = 0; i < _NTHREADS; i++) {
CallErr(pthread_join, (pids[i], NULL));
//printf("%d\n",status[i]);
if (status[i] == 2)
printf("thread %d: finished succesfully\n",i+1);
}
// Clean up and exit
CallErr(pthread_attr_destroy, (&attr));
CallErr(pthread_mutex_destroy, (&exit_mutex));
return 0;
}
static void signalHandler(int sig) {
int i;
pthread_t id;
id = pthread_self();
for (i = 0; i < _NTHREADS; i++)
if (pids[i] == id) {
// Exits if worker thread
printf("Worker thread caught signal");
break;
}
if (sig==2) {
sigaction(SIGINT, &oact, &act);
}
pthread_mutex_lock(&exit_mutex);
if (!exitnow)
exitnow = 1;
pthread_mutex_unlock(&exit_mutex);
longjmp(jmp_env, 1);
}
void *thread_function(void *arg) {
cpu_set_t set;
thread_arg_t* threadarg;
int thread_id;
threadarg = (thread_arg_t*) arg;
thread_id = threadarg->thread_id+1;
tids[thread_id] = gettid();
CPU_ZERO( &set );
CPU_SET( threadarg->cpu_id, &set );
CallErrExit(sched_setaffinity, (gettid(), sizeof(cpu_set_t), &set ),
NULL);
int k = 8;
// While loop waiting for exit condition
while (k>0) {
sleep(rand() % 3);
pthread_mutex_lock(&exit_mutex);
if (exitnow) {
status[threadarg->thread_id] = 1;
pthread_mutex_unlock(&exit_mutex);
pthread_exit(NULL);
}
pthread_mutex_unlock(&exit_mutex);
k--;
}
status[threadarg->thread_id] = 2;
pthread_exit(NULL);
}
static pid_t gettid( void ) {
pid_t pid;
CallErr(pid = syscall, (__NR_gettid));
return pid;
}
I run serveral tests and the conbinations and results are:
For all test cases, I register a signal handler by calling sigaction in the main thread.
main thread block target signal, thread A unblock target signal by calling pthread_sigmask, thread A sleep, send target signal.
result: signal handler is executed in thread A.
main thread block target signal, thread A unblock target signal by calling pthread_sigmask, thread A calls sigwait, send target signal.
result: sigwait is executed.
main thread does not block target signal, thread A does not block target signal, thread A calls sigwait, send target signal.
result: main thread is chosen and the registered signal handler is executed in the main thread.
As you can see, conbination 1 and 2 are easy to understand and conclude.
It is:
If a signal is blocked by a thread, then the process-wide signal handler registered by sigaction just can't catch or even know it.
If a signal is not blocked, and it's sent before calling sigwait, the process-wide signal handler wins. And that's why APUE the books require us to block the target signal before calling sigwait. Here I use sleep in thread A to simulate a long "window time".
If a signal is not blocked, and it's sent when sigwait has already been waiting, sigwait wins.
But you should notice that for test case 1 and 2, main thread is designed to block the target signal.
At last for test case 3, when main thread is not blocked the target signal, and sigwait in thread A is also waiting, the signal handler is executed in the main thread.
I believe the behaviour of test case 3 is what APUE talks about:
From APUE ยง12.8:
If a signal is being caught (the process has established a signal
handler by using sigaction, for example) and a thread is waiting for
the same signal in a call to sigwait, it is left up to the
implementation to decide which way to deliver the signal. The
implementation could either allow sigwait to return or invoke the
signal handler, but not both.
Above all, if you want to accomplish one thread <-> one signal model, you should:
block all signals in the main thread with pthread_sigmask (subsequent thread created in main thread inheris the signal mask)
create threads and call sigwait(target_signal) with target signal.
test code
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
FILE* file;
void* threadA(void* argv){
fprintf(file, "%ld\n", pthread_self());
sigset_t m;
sigemptyset(&m);
sigaddset(&m, SIGUSR1);
int signo;
int err;
// sigset_t q;
// sigemptyset(&q);
// pthread_sigmask(SIG_SETMASK, &q, NULL);
// sleep(50);
fprintf(file, "1\n");
err = sigwait(&m, &signo);
if (err != 0){
fprintf(file, "sigwait error\n");
exit(1);
}
switch (signo)
{
case SIGUSR1:
fprintf(file, "SIGUSR1 received\n");
break;
default:
fprintf(file, "?\n");
break;
}
fprintf(file, "2\n");
}
void hello(int signo){
fprintf(file, "%ld\n", pthread_self());
fprintf(file, "hello\n");
}
int main(){
file = fopen("daemon", "wb");
setbuf(file, NULL);
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = hello;
sigaction(SIGUSR1, &sa, NULL);
sigset_t n;
sigemptyset(&n);
sigaddset(&n, SIGUSR1);
// pthread_sigmask(SIG_BLOCK, &n, NULL);
pthread_t pid;
int err;
err = pthread_create(&pid, NULL, threadA, NULL);
if(err != 0){
fprintf(file, "create thread error\n");
exit(1);
}
pause();
fprintf(file, "after pause\n");
fclose(file);
return 0;
}
run with ./a.out & (run in the background), and use kill -SIGUSR1 pid to test. Do not use raise. raise, sleep, pause are thread-wide.
in a single main() function,so need signal handling. Use Posix Message Queue IPC mechanism , can ignore the priority and other linked list message,to implement the scenario:
client:Knock Knock
server:who's there
client: pilcrow
Server:pilcrow,thanks a lot.
client:exit
all process terminated
stdin->POSIX MsgQ client send "knock knock" to server->Server compares string and send "who's there" back to client
What I got is :
client:knock knock
Server:Who's there?
client:pilcrow
pilcrow
client:Exit
Exit
1st round successfully give me the right result.From 2nd round, the client output the same typing on console.
Please help. Remember to use gcc -lrt to link mq_function.
Below is my code,
#include <mqueue.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#define MSG_SIZE 100 //max size of msg
#define MAX_MSG 1 //max # of msg
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
volatile sig_atomic_t mqflag; /* set nonzero by signal handler */
static void sig_usr1(int);
sigset_t zeromask, newmask, oldmask;
int main(int argc, char **argv) {
int c,flags;/* for getopt() */
pid_t child_pid;
mqd_t msgq_id;
struct mq_attr attr;
struct sigevent sigev;
char *buff_forward,*buff_backward;
flags=O_RDWR | O_CREAT;
attr.mq_msgsize=MSG_SIZE;
attr.mq_maxmsg=MAX_MSG;
buff_forward=malloc(attr.mq_msgsize);
buff_backward=malloc(attr.mq_msgsize);
while ((c= getopt(argc, argv, "e")) != -1) {
switch (c) {
case 'e': /* create the queue exclusive */
flags|= O_EXCL;
break;
}
}
if (optind!=argc-1){
printf("usage: [-e] <name>");
exit(1);
}
msgq_id = mq_open(argv[optind],flags,FILE_MODE,&attr);
/* producing the message */
mq_getattr(msgq_id, &attr) ;
printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- "
"large at most %ld bytes each\n\t- currently holds %ld messages\n",
argv[optind], attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);
sigemptyset(&zeromask); /* no signals blocked */
sigemptyset(&newmask);
sigemptyset(&oldmask);
sigaddset(&newmask, SIGUSR1);
/* establish signal handler, enable notification */
signal(SIGUSR1, sig_usr1);
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
sigprocmask(SIG_BLOCK, &newmask, &oldmask);/* block SIGUSR1 */
if ((child_pid=fork())==0){
for (; ; ) {
while (mqflag == 0)
sigsuspend(&zeromask);
mqflag =0; /* reset flag */
msgq_id=mq_open(argv[optind],O_RDONLY);
mq_receive(msgq_id, buff_forward, attr.mq_msgsize, NULL);
mq_close(msgq_id);
if (strcasecmp ("Knock Knock",buff_forward)==0){
strcpy(buff_backward,"Server:Who's there?");
}
else if(strcasecmp ("pilcrow", buff_forward)==0){
strcpy(buff_backward,"Server:Pilcrow,thanks a lot!");
}
else if(strcasecmp ("Exit",buff_forward)==0){
kill(getppid(),SIGTERM);
exit(0);
}
msgq_id=mq_open(argv[optind],O_WRONLY);
mq_send(msgq_id,buff_backward,MSG_SIZE,NULL);
mq_close(msgq_id);
mq_notify(msgq_id, &sigev); /* reregister */
}
sigprocmask(SIG_UNBLOCK, &newmask, NULL); /* unblock SIGUSR1 */
exit(0);
}
else if(child_pid>0){
for(;;){
printf("client:");
gets(buff_forward);
msgq_id=mq_open(argv[optind],O_WRONLY);
mq_send(msgq_id,buff_forward,MSG_SIZE,NULL);
mq_close(msgq_id);
mq_notify(msgq_id, &sigev);
while(mqflag==0)
sigsuspend(&zeromask);
mqflag==0;
msgq_id=mq_open(argv[optind],O_RDONLY);
mq_receive(msgq_id, buff_backward, attr.mq_msgsize, NULL);
printf("%s\n",buff_backward);
mq_close(msgq_id);
}
sigprocmask(SIG_UNBLOCK, &newmask, NULL); /* unblock SIGUSR1 */
exit(0);
}
return (EXIT_SUCCESS);
}
static void sig_usr1(int signo) {
mqflag = 1;
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
return;
}
The child calls sigsuspend before calling mq_notify (because mq_flag is implicitly initialized to zero and the check for interruption is improperly performed before notification). It can never be woken up as intended.
Initialize mq_flag to 1 to see the difference. Then refactor.
UPDATED
The OP changed the code substantially, so I changed this answer, too.