Add Book to My BookshelfPurchase This Book Online

Chapter 10 - Signals

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Using Signals for Timeouts
One of the more common uses for signals is the implementation of timeouts. For example, suppose that a process wants to stop for a short period of time, and then continue. This might be necessary in a program that prints a large amount of output—if an error occurs, the error message should be printed and then the program should pause for a moment to give the user time to read the error message before it disappears from the screen.
To do this, we can use the alarm function:
    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
The alarm function tells the operating system to deliver a SIGALRM signal to the process after seconds seconds have elapsed. There is only one alarm clock for each process; if a second call to alarm is made before the first one has expired, the clock is reset to the second value of seconds. If seconds is 0, any previously made alarm request is cancelled. The alarm function returns the amount of time remaining in the alarm clock from the previous request. Using alarm, we can implement our pause-after-an-error-message function:
    #include <signal.h>
    #include <unistd.h>
    static void handler(int);
    void
    stop(int seconds)
    {
        signal(SIGALRM, handler);
        alarm(seconds);
        pause();
    }
    void
    handler(int sig)
    {
        return;
    }
By calling stop with the number of seconds we wish to pause, we can allow the user to read an error message. The function sets up a signal handler for SIGALRM, and then requests, using alarm, that the operating system send a SIGALRM after seconds seconds have elapsed. It then simply calls pause to suspend execution until the signal arrives. The signal handler doesn't actually have to do anything; it exists only so that we can get out of pause.
The stop function works, but is too primitive to be suitable for inclusion in a system programming library, for example. Some of the problems with this function include:
 The disposition of the SIGALRM signal is altered. If the programmer had already set up his own disposition for this signal, it is lost once he calls stop. A more polite function would save the old disposition of the signal (returned by the call to signal), and restore it when the function returns.
 If the caller has already scheduled an alarm with alarm, that alarm is erased by the call to alarm within stop. This can be corrected by saving the return value from alarm. If it is less than seconds, then we should wait only until the previously set alarm expires. If it is greater than seconds, then before returning we should reset the alarm to occur at its designated time.
 Finally, there is the problem of what happens when the alarm goes off and the signal handler is called before we call pause. If this happens, then stop will be aptly named; the program will stop “forever.”
Because these problems tend to make implementing stop more difficult, especially in a portable fashion, all versions of UNIX provide a library routine that handles them for you. This routine is called sleep:
    #include <unistd.h>
    unsigned int sleep(unsigned int seconds);
This function causes the program to suspend itself for seconds seconds, and then return. The number of unslept seconds is returned. This value may be non-zero if another signal arrives while the process is suspended (since pause returns after the receipt of any signal, not just SIGALRM), or if the calling program had another alarm scheduled to go off before the end of the requested sleep.
Timeouts are also useful for breaking out of operations that would otherwise block indefinitely. For example, consider the following code fragment:
    printf("Enter a string: ");
    fgets(buf, sizeof(buf), stdin);
If the user walks away from the terminal, the program using this code will sit there forever, waiting for him to come back. But let's suppose that the program can assume a reasonable default value for the string, and if the user doesn't enter one of his own, the program can use that default. Now all that's necessary is to give the user a chance to enter his string, and if he doesn't do so in a certain amount of time, just continue about our business using the default value. Example 10-4 shows a program that does just that.
Example 10-4:  timeout1
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int     flag = 0;
void    handler(int);
int
main(void)
{
    char buf[BUFSIZ];
    char *defstring = "hello";
    /*
     * Set up a timeout of 10 seconds.
     */
    signal(SIGALRM, handler);
    alarm(10);
    /*
     * Prompt for a string and remove the newline.
     */
    printf("Enter a string: ");
    fgets(buf, sizeof(buf), stdin);
    buf[strlen(buf)-1] = '\0';
    /*
     * Turn off the alarm, they typed something.
     */
    alarm(0);
    /*
     * If flag is 1, the alarm went off.  Assume default string.
     */
    if (flag == 1) {
        strcpy(buf, defstring);
        putchar('\n');
    }
   
    /*
     * Display the string we're using.
     */
    printf("Using string \"%s\"\n", buf);
    exit(0);
}
/*
* handler - catch alarm signal and set flag.
*/
void
handler(int sig)
{
    flag = 1;
}
    % timeout1
    Enter a string: howdy
    Using string "howdy"
    % timeout1
    Enter a string:
    Using string "hello"
