Unix Signals

Technical Overview

Suppose you want to write some kind of game program, with input from the keyboard at unpredictable times. For example, hitting a certain key may make a Pacman figure change directions. Clearly we cannot use scanf for this purpose, because the program would just hang there, with the screen frozen, waiting for us to strike a key. This is not what we want; we want the animation on the screen to continue.

This kind of situation can be handled via the concept of signals.

footnote: This is similar to the concept of interrupts studied in ECS 50 and EEC 70.
Actually, a signal-handler function, which we will see an example of here, is a high-level version of an interrupt-handler; the latter still exists, but is hidden from the ordinary programmer, who works only through the former. This is good both for convenience, and because on timesharing systems, ordinary users are not allowed to access the portions of memory in which interrupt vectors are placed. For example, you can arrange things so that if the user hits ctrl-c, the program calls a programmer-specified function instead of just being killed.

The main library functions needed are signal, which specifies what function should be called when a ctrl-c or other signal is received by the program, and the pair of functions setjmp and longjmp, which are used to specify where the function is to resume execution if not at the point it was when the signal was received. In order to use these functions, you must have #include lines for <signal.h> and <setjmp.h>.

Example Program

The program below is described in the comments in Lines 3-15. It acts like the Unix grep command (which finds all lines in a file which contain the user-specified string), except that it is more interactive: The user specifies the files one at a time, and (here is why the signals are needed) he/she can cancel a file search in progress without canceling the grep command as a whole.

footnote: You should make sure that you understand why an ordinary scanf call will not work here.

For example, when I ran the program (Line 2), I first asked it to search for the string `type' (Line 4). It asked me what file to search in, and I gave it a file name (Line 6); the program then listed for me (Lines 7-20) all the lines from the file HowToUseEMail.tex which contain the string `type'. Then the program asked me if I wanted to search for that (same) string in another file (Line 21), so I asked it to look in the system dictionary (Line 22). The program had already printed out the first few response lines (Lines 23-25) when I changed my mind and decided that I didn't want to check that file after all; so, I typed control-c (Line 26), and program responded by confirming that I had abandoned its search in that file (Line 26). I then gave it another file to search (Line 28).

  1  Script started on Wed May 27 16:20:18 1992
  2  heather% a.out
  3  string?
  4  type
  5  file name?
  6  HowToUseEMail.tex
  7  Simply type {\bf mail \it address}, where {\it address} is the
  8  After you type this command line, you will be prompted to give
  9  a subject title for your message, and then you type your message
 10  typed so far.  You can then edit the text as usual.  When you are done, 
 11  type ZZ as usual to leave {\bf vi}, putting you back in send-mail mode, 
 12  and type ctrl-d to terminate and dispatch your message as explained 
 13  {\bf mail}, type tilde-v to get into {\bf vi} as above, and then use 
 14  Then simply type
 15  Z.  I would type the following:
 16  ``begin'' line.  Then she would type:
 17  Simply type {\bf mail}.  
 18  When you are finished reading your mail, type either {\bf x}
 19  truly discarding all of the messages that you had typed {\bf d} for,
 20  and also discarding any that you typed {\bf s} for.  By contrast,
 21  file name?
 22  /usr/dict/words
 23  archetype
 24  genotype
 25  Linotype
 26  ^COK, forget that file
 27  file name?
 28  Hwk4.tex
 29  typedef struct PathNode *PNPtrType;
 30  pointer type.  The return value is a pointer to a list of PathNode
 31  file name?
 32  q
 33  heather% cat $a40progs/Grep.c
 34  
 35  
 36  /* program to illustrate use of signals 
 37  
 38     this is like the Unix `grep' command; it will report all lines
 39     in a file that contain the given string; however, this one is
 40     different:  the user specifies the string first, and then the
 41     program prompts the user for the names of the files to check
 42     the string for, one file at a time
 43  
 44     reading a large file will take some time, and while waiting
 45     the user may change his/her mind, and withdraw the command
 46     to check this particular file (the given string is still
 47     valid, though, and will be used on whatever further files
 48     the user specifies); this is where the signals are used */
 49  
 50  
 51  
 52  #define MaxLineLength 80
 53  
 54  
 55  #include <stdio.h>
 56  #include <signal.h>
 57  #include <setjmp.h>
 58  
 59  
 60  jmp_buf(GetFileName);
 61  
 62  
 63  char Line[MaxLineLength],  /* one line from an input file */
 64       String[MaxLineLength],  /* current partial-word */
 65       FileName[50];  /* input file name */
 66  
 67  
 68  int StringLength;  /* current length of the partial-word */
 69  
 70  
 71  FILE *FPtr;
 72  
 73  
 74  /* ReadLine() will, as its name implies, read one line of the file; it
 75     will return a value which will be the line length if it successfully
 76     reads a line, or -1 if the end of the file was encountered */
 77  
 78  int ReadLine()
 79  
 80  {  char C;  int LineLength;
 81  
 82     LineLength = 0;
 83     while (1)  {
 84        if (fscanf(FPtr,"%c",&C) == -1) return -1;     
 85        if (C != '\n') Line[LineLength++] = C;
 86        else  {
 87           Line[LineLength] = 0;
 88           break;
 89        }
 90     }  
 91     return LineLength;
 92  }
 93  
 94  
 95  FindAllMatches()
 96  
 97  {  int LL,J,MaxJ;  
 98  
 99     FPtr = fopen(FileName,"r");
100     while (1)  {
101        LL = ReadLine();
102        if (LL == -1) break;
103        MaxJ = LL - StringLength + 1;
104        for (J = 0; J < MaxJ; J++)  
105           if (!strncmp(String,Line+J,StringLength))  {
106              printf("%s\n",Line);
107              break;
108           }
109     }
110  }
111  
112  
113  CtrlC()
114  
115  {  printf("OK, forget that file\n");
116     longjmp(GetFileName);
117  }
118  
119  
120  main()
121  
122  {  char NewLineChar;
123  
124     signal(SIGINT,CtrlC);
125     printf("string?\n");  scanf("%s",String);  scanf("%c",&NewLineChar);
126     StringLength = strlen(String);
127     while (1)  {
128        setjmp(GetFileName);
129        printf("file name?\n");  scanf("%s",FileName);
130        if (!strcmp(FileName,"q")) exit();
131        FindAllMatches();
132     }
133  }
134  
138  heather% e
139  heather% 
140  script done on Wed May 27 16:22:48 1992

