JMU
Signals
An Introduction


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
Overview
Overview (cont.)
Signals from the Kernel
Terminology
Delivery
Things You Might Not Expect
Default Effect on the Process
Standard Signals
images/unix_signals.gif
Changing the Disposition
More Terminology
Changing the Disposition: signal() signal
void ( *signal(int num, void (*handler)(int)) ) (int);
Purpose:
Set the disposition of a signal
Details:
num The signal number
handler The signal handler
Return The previous disposition on success; SIG_ERR on error
#include <signal.h> signal.h

Understanding the Signature:

handler is a pointer to a function that returns nothing and is passed an int.

signal() itself returns a pointer to a function that returns nothing and is passed an int.

Improving the Readability of signal()
"Special" Signal Handlers
Going Further: Portable Signal Handling
Going Further: Portability: sigaction() sigaction
int sigaction(int num, const struct sigaction *act, struct sigaction *oact)
Purpose:
Examine and change a signal action
Details:
num The signal number
act The action to be assoicated with the signal (or NULL)
oact The previous action assoicated with the signal (or NULL)
Return 0 on success; -1 on error
#include <signal.h> signal.h

Note: struct sigaction contains a pointer to the signal handler, the additional signals to be blocked, flags, etc...

Sending Signals from the Shell: kill kill
Sending Signals from the Shell (cont.)
Sending Signals: kill() kill
int kill(pid_t pid, int num)
Purpose:
Send a signal to a process of process group
Details:
pid The destination of the signal, specifically the process ID (if > 0), all processes in the group of the sender (if 0), all processes (if -1), all processes in the absolute value of the given group ID (if < -1)
num Is the signal number
Return 0 on success; -1 on error
#define _POSIX_SOURCE
#include <sys/types.h> sys/types.h
#include <signal.h> signal.h

Note: A signal will only be sent to processes for which the calling process has the appropriate permission

Good Uses of Signals
Reasonable Uses of Signals
An Example of "Alerting"
An Example of "Alerting" - A Conventional System
unixexamples/signals/tax0.c
        #include <stdio.h>
#include "tax_lib.h"

int
main(void)
{
  float rate, sales, tax;
  int   should;

  setup();
  rate = 0.05;

  should = should_tax();
  sales = total_sales();
  if (should) tax = sales * rate;
  else        tax = 0.0;

  printf("Tax: %5.2f\n", tax);
}
        
An Example of "Alerting" - A Concurrent System
An Example of "Alerting" - A Defective Concurrent System
unixexamples/signals/tax1.c
        #include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include "tax_lib.h"

int
main(void)
{
  float rate, sales, tax;
  int   should, status;
  pid_t pid;

  should = 0;
  rate   = 0.05;
  pid    = fork();
  switch(pid)
    {
    case -1:
      exit(1);

    case 0:
      should = should_tax();
      break;

    default:
      sales = total_sales();
      wait(&status); // Note: There is only one child
      if (should) tax = sales * rate;
      else        tax = 0.0;
      printf("Tax: %5.2f\n", tax);
      break;
    }
}
        
An Example of "Alerting" - Using a Signal
An Example of "Alerting" - Using a Signal (cont.)
unixexamples/signals/tax2.c
        #define _POSIX_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include "tax_lib.h"

volatile int should = 0;

static void
sighandler(int sig)
{
  should = 1;
}

int
main(void)
{
  float rate, sales, tax;
  int   status;
  pid_t pid;

  rate   = 0.05;
  pid    = fork();

  signal(SIGUSR1, sighandler);

  switch(pid)
    {
    case -1:
      exit(1);

    case 0:
      if (should_tax()) kill(getppid(), SIGUSR1);
      break;

    default:
      sales = total_sales();
      wait(&status); // Note: There is only one child
      if (should) tax = sales * rate;
      else        tax = 0.0;
      printf("Tax: %5.2f\n", tax);
      break;
    }
}
        