This program uses alarm to set a ten-second timeout, and then prompts for the string. If the user enters a string, the read (fgets) returns, the alarm is turned off, the flag variable is still 0, and the program uses the string the user entered. However, if the user doesn't type anything, the alarm goes off, resulting in a call to handler, which sets flag to 1. The signal handler returns, the value of flag results in copying the default string value into buf, and the program continues.
Unfortunately, this program doesn't always work. If we try to use it on a system that offers automatic restarting of system calls, such as 4.2BSD or 4.3BSD, the read from the terminal will be restarted when handler returns, and we'll be right back where we started. Thus, for portability, we need some way to get out of the read even on systems that restart it after a signal arrives.
The setjmp and longjmp Functions
If C allowed us to goto a label in another function, we could solve this problem easily. Simply place a label after the call to fgets, and then instead of doing a return from handler, call goto with that label as an argument. Unfortunately, we can't do this.
UNIX does, however, provide two functions that do allow non-local branching:
    #include <setjmp.h>
    int setjmp(jmp_buf env);
    void longjmp(jmp_buf env, int val);
The setjmp function is called first, and saves the current program state in the variable env. When called directly, setjmp returns 0. In order to return to the point in the program at which we called setjmp, the longjmp function is used. The first argument, env, is the same one we passed to setjmp. The second argument, val, is a non-zero value that becomes the return value from setjmp. This second argument allows us to have more than one longjmp for a single setjmp.
Example 10-5 shows a re-implementation of our timeout program, this time using setjmp and longjmp.
Example 10-5:  timeout2
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
#include <stdio.h>
jmp_buf env;
void    handler(int);
int
main(void)
{
    char buf[BUFSIZ];
    char *defstring = "hello";
    /*
     * Set up signal handler.
     */
    signal(SIGALRM, handler);
    /*
     * If setjmp returns 0, we're going through the first time.
     * Otherwise, we're going through after a longjmp.
     */
    if (setjmp(env) == 0) {
        /*
         * Set an alarm for 10 seconds.
         */
        alarm(10);
        /*
         * Prompt for a string and strip the newline.
         */
        printf("Enter a string: ");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf)-1] = '\0';
        /*
         * Turn off the alarm; they typed something.
         */
        alarm(0);
    }
    else {
        strcpy(buf, defstring);
        putchar('\n');
    }
   
    /*
     * Display the string we're using.
     */
    printf("Using string \"%s\"\n", buf);
    exit(0);
}
/*
* handler - catch alarm signal and longjmp.
*/
void
handler(int sig)
{
    longjmp(env, 1);
}
    % timeout2
    Enter a string: howdy
    Using string "howdy"
    % timeout2
    Enter a string:
    Using string "hello"
The first time through the program, we call setjmp, which returns 0. This allows us to schedule our alarm and prompt for the string. If the user types something, we turn off the alarm and continue with the program. However, if the user doesn't type anything, we eventually receive a SIGALRM signal, and handler is called. In handler, we call longjmp with the val parameter equal to 1. This transfers control back to the if statement in main, and makes it appear to the program that setjmp has just returned 1. This causes us to take the else branch, and copy in the default string.
This version of timeout will work on any type of UNIX system, whether or not it restarts system calls. However, there is still another problem. If the program is used on a system that provides reliable signals, then recall that when handler is called, SIGALRM will be added to the process' signal mask. Since we don't actually return from handler, SIGALRM will still be blocked after the call to longjmp. This means that the process will no longer receive SIGALRM signals.
The 4.2BSD and 4.3BSD versions of setjmp and longjmp handle this case properly, by saving and restoring the signal mask. However, the SVR4 versions of these functions do not handle this case. One way to deal with it is to call sigrelse inside handler before doing the longjmp. Another way is to use the POSIX sigsetjmp and siglongjmp functions; these are described later in this chapter.
 NoteAlthough the timeout mechanism shown here is viable, the select and poll functions described in Chapter 6, Special File Operations, are more efficient and more flexible for this type of work.
Interval Timers
4.2BSD introduced a substantially more intricate version of timers and timeouts than those provided by alarm and sleep, called interval timers. These timers provide millisecond accuracy (subject to the resolution of the system's on-board clock). Interval timers have been carried forward into SVR4 as well. There are two basic functions for working with interval timers:
    #include <sys/time.h>
    int getitimer(int which, struct itimerval *value);
    int setitimer(int which, struct itimerval *value,
            struct itimerval *ovalue);
The getitimer function looks up the current settings for the interval timer identified by which, and returns them in the area pointed to by value. The setitimer function makes the settings for the interval timer identified by which equal to those in value; if ovalue is non-null, the previous settings are returned.
There are four interval timers, identified by which:
ITIMER_REAL
Decrements in real time (“clock on the wall” time). ASIGALRM signal is delivered to the process when this timer expires.
ITIMER_VIRTUAL
Decrements in process virtual time. This timer runs only when the process is executing. ASIGVTALRM signal is delivered to the process when this timer expires.
ITIMER_PROF
Decrements in both process virtual time and when the system is executing on behalf of the process. This timer is designed to be used by interpreters when statistically profiling the execution of interpreted programs. ASIGPROF signal is delivered to the process when this timer expires.
ITIMER_REALPROF
Decrements in real time. This timer, designed to be used for real-time profiling of multithreaded programs, is specific to Solaris 2.x.
A timer is described by a structure of type struct itimerval:
    struct itimerval {
        struct timeval    it_interval;
        struct timeval    it_value;
    };
The it_value element of the structure specifies, in seconds and microseconds, the amount of time remaining until the timer expires. The it_interval element specifies a value to be used in reloading it_value when the timer expires. Thus, interval timers run over and over again, sending a signal each time they expire. Setting it_value to 0 disables a timer, regardless of the value of it_interval. Setting it_interval to 0 disables a timer after its next expiration (assuming it_value is non-zero). Example 10-6 shows another implementation of our timeout program, using interval timers.
Example 10-6:  timeout3
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int     flag = 0;
void    handler(int);
int
main(void)
{
    char buf[BUFSIZ];
    struct itimerval itv;
    char *defstring = "hello";
    /*
     * Set up a timeout of 10 seconds.
     */
    signal(SIGALRM, handler);
    itv.it_interval.tv_usec = 0;
    itv.it_interval.tv_sec = 0;
    itv.it_value.tv_usec = 0;
    itv.it_value.tv_sec = 10;
    setitimer(ITIMER_REAL, &itv, (struct itimerval *) 0);
    /*
     * Prompt for a string and strip the newline.
     */
    printf("Enter a string: ");
    fgets(buf, sizeof(buf), stdin);
    buf[strlen(buf)-1] = '\0';
    /*
     * Turn off the alarm, they typed something.
     */
    itv.it_value.tv_usec = 0;
    itv.it_value.tv_sec = 0;
    setitimer(ITIMER_REAL, &itv, (struct itimerval *) 0);
    /*
     * If flag is 1, the alarm went off.  Assume default string.
     */
    if (flag == 1) {
        strcpy(buf, defstring);
        putchar('\n');
    }
   
    /*
     * Display the string we're using.
     */
    printf("Using string \"%s\"\n", buf);
    exit(0);
}
/*
* handler - catch alarm signal and set flag.
*/
void
handler(int sig)
{
    flag = 1;
}
    % timeout3
    Enter a string: howdy
    Using string "howdy"
    % timeout3
    Enter a string:
    Using string "hello"

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback