Signals in Linux

July 3, 2021 0 Comments

Signals in Linux

Signals in Linux, Signals are Asynchronous events deliveres to a process by the signal subsystem in order to notify the process about an event that occurred. Linux supports 64 signals. Linux provides two different categories of signals.

  1. General-purpose signals
  2. Real-time signals

General-purpose signals:

Use as system event notifications. each signal identified with a name or an integer value which is a map to a predefined event.

Real-time signals:

Use as inter-process communication. the process should map the events for each signal in this category as per application requirements. These signals do not have pre-assigned names. They are priority ordered (32-64).

When a signal delivered to a process how that process responds to the signal called as signal disposition. A process can respond to a signal synchronously or asynchronously.

Synchronous Mode:

In this mode, the receiving process suspends its execution and waits for the signal to be delivered and when the signal is received by the receiving process it will resume the execution.

Asynchronous Mode:

In this mode, when a signal deliveres to the process, the process will stop the current execution and a call back made to the response routine associated with that signal. once the process response routine terminated process will resume.

General-purpose signals:

When a Process receives a signal, Either of the three things can happen:

  1. Default: Process executes the default action of the signal.
  2. Customized: Process does its customized processing on receiving the signal by executing the signal handler routine.
  3. Ignore: Process ignore the signal.

There are three ways of generating signal in Linux:

  1. Raising a signal from OS to process (ctrl-c etc).
  2. Sending a signal one process to other process using kill().
  3. Sending a signal from process to itself using raise().

Changing Signal Disposition:

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

This routine can be used to set the disposition of a signal specified in the first argument to the handler passed as the second argument.

Changing signal disposition to define application handler:

1.The application should define a handler that will execute in response to a signal.

2. Invoke signal API to registered the above-defined function with any signal as needed.

#include<stdio.h>
#include<signal.h>
#include <unistd.h>
int f;
void sig_han(int signo)
{
        printf("signal handler:%d\n",signo);
        f = 1;
}
int main()
{
        signal(2,sig_han);
        while(1) {
                if(f == 1)
                        break;
                printf("test\n");
                sleep(1);
        }
        return 0;
}

Kill:

#include <signal.h>
int kill(pid_t pid, int sig);

Example of Kill() system call.

//kill_sender.c:

#include<stdio.h>
#include<signal.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
        int pid = atoi(argv[1]);
        kill(pid,2);
        return 0;
}
//Kill_recev.c

#include<stdio.h>
#include<signal.h>
#include <unistd.h>
int f;
void sig_han(int signo)
{
        printf("signal handler:%d\n",signo);
        f = 1;
}
int main()
{
        signal(2,sig_han);
        while(1) {
                if(f == 1)
                        break;
                printf("test\n");
                sleep(1);
        }
        return 0;
}
one terminal run kill_recev and other terminal run kill_sender
./kill_recev
./kill_sender 139    // 139 is pid of kill_recev
Output:
test
signal handler:2

raise:

#include <signal.h>
int raise(int sig);

Example of raise() system call.

#include<stdio.h>
#include<signal.h>
#include <unistd.h>
void sig_han(int signo)
{
        printf("signal handler:%d\n",signo);
}
int main()
{
        signal(2,sig_han);
        printf("test\n");
        raise(2);
        return 0;
}

Alaram:

SIGALRM is a signal which will be delivered to a process when the specific time expires. The application can register for the time expiration notification for the kernel using the alarm interface.

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

The alarm arranges for a SIGALRM signal to be delivered to the calling process in seconds.

#include<stdio.h>
#include<signal.h>
#include <unistd.h>
void sig_han(int signo)
{
        printf("signal handler:%d\n",signo);
}
int main()
{
        signal(SIGALRM,sig_han);
        alarm(2);
        while(1) {
                printf("test\n");
                sleep(1);
        }
        return 0;
}

Sigaction Interface:

#include <signal.h>
int sigaction(int signum, const struct sigaction *restrict act,
                     struct sigaction *restrict oldact);
