Signals

Introduction

  • A signal is a notification that is generated as a consequence of some event.
  • This notification can be process to process or even can be a system to process.
  • A signal is just like an interrupt, when it is generated by user level, a call is made to the kernel of the OS and it will act accordingly.
  • Few typical examples could be:
    • SIGSEGV : Illegal memory access(Terminating a process, no kill )
    • SIGINT : Terminal Interrupt(ctrl + c)
    • SIGSTP : Suspend/Stop the execution(ctrl + z)
    • SIGCONT : Continue executing, if stopped
    • SIGTERM : kill()

Need of signals:
Signals serve two important purposes:

  • Event Notification
  • To execute signal handlers.

Action taken receipt of signals:

There are three action which can be taken upon receipt of signals:

  • Explicitly ignore the signal.
  • Execute the default action depending upon type of signal.
  • Catch the signal and invoke appropriate signal handler.

Internal working of signals:

  • A signal is generated either by the kernel internally (for example, SIGSEGV when an invalid address is accessed or by a program using the kill system calls.
  • If it’s by one of the syscalls, then the kernel confirms the calling process has sufficient privileges to send the signal. If not, an error is returned (and the signal doesn’t happen).
  • Each signal is identified by a signal number(sig id) and is like an interrupt that changes the execution of code from user space to kernel space.
  • The kernel maintains a table called signal table where each entry has a mapping between signal number and its corresponding signal handler address. (Concept of Function pointer in C).
  • The current context of the process is saved onto the stack and is loaded with a new context of the signal handler.
  • The program counter(next instruction) is updated with the signal handler address.
  • Once the signal handler is done, the saved context is restored and process execution moves from kernel space to user space and continues with the next instruction.

Diag-1: Internal Working of signals

This image has an empty alt attribute; its file name is f5f202_6316f5f2e7364f6bb861ac8081241985~mv2.webp

Sending Signals(kill() API):

  • A process can send a signal to another process, including itself, by calling kill() API or kill command via terminal.
  • Kill Command is a wrapper around kill() API which sends a signal to a particular process or group of process, reference by their PID(PID) or process group ID’s(PGID’s)
  • To send a signal, the sending process must have permission to do so, that is both the process must have the same user ID.
  • Syntax: int kill( pid_t pid, int sig);
  • The first parameter is the process or process group ID and the second parameter is the signal id.
  • It returns -1 on failure and 0 on success.
  • Syntax for kill command: kill <sig_num> <PID>
  • By default the signal which is passed in kill is SIGTERM.
  • Example : (kill 1234) is equivalent to (kill TERM 1234), (kill -9 1234) is equivalent to kill (KILL 1234).

Signal system call API:

int sigaction (int sig, const struct sigaction *act, struct 
              sigaction *old);
int sigaddset (sigset_t *set, int sig); 
int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigdelset (sigset_t *set, int sig);
int sigismember (sigset *set, int sig);
int sigprocmask (int how, const sigset_t *set, sigset_t *old);
int sigpending (sigset_t *set);
int sigsuspend (sigset_t *set);

Set the action to be associated with a signal:

  • The action to be associated with a particular signal is set by sigaction() API.
  • It defines the action to be taken on receipt of the signal specified by sig.
  • Syntax: int sigaction(int sig, const struct sigaction *act, struct sigaction *old_backup);
  • The first parameter is the signal with which the action to be associated with.
  • The signal can be any valid signal except SIGKILL and SIGSTOP.
  • The second parameter is a pointer to sigaction structure, which has a pointer to signal handler function, mask(signal to block while executing signal handler), and flags.
   struct sigaction
   {
     void (*sa_handler)(int);
     sigset_t sa_mask;
     int sa_flags;    
   };

/* This is the function called on receipt of signal(ctrl + c) */
   void sig_handler(int sig)
   {
     printf("\n This is signal handler for signal %d\n", sig);
   }

   struct sigaction act;
   act.sa_handler = sig_handler;
   sigemptyset (&act.sa_mask);
   act.sa_flags =0;
           
   sigaction (SIGINT, &act, 0);

Note:

  • For signal SIGKILL and SIGSTOP there cannot be any action associated. They will always execute their default behavior.
  • These signals can never be caught, blocked, or ignored.
  • This is useful in cases where the application goes unresponsive and the only option left is to kill it.
  • Most of the time there don’t handle memory cleanup and other housekeeping stuffs well.
  • Hence this should be the last option to kill or stop the process.
  • SIGTERM is best to use for gracefully terminating a process, using the kill command.

Adding a signal to a signal set:

  • The sigaddset() API is used for adding a signal specified by sig to a signal set pointed by set.
  • Syntax: int ret = sigaddset (sigset_t *set, int sig);
  • The parameters are the set where the signal is to added.
  • It returns -1 on failure and 0 on success.
sigset_t set;
struct sigaction act;
act.sa_handler = sig_handler;
/* Adding SIGINT to signal set */
sigaddset (&set, SIGINT);
act.sa_mask =set;
act.sa_flags = 0;
sigaction (SIGINT, &act, 0);

Deleting a signal from signal set:

  • The sigdelset() API is used for deleting a signal specified by sig from a signal set pointed by set.
  • Syntax: int ret = sigdelset (sigset_t *set, int sig);
  • The parameters are the set from where the signal pointed by sig is to be deleted.
  • It returns -1 on failure and 0 on success.
          sigset_t set;
          sigaddset (&set, SIGINT);
          sigaddset (&set, SIGTERM);
          sigdelset (&set, SIGINT);

Emptying the set:

  • The sigemptyset() API is used for initializing the signal set pointed by set to be empty.
  • Syntax: int ret = sigemptyset (sigset_t *set);
  • The parameters are set to be initialized.
  • It returns -1 on failure and 0 on success.
           sigset_t set;
           sigaddset (&set, SIGINT);
           sigaddset (&set, SIGTERM);
           sigemptyset (&set);

Filling the signal set:

  • The sigfillset() API is used for initializing the signal set to contain all the defined signals
  • Syntax: int ret = sigfillset (sigset_t *set);
  • The parameters are the set to be initialized to contain all signals
  • It returns -1 on failure and 0 on success.
           sigset_t set;
           sigfillset (&set);

Checking the existence of signal in a set:

  • The sigismember() API is used for checking the existence of signal specified by sig in a set pointed by set.
  • Syntax: int sigismember(sigset_t *set, int sig);
  • The parameters are the set where the signal is to be checked against and the signal to be checked.
  • It returns 1 if a signal is a member of the set and returns 0 if not found.
        sigset_t set;
        sigemptyset (&set);
        sigaddset (&set, SIGTERM);
        sigaddset (&set, SIGINT)
        /* Should return 1*/
        printf("\n Return is %d\n", sigismember (&set, SIGINT));
        /* Should return 0 */
        printf("\n Return is %d\n", sigismember (&set, SIGPIPE)); 

Setting the signal mask:

  • The process signal mask is set or examined by calling the sigprocmask() API.
  • The signal mask is the set of signals that are currently blocked and will therefore not be received by the current process.
  • Syntax: int sigprocmask ( int how, const sigset_t *set, sigset_t *oldset);
    • The first argument determines the signal mask, its value can be:
    • SIG_BLOCK : The signal in the set are added to the signal mask
    • SIG_SETMASK : Update the signal mask with the new set and copy the old set to oldset(if not NULL).
    • SIG_UNBLOCK : The signal in the set are removed from the signal mask.
  • New values of the signal mask are passed in the argument set if isn’t null, and previous signal mask is written to the signal set oldset.
  • If the set argument is a null pointer, the value of how is not used ,and the only purpose of the call is to fetch the value of the current signal mask to oldset.
  • It returns 0 on success and -1 on failure.

Get the set of pending signal:

  • If a signal is blocked by a process and if it happens to receive those signals, it won’t be delivered but will put into a set of pending signals by using sigpending() API.
  • Syntax: int sigpending ( sigset *set).
  • Later this can be verified by using sigismember() API.
  • It returns 0 on success and -1 on failure.
sigset_t set;
sigset_t pending_set;
sigaddset (&set, SIGINT);
sigaddset (&set, SIGPIPE)
sigprocmask ( SIG_BLOCK, &set, NULL); 
while(1)
{
  sigpending (&pending_set);
  /* Until the SIGINT(ctrl + c) is delivered, sigismember() should 
    return 0, once signal is generated it should return 1 /*
  printf("\n %d\n",sigismember(&pending_set, SIGINT));
}

Suspending the execution:

  • By using sigsuspend() API, a process can suspend its execution until any one of the signals is delivered from a set of signals.
  • The clean and reliable way to wait for a signal is to block it and then use sigsuspend.
  • By using sigsuspend in a loop, one can wait for certain kinds of signals while allowing other kinds of signals to be handled by their handler.
  • It allows processes to suspend its execution until the receipt of one signal which is not present in the set pointed by the mask.
  • Syntax: int sigsuspend(sigset_t *mask);
  • It temporarily replaces the process current mask with a new mask and is restored once the handler for the signal returns.
  • Since sigsuspend() suspends threads execution indefinitely, there is no return value for successful completion and return -1 on failure.

Use Case:

One of the best use cases of sigsuspend() API is during the critical Section, which should be atomic that is a critical region of code should not be interrupted by a signal.

The below program makes sure that SIGKILL or SIGTERM signal is blocked when the program executes in the critical section and resumes execution once SIGINT is signaled and its corresponding signal handler is executed. This also shows usage of other signals API.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

int cs = 0;
int value = 5;

/* This is the function called on receipt of signal(ctrl + c) */
void sig_handler (int sig)
{
    printf("\n This is signal handler for signal %d\n", sig);
    cs = 1;
}

int main()
{
    struct sigaction act;
    act.sa_handler = sig_handler;
    sigemptyset (&act.sa_mask);
    sigaddset (&act.sa_mask, SIGPIPE);
    act.sa_flags = 0;

    sigset_t sig_term_kill_mask;
    sigaddset (&sig_term_kill_mask, SIGTERM);
    sigaddset (&sig_term_kill_mask, SIGKILL);

    sigaction (SIGINT, &act, NULL);

    /* Critical section */
    value++;
    printf ("\n Value is %d\n", value);

    while (cs == 0)
            sigsuspend (&sig_term_kill_mask);

    printf("\n Return from suspended successful\n");
    int ret = sigismember (&act.sa_mask, SIGPIPE);
    if (ret)
        printf("\n Old Mask that is SIGPIPE is restored after return from sigsuspend\n");
    else
        printf("\n Old Mask that is SIGPIPE is fails to restored after return from sigsuspend\n");

    return 0;
    }

Output:

[aprakash@wtl-lview-6 signal]$ ./a.out 

 Value is 6
^C
 This is signal handler for signal 2
 Return from suspended successful
 Old Mask that is SIGPIPE is restored after return from      sigsuspend


Categories: Operating system (OS)

1 reply

Trackbacks

  1. Broken pipe error - Tech Access

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: