Review SIGNAL
Signal can be used to control process. SIGKILL, SIGSTOP, SIGINT are well known types of signal. No process can mask SIGKILL and SIGSTOP.
In a program, you can register signal handler function so that when a specific signal is sent to a process it can invoke registered signal handler function.
And also you can block in a certain part of the code to wait signal.
In multi-thread program, we can set signal mask so that only specified thread can handle the signal.
* How to register signal handler.
Though we've used the function signal() before, it's ANSI compatible one but not a standard. We can use sigaction() in the standard.
sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
If handler is registered with the function above, handler will be called when the signal occurs.
We use struct sigaction structure to register handler, to set mask, flags.
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); // kind of advanced
// used with sigfillset(), sigemptyset(), sigaddset(), sigdelset()
sigset_t sa_mask;
int sa_flags; // option flags like
/*
SA_NOCLDSTOP, // child의 중지시 SIGCHLD 안받음
SA_ONESHOT/SA_RESETHAND : 일회용 핸들러
SA_NOMASK, SA_NODEFFER : 같은 시그널 발생을 block하지 않음
SA_SIGINFO : 핸들러로 sa_sigaction을 사용
*/
void (*sa_restorer)(void);
};
Sample code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void usr_handler(int signum);
int main()
{
struct sigaction sa_usr1;
struct sigaction sa_usr2;
memset(&sa_usr1, 0, sizeof(struct sigaction));
sa_usr1.sa_handler = usr_handler;
sa_usr1.sa_flags = SA_NODEFER;
sigfillset(&sa_usr1.sa_mask);
memset(&sa_usr2, 0, sizeof(struct sigaction));
sa_usr2.sa_handler = usr_handler;
sa_usr2.sa_flags = SA_NODEFER;
sigemptyset(&sa_usr2.sa_mask);
sigaction(SIGUSR1, &sa_usr1, NULL);
sigaction(SIGUSR2, &sa_usr2, NULL);
printf("SIGUSR1, SIGUSR2 Registered!\n");
for (;;)
{
pause();
}
return EXIT_SUCCESS;
}
void usr_handler(int signum)
{
int i;
for (i = 0; i < 5; i++)
{
printf("[%d]Caught signal. will sleep 1 sec : SIGUSR%d\n"
,i , (signum == SIGUSR1) ? 1 : 2);
sleep(1);
}
}
* How to block signal in a process
sigprocmask() offers signal blocking.
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
If oldset is not NULL, current signal mask will be copied into oldset.
* How to handle signal in multi-thread program.
As a default, if program receive signal, all of its thread receive the signal and it will make,. a chaos and program will halt. Generally, people set signal mask so that signal can be handled by specific thread.
Like sigprocmask, threre is pthread_sigmask(). If it's called in main(), all thread created after will have that signal mask. It it's called in a certain thread, only that thread will have that mask. In this way, we can specify which thread will receive signal.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#define THREADNUM 5
#define LIFETIME 10
struct s_thread_ctx
{
pthread_t tid;
int index;
}ctx[THREADNUM];
void sig_handler(int);
void *worker(void *);
int main()
{
void *res;
int i;
sigset_t sigmask, sigmask_old;
sigfillset(&sigmask);
printf("[MAIN] Masking all signals\n");
pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask_old);
for (i = 0; i < THREADNUM; i++)
{
ctx[i].index = i;
pthread_create(&ctx[i].tid, NULL, worker, (void *)&ctx[i]);
printf("[MAIN] Create %dth thread\n", i);
}
printf("[MAIN] Joining threads\n");
for (i = 0; i < THREADNUM; i++)
{
pthread_join(ctx[i].tid, &res);
}
return 0;
}
void *worker(void *arg)
{
int i;
sigset_t sigmask;
struct s_thread_ctx *ctx;
struct sigaction sa;
ctx = (struct s_thread_ctx *)arg;
sigfillset(&sigmask);
sigdelset(&sigmask, SIGINT);
sa.sa_handler = sig_handler;
sigfillset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
#ifdef HANDLER_THREAD
/* only last thread will catch SIGINT */
if (ctx->index == (THREADNUM - 1))
{
pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
}
#endif
/* Just waiting for LIFETIME seconds */
for (i = 0; i < LIFETIME; i++)
sleep(1);
return arg;
}
void sig_handler(int signum)
{
if (signum == SIGINT)
{
printf("[HANDLER] I've got SIGINT\n");
}
return;
}
* Handling signal and waiting signal.
If handler is registered, it would be called when specified signal is sent. And we can also wait until the signal arrives.
int sigsuspend(const sigset_t *mask);
Function sigsuspend will block the program until specified signals in mask arrive.
#include <signal.h>
#include <stdio.h>
#include <string.h>
void handler(int signal)
{
printf("[Handler] Signal handled\n");
return;
}
int main()
{
int sig;
sigset_t set;
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGUSR1);
sigaction(SIGUSR1, &sa, NULL);
sigfillset(&set);
sigdelset(&set, SIGUSR1);
sig = sigsuspend(&set);
printf("SIGUSR1 arrived\n");
return 0;
}
* Signal and process
If a signal is sent to a certain process, it's also sent to all of its child as long as they have same process group id. That means, if a child become a process group leader, signal will not be propagated to it any more.
cf. session = process groups and processes, process group = processes, if pid == pgid, it's process group leader.
related functions : setpid, setpgid, setpgrp, getsid, setsid
> kill -SIGUSR1 -1234 # to send SIGUSR1 signal to all processes which have pgid as 1234