sigaction is used to change the disposition of a signal.The sigaction structure is defined as something like:
struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

sa_handler:

Initialize sa_handler member in sigaction object with application defines signal handler address.

#include<stdio.h>
#include<signal.h>
#include<string.h>
#include <unistd.h>
int f;
void sig_han(int signo)
{
        printf("the sighandler :%d \n",signo);
        printf("the pid :%d\n",getpid());
        f = 1;
}
int main()
{
        struct sigaction act;
        memset(&act,0,sizeof(act));
        act.sa_handler = sig_han;
        sigaction(2,&act,NULL);
        while(1) {
                if(f == 1)
                        break;
                printf("test\n");
                sleep(1);
        }
        return 0;
}

sa_sigaction:

sa_flags:

When this flag is set then the signal disposition should be initialized to sa_sigaction member instead of sa_handler member.

1. SA_SIGINFO:

When this flag is use more information about the signal delivered to the signal handler in the form of siginfo_t. The siginfo_t data type is a structure with the following fields:

siginfo_t {
               int      si_signo;     /* Signal number */
               int      si_errno;     /* An errno value */
               int      si_code;      /* Signal code */
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID */
               uid_t    si_uid;       /* Real user ID of sending process */
               int      si_status;    /* Exit value or signal */
               clock_t  si_utime;     /* User time consumed */
               clock_t  si_stime;     /* System time consumed */
               union sigval si_value; /* Signal value */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
               void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
               int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }
#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<sys/types.h>
#include <unistd.h>
int f;
void sig_han(int signo,siginfo_t *obj,void *p)
{
        printf("the sighandler num :%d \n",obj->si_signo);
        printf("the sigcode :%d \n",obj->si_code);
        f=1;
}
int main()
{
        struct sigaction act;
        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_flags = SA_SIGINFO;
        sigaction(2,&act,NULL);
        while(1) {
                if(f == 1)
                        break;
                printf("test\n");
                sleep(1);
        }
        return 0;
}
2. SA_NODEFER:

When a process is in the execution of a signal handler and if the same signal delivered then the signal deferred or queue. The general purpose signal is delivered repeatedly to a process then only one instance of that signal cured in the pending signal queue for this behavior applications cannot respond to all the general-purpose signals.

Applications can alter this behavior in such a way that the pending signal queue can be disabled so that applications can respond to every signal with the same signal repeatedly. this behavior can be achieved by initializing sa_flags member in sigaction object with SA_NODEFER.

#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<unistd.h>
int f;
void sig_han(int signo,siginfo_t *obj,void *p)
{
        printf("the sighandler :%d \n",signo);
        sleep(5);
        f = 1;
        printf("exit sighandler\n");
}
int main()
{
        struct sigaction act;
        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_flags = SA_NODEFER;
        if(sigaction(2,&act,NULL) == -1)
                perror("sigaction");
        while(1) {
                if(f == 1)
                        break;
                printf("test\n");
                sleep(1);
        }
        return 0;
}
3. SA_RESTART:

When an application blocks a system call and if a signal delivers to the process, the process will respond executing by the signal handler but would result in an interrupted system call. The Interrupted system call will not restart by default. we need to restart them using application logic manually or automate system call restart by using sigaction interface.

Restart the system call automatically when interrupted set sa_flags member in sigaction object with SA_RESTART.

#include<stdio.h>
#include<signal.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
static int pipe_fd[2];
void sig_han(int signo,siginfo_t *obj,void *p)
{
        printf("the sighandler :%d \n",signo);
        write(pipe_fd[1],"welcome",8);
}
int main()
{
        int fd;
        char rbuf[8];
        struct sigaction act;
        if (pipe(pipe_fd) < 0) {
                perror("pipe");
        }
        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_flags = SA_RESTART;
        if(sigaction(2,&act,NULL) == -1)
                perror("sigaction");
        printf("test\n");
        memset(rbuf, 0, 8);
        read(pipe_fd[0],rbuf,sizeof(rbuf));
        printf("the read :%s %ld\n",rbuf, strlen(rbuf));
        return 0;
}
4. SA_ONSTACK:

