10.3 Setting One of Five Single TimersThis section describes an implementation for setting one of five possible software timers, using the underlying process interval timer ITIMER_REAL. The main program takes the timer number and the timer interval (in microseconds) as command-line arguments and calls the timerstart function. The main program then waits for the timer to expire, prints out a message that the timer has expired, and exits. 10.3.1 The virtualtimers objectImplement the software timers in an object called virtualtimers. Use a static variable called timers of type timerdata_t to hold the internal timer data for the object as shown below.
The members of timerdata_t have the following meanings.
Return the timer number associated with a particular entry in the events array. The eventnumber parameter specifies the position in the events array, which is indexed from 0. The getevent functions returns 1 if eventnumber is negative or greater than or equal to numevents.
Return the value of numevents.
Return the timer number of the running timer or 1 if there is no running timer.
Return the current value of a timer n from the active array or 1 if the timer is not active or the timer number is invalid.
Remove the top event from events and return the event's timer number or 1 if events is empty. This function is needed later when multiple timers are handled.
Initialize the timers data structure as shown in Figure 10.2. The function also calls catchsetup of the hardwaretimer object and showinit of the show object. If successful, timerinit returns 0. If unsuccessful, timerinit returns 1 and sets errno.
Start timer n with the time interval given in microseconds. For this part, assume that no timers are active. The interval is the number of microseconds in the future after which the timer should expire. To start timer n, do the following.
Stop timer n if it is active and remove the timer from events if it is there. This function is needed later when multiple timers are handled.
Wait until there is an event in events and then return without changing events. Do not use busy waiting, but instead, call waitforinterrupt from the hardwaretimer module. The virtualtimers object also contains the private timerhandler function, which it passes to the hardware timer module by calling catchsetup in timerinit.
Handle the timer signal. This function is called by the actual signal handler in hardwaretimer to maintain the timers structure when the real hardware timer expires. Do the following steps in timerhandler.
Since the hardwaretimer object handles the signals, it must contain the actual signal handler. The prototype of the signal handler may depend on the implementation and should not be part of the virtualtimers object. Since the timers must be manipulated when the signal is caught, this work should be done in the virtualtimers object. The real signal handler calls timerhandler to do this. Since timerhandler has internal linkage, the timerinit function passes a reference to it when calling catchsetup in the hardwaretimer object. The timerhandler is an example of a callback. Callbacks are frequently used by applications to request that a service call one of the application's functions when some event occurs. 10.3.2 The hardwaretimer objectThe hardwaretimer object contains code to handle a single "hardware" timer. The functions that are accessible from outside the object are as follows.
Block the SIGALRM signal. The blockinterrupt function returns 1 if the signal was already blocked and 0 otherwise.
Set up a signal handler to catch the SIGALRM signal by calling sigaction. If successful, catchsetup returns 0. If unsuccessful, catchsetup returns 1 and sets errno. The handler parameter is the name of the function that does the work of handling the signal. The actual signal handler in hardwaretimer just calls the handler function. The virtualtimers object calls the function catchsetup to set up signal handling.
Return the time remaining on the hardware timer if it is running or 0 if it is not running. If unsuccessful, gethardwaretimer returns 1 and sets errno. Use getitimer to implement this function.
Return 1 if the SIGALRM signal is blocked and 0 otherwise.
Start the ITIMER_REAL timer running with the given interval in microseconds. Call sethardwaretimer only when the timer interrupt is blocked or the interval timer is stopped. The interval parameter specifies the interval for setting the timer in microseconds. Use setitimer to implement this function.
Stop the hardware timer if it is running. This function is harder to implement than it might seem. We discuss this later since it is not needed in this section.
Unblock the SIGALRM signal.
Call sigsuspend to wait until a signal is caught. The waitforinterrupt function does not guarantee that the signal was from a timer expiration. This function is normally entered with the timer signal blocked. The signal set used by sigsuspend must not unblock any signals that were already blocked, other than the one being used for the timers. If the main program has blocked SIGINT, the program should not terminate if Ctrl-C is entered. Some of these functions are not needed until a later part of this project. The interface to the hardware timer is isolated in this file, so using POSIX:TMR timers or a different underlying timer than ITIMER_REAL only requires changing these functions. Define a header file called hardwaretimer.h that has the prototypes of the functions in the hardwaretimer object. 10.3.3 Main program implementationWrite a main program called timermain that initializes everything by calling timerinit and then loops, reading from standard input until an error or end-of-file occurs. Specifically, timermain does the following tasks in the loop.
Use scanf to read in the values from standard input. 10.3.4 Instrumentation of the timer code with showCode with signal handlers and timers is hard to test because of the unpredictable nature of the events that drive the program. A particular timing of events that causes an error might occur rarely and not be easily reproducible. Furthermore, the behavior of the program depends not only on the input values but also on the rate at which input data is generated.This section describes how to instrument the code with calls to a show function as a preliminary step in testing. This instrumentation is critical for debugging the later parts of the project. Two versions of the show function are presented here: one outputs to standard output and the other uses remote logging. This subsection explains what show does and how to use it in the program.The prototype for show is as follows.
If the traceflag is 0, show does nothing, allowing you to easily remove the debugging output. If traceflag is 1, the show function displays the message in the second parameter and the status of the timer data structure. The show function displays the val1 and val2 parameters if they are nonnegative. Usually, these parameters will represent a timer number and an interval in microseconds, but sometimes they will represent two timers. The blockedflag is 1 if the timer signal is supposed to be blocked when the call is made and 0 if the timer signal should not be blocked. It will be important to keep track of the blocking and unblocking of the signal in the complete timer implementation.The virtualtimers file should have a traceflag global variable initialized to 1. Insert a call to showinit in the timerinit function of the virtualtimers module. Insert calls to show liberally throughout the virtualtimers module. For example, the first line of timerstart could be the following.
A call to start timer [3] for 1,000,000 microseconds might then produce the following output.
The fields are as follows.
Program 10.1 can be used with this project to display messages similar to the one above. Program 10.1 show.cA version of show that prints to standard output.
Put the code of Program 10.1 in a separate file. Instrument the timer functions so that each time something of interest occurs, the program calls show with the appropriate parameters. For this part, just insert the following four lines.
Test the program with a variety of appropriate inputs and observe the output of show. Remember that printf is not async-signal safe. The calls to show in timerhandler cause a problem if timermain also uses the standard I/O library without blocking the signals during the calls. The show function blocks the timer interrupt before producing any output to avoid this problem as well as to protect the shared timers data structure.Appendix D.2. It avoids a possible buffer overflow by calling snprintfappend to add to the message. This function takes parameters similar to those of snprintf but appends to a string given by the first parameter. The second parameter is a limit on the total size of the buffer used to hold the string.In this version, the showinit function opens a connection to the remote logger, using the default parameters. Each output message is associated with a generator string indicating the source of the message. The generator is just the timer gotten from the val1 parameter. The output message has the following fields separated by tabs so they can be displayed in a table.
Figure 10.9 on page 363 shows sample output from one window of the remote logger. Program 10.2 showremote.cA version of show that uses a remote logging facility.
|