Signals and Coordination
A "Real World" Example
A "Real World" Example (cont.)
Signals and Coordination: pause() pause
int pause(void)
Purpose:
Put a calling process in the stopped state until any signal is received
Details:
Return -1
#include <unistd.h> unistd.h

Note: The sleep() sleep function puts the calling process in the stopped state until a given amount of time passes or it is interrupted by a signal.

Signals and Coordination Revisited

The Goal: Sequence Task 1 Before Task 2

unixexamples/signals/sequence.c
        #define _POSIX_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

static void
handle_continue(int sig)
{
  // Nothing to do but interrupt the call to pause()
}

/*
 * The Goal:           Sequence Task 1 before Task 2
 * The Race Condition: kill() in child may be called before pause() in parent
 */
int
main(void)
{
  // Setup the signal handler
  signal(SIGCONT, handle_continue);

  pid_t  pid = fork();
  switch(pid)
    {
    case -1:
      exit(1);

    case 0:  // Child
      printf("Task 1\n");
      kill(getppid(), SIGCONT);
      exit(0);

    default: // Parent
      pause();
      printf("Task 2\n");
      exit(0);
    }
}
        

The Race Condition: The child process might call kill() before the parent process calls pause().

A "Real World" Example Revisited
A "Real World" Example - An Incorrect Coordination Protocol
unixexamples/signals/tax3.c
        #define _POSIX_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "tax_lib.h"

// An example with a fairly obvious race condition

volatile int should = 0;

static void
sighandler(int sig)
{
  should = 1;
}

int
main(void)
{
  float rate, sales, tax;
  pid_t pid;

  rate   = 0.05;
  pid    = fork();

  signal(SIGUSR1, sighandler);

  switch(pid)
    {
    case -1:
      exit(1);

    case 0:
      if (should_tax()) kill(getppid(), SIGUSR1);
      record_status(); // This takes a long time
      break;

    default:
      sales = total_sales();
      pause();
      if (should) tax = sales * rate;
      else        tax = 0.0;
      printf("Tax: %5.2f\n", tax);
      break;
    }
}
        
Another Important Situation
Explicit Blocking: sigprocmask() sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
Purpose:
Change the set of currently blocked signals
Details:
how The change to perform
set The set of signals
oldset Will hold the old set of signals (if non-NULL)
Return 0 on success; -1 on error
#include <signal.h> signal.h

how can be SIG_BLOCK to make blocked = blocked | set, SIG_UNBLOCK to make blocked = blocked & ~set, or SIG_SETMASK to make blocked = set.

Explicit Blocking (cont.)
A "Real World" Example - Another Incorrect Coordination Protocol
unixexamples/signals/tax4.c
        #define _POSIX_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "tax_lib.h"

// An example with a more subtle race condition

volatile int should = 0;


static void
sighandler(int sig)
{
  should = 1;
}

int
main(void)
{
  float    rate, sales, tax;
  pid_t    pid;
  sigset_t set, oldset;
  
  rate   = 0.05;
  pid    = fork();

  signal(SIGUSR1, sighandler);

  sigemptyset(&set);
  sigaddset(&set, SIGUSR1);
  sigprocmask(SIG_BLOCK, &set, &oldset);

  switch(pid)
    {
    case -1:
      exit(1);

    case 0:
      if (should_tax()) kill(getppid(), SIGUSR1);
      record_status(); // This takes a long time
      break;

    default:
      sales = total_sales();
      sigprocmask(SIG_SETMASK, &oldset, NULL);
      pause();
      if (should) tax = sales * rate;
      else        tax = 0.0;
      printf("Tax: %5.2f\n", tax);
      break;
    }
  return 0;
}
        
Block-Suspend-Reset
Block-Suspend-Reset: sigsuspend() sigsuspend
int sigsuspend(const sigset_t *set)
Purpose:
Temporarily replace the current blocked set and suspend the process
Details:
set The signal set to block
Return -1
#include <signal.h> signal.h

sigsuspend() is essentially the same as the three calls above, but the first two calls are atomic.

Signal Handling Patterns
Signal Handling Patterns (cont.)
What Makes a Function "Signal Safe"?