By default, all signal handlers will use a process stack for allocating stack frames. If a process has registered process defined signal handler for segmentation fault and if segmentation fault occurs because of stack overflow then the register signal handler will never execute as it is using process stack segment.

If we want to execute the process defined signal handler for segmentation fault even if a stack overflow occurs then we should allocate an alternate stack for the signal handler.

#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
void sig_han(int signo,siginfo_t *obj,void *p)
{
        printf("the sighandler :%d \n",obj->si_signo);
        printf("the sighandler address :%p \n",obj->si_addr);
        exit(1);
}
void fun(int a)
{
        printf("the call value :%d\n",a);
        fun(a);
}
int main()
{

        struct sigaction act;
        stack_t  sigstack;
        sigstack.ss_sp = malloc(SIGSTKSZ);
        if (sigstack.ss_sp == NULL)
                perror("handler");
        sigstack.ss_size =SIGSTKSZ;
        sigstack.ss_flags = 0;
        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_flags = SA_ONSTACK | SA_SIGINFO;
        if(sigaltstack(&sigstack,NULL) == -1)
                perror("sigaltstack");
        if(sigaction(11,&act,NULL) == -1)
                perror("sigaction");

        printf("test\n");
        fun(100000);
        return 0;
}
sa_mask:

Blocking signals when a signal handler executes. It is possible to block the other signals when a signal handler executes with the help of a sa_mask member in struct sigaction.

#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<unistd.h>
int f;
void sig_han(int signo, siginfo_t *obj, void *p)
{
        printf("the sighandler :%d \n",signo);
        sleep(5);
        f=1;
        printf("the sighandler returned\n");
}
int main()
{
        struct sigaction act;
        sigset_t sigmask;
        int r;

        printf("pid : %u\n", getpid());
        r = sigemptyset(&sigmask);
        printf("%d\n",r);

        r = sigaddset(&sigmask,4);
        printf("%d\n",r);

        r = sigaddset(&sigmask,3);
        printf("%d\n",r);

        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_mask = sigmask;

        if(sigaction(SIGINT,&act,NULL) == -1)
                perror("sigaction");
        printf("test\n");
        for(;;) {
                if(f == 1)
                        break;
        }
        return 0;
}

Real-time signals:

The Linux kernel supports a range of 33 different real-time signals, numbered 32 to 64. The range of supported real-time signals defines by the macros SIGRTMIN and SIGRTMAX.

However, the Glibc POSIX threads implementation internally uses two (for NPTL) or three (for LinuxThreads) real-time signals (see pthreads(7)), and adjusts the value of SIGRTMIN suitably (to 34 or 35). The real-time signals using the notation SIGRTMIN+n, and include suitable (run-time) checks that SIGRTMIN+n does not exceed SIGRTMAX.

#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include <unistd.h>
void sig_han(int signo,siginfo_t *obj,void *p)
{
        printf("the sighandler num :%d \n",obj->si_signo);
        switch (obj->si_value.sival_int) {
                case 1:
                        printf("Wide ball\n");
                        break;
                case 2:
                        printf("No Ball, Free Hit\n");
                        break;
                case 3:
                        printf("Out\n");
                        break;
                default:
                        printf("No Signal\n");
                        break;
        }
}
int main()
{
        struct sigaction act;
        union sigval sig;
        int c;
        memset(&act,0,sizeof(act));
        act.sa_sigaction = sig_han;
        act.sa_flags = SA_SIGINFO;
        sigaction(SIGRTMIN,&act,NULL);
        printf("Enter the signal value\n");
        while(1) {
                scanf("%d", &sig.sival_int);
                sigqueue(getpid(),SIGRTMIN, sig);
                sleep(1);
        }
        return 0;
}

Share This: