Menu

Class 14: Signals


Reading
10.1, 10.2 (you don't need to read about every signal ... just browse), and 10.3.

Homework
None.

A silly program I'd like to write
One of the first programs I wrote was one that printed my name on the screen ... infinitely. Hopefully I'm a bit less egotistical now. Here's the same concept, except that it just prints one character ... forever.
#include <stdio.h>

int main()
{
  char c = 'X';
  setbuf(stdout,NULL);
  while(1)
  {
    printf("     %c",c);
  }

  return 0;
}
Anyway, suppose we wanted to write a program to print a character on the screen forever, with the catch that I'd like to be able to change the character while the program executes. A first attempt might be something like this:
#include <stdio.h>

int main()
{
  char c = 'X';
  setbuf(stdout,NULL);
  while(1)
  {
    scanf(" %c",&c);
    printf("     %c",c);
  }

  return 0;
}
However, this doesn't work at all. The problem is that the call scanf(" %c",&c) is blocking, meaning that your process blocks (i.e. does not get time on the CPU) until there's a character available for you to read. The kernel knows when a character's available, and only then does it wake the process back up. So you can either printf over and over, or you can do a scanf and wait around for the user to enter a character, but you simply can't do both at once. [NOTE: There are non-blocking input/output routines, which give us one solution to this. However, we're taking another route. In no small measure because analogous problems crop up in many places where non-blocking I/O doesn't help.]

What we need is to make our application consist or two processes, one that prints character c infinitely, and one that waits for the user to to enter a new character and reads it. The key issue is that the second process will need to somehow signal the first that a new character has been entered. The mechanism by which we can do this is, appropriatley enough, called a signal.

Signals very briefly
A process can send a signal with the kill system call. (The kill utility lets users send signals from the command line.)
#include <sys/types.h>
#include <signal.h>

int kill(pidt pid, int sig);
The first argument is the process ID of the intended recipient of the signal. For the second, you usually use a predfined constant like SIGKILL (see signal.h(3HEAD). Recieving a signal is a bit tricky. You see, the recipient process has no idea when the signal will come in. It'll be minding its own business, computing whatever it computes, when it is suddenly interrupted by the arrival of this signal. This kind of communication is called asynchronos, meaning that there's no logic to when the input comes. All the other input we've dealt with is in direct response to a program's request for info: scanf, or getenv, or querying argv. Signals aren't like that. So we react to signals by providing a chunk of code, called a signal handler, and telling the kernel to interrupt the program when a signal comes, stop what it was doing, and execute that chunk of code. The function signal is a C standard library function that takes a signal (i.e. one of the predefined constants like SIGINT) and a function to act as a signal handler, and registers the function with the kernel as the handler for that signal. As with any function that takes a function as an argument, the prototype of signal is a bit cryptic.
#include <signal.h>

void (*signal(int sig, void (*disp)(int)))(int);

Writing the silly program
Warning: What you're about to see is not the "right" way to do inter-process communication. But it is a great way to illustrate the use of signals using tools we already know. We're now in a position to write my silly program ... better described somewhat pompously as an application since, in fact, it will consist of two programs. Program p1 will run in one terminal, printing X's endlessly. In another terminal, we'll launch program p2 with p1's PID as a command-line argument. Whenever we enter a new symbol as input to p2, it will write that symbol to a file foo and signal p1 with signal SIGUSR1. In response to receiving SIGUSR1, p1's signal handler will read a character from file foo and make that character the one that it prints. We also need to signal p1 when file foo is ready to be opened, since it can't successfully open it until p2 creates it. We use signal SIGUSR2 to indicate that foo is ready for p1 to open.

p1.cp2.c
/* This is program p1. */
#include <stdio.h>
#include <signal.h>
#include <time.h>

/* Ssshhh! Global variables!  We need
   these so that main() and sh1 & sh2
   can communicate. */
char sym = 'X';
FILE *fp = NULL;

void sh1(int st)
{
  signal(SIGUSR1,sh1);
  fscanf(fp,"%c",&sym);
}

void sh2(int st)
{
  fp = fopen("foo","r");
  setbuf(fp,NULL);
}

int main()
{
  /* Register signal handlers. */
  signal(SIGUSR2,sh2);
  signal(SIGUSR1,sh1);

  /* Print sym forever: Uncomment the stuff below if you
                        are running this remotely! */
  setbuf(stdout,NULL);
  /*struct timespec T, D; T.tv_sec = 0; T.tv_nsec = 5000000;*/
  while(1)
    printf("     %c",sym)/*,nanosleep(&T,&D)*/;
  return 0;
}
/* This is program p2. */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  /* Get p1's process ID. */
  pid_t pid = atoi(argv[1]);

  /* Open foo for unbuffered writing. */
  FILE *fp = fopen("foo","w");
  setbuf(fp,NULL);

  /* Tell p1 to open foo. */
  kill(pid,SIGUSR2);

  /* Read char, write to foo, notify p1. */
  char c;
  while(scanf("%c",&c) == 1)
  {
    if (c == '\n') continue;
    fprintf(fp,"%c",c);
    kill(pid,SIGUSR1);
  }

  /* Delete file foo & kill p1. */
  unlink("foo");
  kill(pid,SIGTERM);

  return 0;
}

Notice the call to "signal" within the handler for SIGUSR1. Why is it there? Why isn't it in the handler for SIGUSR2? Well, when the signal handler is called, it is automatically "deregistered" as the handler for that signal, and we must automatically reregister it. We're only expecting one SIGUSR2 signal, so there's no point.

This program has some flaws, which point out flaws in the "unreliable" signal handling mechanism embodied in the "signal" function. For example: If you type "ABC" and press enter in the p2 terminal, it may terminate p1 with the " User signal 1" message. What happened? Well, the p1 handler for SIGUSR1 was called because the A character was written by p2, but before the handler could do anything, p2 sent another signal for the B it wrote to the pipe. Since as soon as the signal handler is called, the action for that signal returns to its default behavior, it's possible for our second signal to sneak in before we reset sh1 as the signal handler for SIGUSR1 and, instead, we get the default behavior, which is to terminate.

Another intersting flaw is that typing ABC may result in a single signal being sent instead of 3. This is because with unreliable signals we are not guaranteed that signals are "queued". When signals occur in quick succession, one or more may be lost. So you type "ABC" and press enter, and p1 starts printing As. It didn't get the other two signals, so it didn't do the other two reads.

One way to fix this would be to have p2 call "pause" to wait until p1 signals it that it has actually read the value p2 just wrote. However, doing this would require p1 to have p2's process ID. Where's it going to get that info? It'd be nice if it could somehow find out the ID of the process sending it the SIGUSR1 and SIGUSR2 signals.

"Reliable" signal handling, accessible with the "sigaction" system call solves all these problems!

You can only send signals to processes you own (unless you're the superuser). It'd be a bit of a disaster otherwise, don't you think?

Signal details
For each signal, the kernel can be instructed to do one of three things for a process: ignore the signal, apply the default action, or call a signal handler. We've already used signal to specify a handler, in place of the handler function, you can pass signal the constant SIG_IGN to ignore the signal, or SIG_DFL to get the default action.

You can check out the list of signals along with default actions with man -s3HEAD signal.h. You can't catch SIGKILL or SIGSTOP.

Terminology