Analysis:

The non-signal part of the program is straightforward. The function main() has a while loop to go through each file (Lines 127-132), and for each file, there is a while loop to read in each line from the file and check for the given string (Lines 100-109).

footnote: Note the expression Line+J in Line 105. Recall that an array name without a subscript, in this case `Line', is taken to be a pointer to the beginning of that array. Thus Line+J is taken to be pointer arithmetic, with the result being a pointer to Line[J]; the string comparison of strncmp will begin there.

On Line 124 we have the call to signal(). SIGINT is the signal number for control-c signals; it is defined in the #include file mentioned above.

footnote: Or type man signal. There are lots of other signal types, e.g. SIGHUP, which is generated if the user has a phone-in connection to the machine and suddenly hangs up the phone while the program is running.
In this call to signal() we are saying that whenever the user types control-c, we want the program to call the function CtrlC() (Lines 113-117).
footnote: Such a function is called a signal-handler. We say, for example, that on Line 124 we are telling the system that we want our function CtrlC() to be the signal-handler for SIGINT-type signals.

When the user types control-c, we want the program to abandon the search in the present file, and start on the next file. In other words, when we finish executing CtrlC(), we do not want execution to resume at the line in the program where was at the instant the user typed control-c (typically somewhere in the range of Lines 100-109)--instead, what we want is for the program to jump to Line 129. This is accomplished by the longjmp() function, which in our case (Line 128) says to jump to the line named `GetFileName'. How do we assign a name to a line? Well, this is accomplished by the setjmp() function, which in our case (Line 128) says to name the next line (Line 129) GetFileName.

footnote: Again, the concepts in ECS 50 and EEC 70 provide more detail here. What is actually happening is that GetFileName will contain the memory address of the first machine instruction in the compiled form of Line 129. How can the function setjmp() ``know'' this address? The answer is that this address will be the return address on that stack at that time. By the way, a goto statement won't work here, because one can't jump to a goto which is in a different function.

(This ``name'' will actually be an integer array which records the memory address of the first machine instruction the compiler generates from Line 129. One needs a declaration for this array, using the macro jmp_buf (Line 60), which again is defined in one of the #include files.)



Norm Matloff
Wed Nov 8 17:36:58 PST 1995