Thursday, April 27, 2017

Signal - signal handlers

There are two types of interrupts 1. Hardware interrupts
2. Software interrupts. Software interrupts are called signals.


How to Generate signals:
Signals are generated by 3 ways.
1. Automatically generated by process
   eg. when you access memory which is not allocated to process.
2. Process generates it willingly eg. abort(), raise()
3. Generates the signal by kill command and send it to
   specific process id. eg. kill -3 1234

What happens when signal is generated.
1. When signal generated by internally or by  kill command,
   kernel will first check that the signaling process has
   enough permission to send singal to specific process, if
   not - it will give error.
2. It sends signal to process.
3. Now process has following way to handle the signal.
    3.1 If process has not registered signal handler, the default signal handler will be called. (Most of default handler terminates the process)
    3.2 If process has registered to ignore the signal, the signal will be ignored.
    3.3 If process has registered the signal handler, that signal handler will be called.
    3.4 If process has masked the received signal, signal will be queued and when the signal is unmasked by the process - it will call the signal handler.
    3.5 If process is busy in any syscall, if that syscall is light weight eg socket(), it will be completed otherwise system call will be interrupted eg. read(), write(), pipe(), and then signal handler will be called.
4. After call of signal handler, the process is resumed from where it was.

To see list of signals, use command kill -l. The signals SIGKILL and SIGSTOP can not be caught, blocked, or ignored. Even in signal handler , all system calls are not
supported. If process uses abort() function, abort() generates SIGABRT twice.So if the signal handler of SIGABRT is registered, on first SIGABRT the registered
handler will be called.  It restores default state and on second SIGABRT - default signal handler will be called that will terminate the process.


List of few signals:

SIGSEGV: generated when invalid read or write operation is done in memory.
SIGBUS: generated when process accesses mmaped memory that does not corresponds to file.  eg. mmaped file is truncated and still process tries to access address of truncated area.
SIGCHLD: when child process exits/dies, this signal is sent to its parent process.
SIGFPE: few system generates this signal when integer is divided by 0.
SIGINTR: Ctrl+c
SIGSTOP: Ctrl+z
SIGQUIT: Ctrl+\
SIGHUP: generated on death of controlling process.
SIGILL: generated when process executes illegal instruction.
SIGABRT: generated when process executes abort().

SIGPIPE: generated when process tries to write on descriptor and there is no one available to read that data.

Signal and fork:
When process is forked, for the child process the signal queue will be empty even if parent has the pending queue.Signal handler and blocked signal state will be inherited by child. Signal mask is preserved in exec.

Signal and thread:
1. If no thread has blocked the signal, signal is delivered to any thread  and signal handler(default/registered) will be called.
2. If thread has blocked the signal, signal is delivered to any thread who has not blocked the signal.If all threads have blocked the signal, signal will be queued. Thread can block the signal by pthread_sigmask().
3. A thread can send signal to other thread by thread_kill.


Example of signal(). However, signal() is deprecated so use sigaction() instead:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

int enable_sigquit = 0;
int flip = 0;

void sig_handler(int signo)
{
    if (signo == SIGINT) {
        if (flip == 0) {
            enable_sigquit = 1;
        }
        printf("SIGINT received.\n");
    } else {
        printf("received singnal - %d\n", signo);
    }
}

int main(void)
{
    sigset_t maskset;

    if (sigemptyset(&maskset) == -1) {
        printf("sigemptyset failed for '%m'\n");
        return 0;
    }
    /*
     * add SIGQUIT to maskset.
     */
    if (sigaddset(&maskset, SIGQUIT) == -1) {
        printf("sigaddset failed for '%m'\n");
        return 0;
    }
    /*
     * block SIGQUIT
     */
    if (sigprocmask(SIG_BLOCK, &maskset, NULL) == -1) {
        printf("sigprocmask block failed for '%m'\n");
        return 0;
    }

    /*
     * try to register signal handler for SIGKILL
     */
    if (signal(SIGKILL, sig_handler) == SIG_ERR) {
        printf("can not handle SIGKILL\n");
    }

    /*
     * register signal handler for SIGINT
     */
    if (signal(SIGINT, sig_handler) == SIG_ERR) {
        printf("signal failed for '%m'\n");
        return 0;
    }

    /*
     * ignore signal SIGUSR1
     */
    if (signal(SIGUSR1, SIG_IGN) == SIG_ERR) {
        printf("signal SIG_IGN failed for '%m'\n");
        return 0;
    }
    printf("SIGQUIT is blocked, to unblocked it, give SIGINT to this process.\n");
    while(1) {
        sleep(1);
        if (enable_sigquit) {
            if (sigprocmask(SIG_UNBLOCK, &maskset, NULL) == -1) {
                printf("sigprocmask block failed for '%m'\n");
                return 0;
            }
            printf("SIGQUIT is unblocked.\n");
            enable_sigquit = 0;
            flip = 1;
        }
    }
  return 0;
}