Topic : Unix Signals Programming
Author : LUPG
Page : << Previous 3  
Go to page :


operating system gives us a simple way of setting up timers that don't require too much hassles, by using special alarm signals. They are generally limited to one timer active at a time, but that will suffice in simple cases.




The alarm() System Call
The alarm() system call is used to ask the system to send our process a special signal, named ALRM, after a given number of seconds. Since Unix-like systems don't operate as real-time systems, your process might receive this signal after a longer time then requested. Combining this system call with a proper signal handler enables us to do some simple tricks. Lets see an example of a program that waits for user input, but exits if none was given after a certain timeout.





#include <unistd.h>    /* standard unix functions, like alarm()          */
#include <signal.h>    /* signal name macros, and the signal() prototype */

char user[40];  /* buffer to read user name from the user */

/* define an alarm signal handler. */
void catch_alarm(int sig_num)
{
    printf("Operation timed out. Exiting...\n\n");
    exit(0);
}

.
.
/* and inside the main program... */
.
.

/* set a signal handler for ALRM signals */
signal(SIGALRM, catch_alarm);

/* prompt the user for input */
printf("Username: ");
fflush(stdout);
/* start a 30 seconds alarm */
alarm(30);
/* wait for user input */
gets(user);
/* remove the timer, now that we've got the user's input */
alarm(0);

.
.
/* do something with the received user name */
.
.





The complete source code for this program is found in the use-alarms.c file.

As you can see, we start the timer right before waiting for user input. If we started it earlier, we'll be giving the user less time then promised to perform the operation. We also stop the timer right after getting the user's input, before doing any tests or processing of the input, to avoid a random timer from setting off due to slow processing of the input. Many bugs that occur using the alarm() system call occur due to forgetting to set off the timer at various places when the code gets complicated. If you need to make some tests during the input phase, put the whole piece of code in a function, so it'll be easier to make sure that the timer is always set off after calling the function. Here is an example of how NOT to do this:




int read_user_name(char user[])
{
    int ok_len;
    int result = 0; /* assume failure */

    /* set an alarm to timeout the input operation */
    alarm(3); /* Mistake 1 */
    printf("Enter user name: "); /* Mistake 2 */
    fflush(stdout);
    if (gets(user) == NULL) {
 printf("End of input received.\n");
 return 0;  /* Mistake 3 */
    }
    /* count the size of prefix of 'user' made only of characters */
    /* in the given set. if all characters in 'user' are in the   */
    /* set, then ok_len with be equal to the length of 'user'.    */
    ok_len = strspn(user, "abcdefghijklmnopqrstuvwxyz0123456789");
    if (ok_len == strlen(user)) {
 /* check if the user exists in our database */
 result = find_user_in_database(user); /* Mistake 4 */
    }
    alarm(0);

    return result;
}





Lets count the mistakes and bad programming practices in the above function:

* Too short timeout:
3 seconds might be enough for superman to type his user name, but a normal user obviously needs more time. Such a timeout should actually be tunable, because not all people type at the same pace, or should be long enough for even the slowest of users.

* Printing while timer is ticking:
Norty Norty. This printing should have been done before starting up the timer. Printing may be a time consuming operation, and thus will leave less time then expected for the user to type in the expected input.

* Exiting function without turning off timer:
This kind of mistake is hard to catch. It will cause the program to randomally exit somewhere LATER during the execution. If we'll trace it with a debugger, we'll see that the signal was received while we were already executing a completely different part of the program, leaving us scratching our head and looking up to the sky, hoping somehow inspiration will fall from there to guide us to the location of the problem.

* Making long checks before turning off timer:
As if it's not enough that we gave the poor user a short time to check for the input, we're also inserting some database checking operation while the timer is still ticking. Even if we also want to timeout the database operation, we should probably set up a different timer (and a different ALRM signal handler), so as not to confuse a slow user with a slow database server. It will also allow the user to know why we are timing out. Without this information, a person trying to figure out why the program suddenly exits, will have hard time finding where the fault lies.




Summary - "Do" and "Don't" inside A Signal Handler
We have seen a few "thumb rules" all over this tutorial, and there are quite many of those, as the area of signals handling is rather tricky. Lets try to summarize these rules of thumb here:

Make it short - the signal handler should be a short function that returns quickly. Instead of doing complex operations inside the signal handler, it is better that the function will raise a flag (e.g. a global variable, although these are evil by themselves) and have the main program check that flag occasionally.
Proper Signal Masking - don't be too lazy to define proper signal masking for a signal handler, preferably using the sigaction() system call. It takes a little more effort then just using the signal() system call, but it'll help you sleep better at night, knowing that you haven't left an extra place for race conditions to occur. Remember - if some bug has a probability of 1/10,000 to occur, it WILL occur when many people use that program many times, as tends to be the case with good programs (you write only good programs, no?).
Careful with "fault" signals - If you catch signals that indicate a program bug (SIGBUS, SIGSEGV, SIGFPE), don't try to be too smart and let the program continue, unless you know exactly what you are doing (which is a very rare case) - just do the minimal required cleanup, and exit, preferably with a core dump (using the abort() function). Such signals usually indicate a bug in the program, that if ignored will most likely cause it to crush sooner or later, making you think the problem is somewhere else in the code.
Careful with timers - when you use timers, remember that you can only use one timer at a time, unless you also (ab)use the VTALRM signal. If you need to have more then one timer active at a time, don't use signals, or devise a set of functions that will allow you to have several virtual timers using a delta list of some sort. If you've no idea what I'm talking about, you probably don't need several simultaneous timers in the first place.
Signals are NOT an event driven framework - it is easy to get carried away and try turning the signals system into an event-driven driver for a program, but signal handling functions were not meant for that. If you need such a thing, use some framework that is more suitable for the application (e.g. use an event loop of a windowing program, use a select-based loop inside a network server, etc.).

Page : << Previous 3