Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

Writing Real-Time Programs Under Unix


JUN88: WRITING REAL-TIME PROGRAMS UNDER UNIX

WRITING REAL-TIME PROGRAMS UNDER UNIX

Bill Cramer

Bill Cramer is a software engineer with Teknekrom Infoswitch, 1784 Firman Dr., Richardson, TX 75081, which builds Unix-based adjunct processors for PBX and central-office telephone switches. Most recently, he has been working in the area of software productivity.


For most programmers, the expression "real-time with Unix" falls into the same category as "user-friendly yet powerful" and "self-documenting code." But just as there actually exist a few powerful, user-friendly products (which were no doubt written with self-documenting code), Unix is a realistic and practical operating system choice for many real-time or near-real-time applications.

A few applications, however, need a subtle mix of different operating system characteristics. In a computer-automated assembly line, for example, each assembly workstation may have its own computer or embedded processor running under a real-time operating system. These real-time computers keep busy reading gauges, moving robot arms, turning switches on and off, and so on. A central computer that doesn't need to precisely control valves opening or the amount of torque a robot arm applies to the handle may preside over the individual workstations. That central computer has more global concerns--for instance it may have to shut down the assembly line if an individual station doesn't have the parts required to build the product.

The central computer may intermittently receive input (how may parts it has processed and so on) from individual workstations. It may then send commands to other workstations to speed up or slow down the assembly line or to reroute a certain part to a different station. It may also sound an audible alarm to alert the foreman that a particular machine needs servicing.

The real-time OS in the workstation controller generally performs the work it was designed to do very well; however, it was never really intended to write reports, maintain a database, or display the status of all the individual workstations on the factory floor. To perform these tasks, the central computer needs a general-purpose, widely used, widely supported OS such as Unix.

To understand how Unix serves both the real-time world and the general-purpose world, it is necessary to understand how real-time systems differ from time-sharing systems. Two common multitasking system features-process scheduling and process blocking--can illustrate how Unix differs from a real-time system.

The Scheduler

Multitasking operating systems include a scheduler, which has the responsibility of allocating CPU time among the various loaded processes. Even though both Unix and most real-time operating systems have schedulers, their basic algorithms differ sharply. A real-time system is event-driven, whereby the OS allocates CPU time to processes that need to service events. (Events include new input data, a clock time-out, and so on.) The scheduler in Unix, on the other hand, is a time slicer; it attempts to give all processes a chance to run.

The system (or the user of the system) may assign a priority to each process in the system. For a real-time system, a high-priority process with outstanding events will have near-absolute control of the CPU; only a hardware interrupt can intrude on the process. A low-priority process will run only when the high-priority process can no longer run (for example, when it must wait on some new external event).

Under Unix, the OS may give a high-priority process a larger slice of CPU time than it does a low-priority process. However, when the high-priority process has used up its time slice, the OS suspends it and begins execution of another process.

Figure 1a flow for solution A

Figure 2a flow for solution B

Asynchronous and Synchronous System Requests

At any given time, a process is in one of three broad states: running, in which the process has control of the CPU; runable, in which the process would like to run but some other process currently has control of the CPU; and blocked, in which the process is waiting on some event--a clock interval, operator input, output to flush, and so on.

Unix, like many other systems, makes most of its OS requests synchronously, and they become blocked until the system can service them. Most real-time systems, on the other hand, make systems calls that may lead to blocking asynchronously, whereby the calls make their request for the resource but continue normal processing. When the resource becomes available, the OS notifies the process in one of two ways--either with an event flag or through a completion routine. Many real-time processes use a combination of event flags and completion routines.

Event Flags

Event flags are semaphores that indicate whether or not a condition has become true--for example, a process may ask the system to read a device and set the event flag when it has finished reading in the data. A real-time process may have several outstanding asynchronous system requests pending--the program may make a request to read input from a port, it may schedule a clock time-out, and it may request a read from an interprocess queue. Real-time operating systems allow the process to become blocked until one flag or a combination of flags become set.

Unix has a similar concept, called semaphores. The most common use of semaphores, however, is to provide locking on some shared resource such as a shared memory table. Unix provides no way to link a read() request to a semaphore, for example; hence it provides no way for a process to become blocked while waiting for the system to service multiple requests.

Completion Routines

Completion routines are called by the OS on the behalf of a process when the OS has finished with some requested function. (In some environments completion routines are also known as asynchronous system traps, or asynchronous system routines.) Generally, you can think of completion routines as the software equivalent of hardware interrupts.

If the OS finds that another event has become true while the process is still handling the original completion routine, it can either handle that event right away or else queue up the routine and handle it after finishing with the current completion routine. Some real-time systems also allow prioritized event handling, like CPUs that prioritize hardware interrupts.

Most systems allow a program to pass some predefined data to a completion routine; hence the program may have a single completion routine servicing similar requests. You may want to set up read requests on several sensors using the same completion routine for each; when the system asynchronously calls the completion routine, it will pass some argument block identifying the particular sensor that has been read.

Unix has no concept of completion routines, and because all resource requests are synchronous, it has no need to notify a process that a particular request has been completed, either through an event flag or by calling a completion routine. (Unix allows you to input calls that return immediately if no data is present. Although this doesn't provide an asynchronous interrupting ability, it does allow a program to set up its own polling loop without becoming blocked waiting on a single event. One of the examples I discuss later uses this sort of "no wait" input.)

Unix does, however, have a construct called a signal, which behaves similarly to a completion routine in that when a process receives a signal (from the operating system or from another process), the signal will begin execution of some predefined routine. Signals were designed to handle asynchronous hardware exceptions such as bus, floating-point, and addressing errors as well as user abort requests and program time-outs.

Once a process has intercepted a signal, the expected response is usually to perform some cleanup (close files, release resources, maybe print an error message) and then exit. By default Unix assigns a set of routines to handle this cleanup; however, a program also has the option of setting up its own functions that Unix will call when it receives a particular signal.

The signal-handler function can perform any operation. Because of the nature of the operating system, however, while in the handler code, the process is in a very fragile state. For example, after Unix calls the signal-catcher routine, it automatically resets the signal catcher to its own default value. If the process receives a second signal before the handler has had a chance to reset the signal catcher, Unix will call its own routine, which may abort the program.

The only signal type that you can use successfully as part of your normal programming is the clock time-out signal, SIGALRM, which is a special type of signal that you can schedule from within your program via the alarm() system call. Although you cannot predict exactly when your program will receive the SIGALRM signal, you can keep track of whether or not your program has issued the alarm() call; hence your program can take some precautions to handle the interruption. One of the sample programs accompanying this article illustrates a "safe" SIGALRM handler.

Two Alternatives

When building a real-time Unix process or porting an existing process from a real-time environment, you can choose two basic plans of attack. The first involves simulating the real-time environment by setting up a scheduler routine within your process. Although your scheduler won't be as time efficient as a real-time OS scheduler, this method does have the advantage that the Unix version will have the same basic structure as the real-time version. This is particularly attractive when porting an existing process to Unix.

The second method of building a real-time process requires that you understand Unix's limitations in the real-time environment and structure your process so that it takes advantage of Unix's strengths. As mentioned earlier, Unix has no real notion of asynchronous system resource requests nor of the real-time constructs used for implementing them (event flags and completion routines). The key, therefore, is to write your processes so that they don't require asynchronous system requests.

That last statement seems intuitively obvious and may seem intuitively impossible as well. With a little forethought, however, you can create a process that will run with nearly the same efficiency as a version running on a dedicated real-time operating system.

The Problem

Suppose, for example, that you have a process whose primary duty is to read a set of sensors and process the data. If the process doesn't receive any input from the sensors after some time interval, it should alert some other process. In the background it may also need to deal with commands coming from other processes. Figure 1 , page 20, shows the basic data flows.

For the sake of simplicity, let's assume that the sensors are connected via a standard serial port and that sensor data comes in over the link in new-line-terminated ASCII strings. The algorithm and code omit the actual input processing as this isn't important to the example. The program listings also omit error checking; for any production program, you will, of course, need a generous portion of error checking.

Solution A--Overriding the Unix Scheduler

One method for solving the problem is to make Unix believe that it is a real-time OS. This solution is inferior to Solution B (described later), but for some applications, particularly for quick-and-dirty ports from real-time systems, you may prefer it.

Listing One, Listing Two, and Listing Three show the solution written in C. Notice that the main loop attempts to read each of the sensors as well as the command queue. If read() finds data present, it will process the data; otherwise, it will continue polling the other inputs. To prevent the process from hogging the CPU in an endless loop, it will delay between loop iterations.

This program includes two functions-nap() and marktime()--that are worthy of further discussion. Nap() provides a program delay of a finer granularity than Unix normally provides with the sleep() system call. Marktime() provides a consistant interface to the SIGALRM clock signal.

Nap()

The Unix system call sleep() delays a program with a granularity of 1 second. Most versions of Unix actually implement this by delaying the program until the next second boundary after the indicated time rather than an exact number of seconds following the point at which your program calls sleep(). Hence, sleep(1) may delay your process anywhere from a single clock tick to nearly a full second after invocation. In most instances, this is acceptable. For the sample program, however, let's arbitrarily decide that you need to poll the input sensors every 300 milliseconds by using the nap() function as shown in Listing Two.

Nap() works by using a special characteristic of the standard terminal driver. Normally, a terminal driver reads characters from a port until it sees a carriage return and then returns the input string to the process. This is known as canonical processing. Canonical processing also understands user-defined ASCII characters for backspace and line erase. Canonical processing is appropriate for reading input when the user is typing in input from a terminal.

Via an ioctl() system call, you can disable canonical processing and set two input parameters---VMIN and VTIME---so that a read() from a port will return after either reading VMIN characters or else delaying "TIME ticks (expressed as tenths of a second). Nap() uses (some may say abuses) this characteristic by attempting to read from an unused port. Naturally, it will find no characters available and will return a failure after a delay of "TIME.

Marktime()

I mentioned earlier that the Unix signal provides a function similar to a completion routine. The alarm() system function call allows a program to schedule the signal SIGARLM sometime in the future, and the signal() system function call allows the program to define a signal handler. By themselves, alarm() and signal() provide a limited means of implementing a completion routine. They are limited in that only one alarm can be outstanding at any given time and that they have no implicit means of passing data to the completion routine. Also, a program that needs to set alarms from different parts of the program has no means of coordinating the different time-outs except through the use of global data--a poor programming practice.

Marktime() and its associated functions form a shell around alarm() and signal(). They allow the calling program to set up multiple time-outs; each time-out can have its own completion routine, a flag that will be set when the time-out expires, and a pointer to a unique data block. The calling process can also disable, enable, and cancel time-outs through the functions associated with marktime(). Note in Listing One that the program disables time-outs when not explicitly processing the main loop; this ensures that the time-out won't unexpectedly interrupt other processing.

Listing Three shows marktime() and its associated functions.

Solution B--Restructuring the Problem

For most situations, the preferred method of solving the problem is to work within the limits of Unix. Remember that Unix processes can only block on one system call at a time, typically a read(), a pause(), or a sleep().

Instead of trying to make a single Unix process handle all the possible events, as I did in solution A, you can reorganize the main routine into several small processes. Each of the small processes will be responsible for a single blockable resource; when one of the processes has completed its duty (for example, when a sensor input process has read a block of data), it will pass a message back to the main process via an IPC message queue. The main process will then have only one blocking state, which is a msgrcv() of its message queue. Figure 2, page 20, shows the data flows for this solution.

Although this method incurs a degree of overhead in the form of message passing, it simplifies the problem by removing the polling/sleeping loop, and hence the overall performance will generally be better than that in solution A.

Listing Four, Listing Five, and Listing Six show the C code for this solution. Note that the listings don't show how the system starts the smaller processes; in a real application the main process may fork() and exec() the smaller processes as children. Also, note that the child processes have no means of relating exception conditions to the parent process; one way to do this would be to define additional messages.

Finally, note that the child processes break the read() using a marktime() time-out. An alternative to this would be to use noncanonical input from the port using the VTIME and VMIN parameters to control the time-out. The down side to using this method is that the terminal driver no longer parses according to new-line delimeters. which means that the program must handle its own parsing of the input stream. Although this method may provide a cleaner interface, particularly if the input is a binary stream rather than ASCII, I used marktime() to illustrate its use with a pseudoevent flag.

Conclusion

Both solutions have merit under the right circumstances, and both can successfully perform the required chores. The best solution to running a near-real-time process under Unix, however, lies in understanding the nature of the operating system--its strengths and weaknesses--and working with the system instead of against it.

Although the solution requires some extra nontraditional planning (nontraditional in the sense of how you might build the product under a real-time operating system). restructuring the problem as I did in solution B provides the most efficient and trouble-free product.

[LISTING ONE]

<a name="0113_0010">
/* Listing One -- Main routine for Solution A  */

#include <stdio.h>              /* standard Unix I/O header */

#include <termio.h>             /* file/port control headers */
#include <fcntl.h>

#include <sys/types.h>          /* required headers for IPC */
#include <sys/ipc.h>
#include <sys/msg.h>

/* local constants */

#define TRUE (1==1)             /* boolean values */
#define FALSE (1==0)
#define SENSOR_1_DEV "/dev/tty45"/* tty port names for sensor ports */
#define SENSOR_2_DEV "/dev/tty46"
#define SENSOR_3_DEV "/dev/tty47"
#define TIMEOUT (10L)           /* timeout if no input received */
#define MAX_SENSOR_MSG (100)    /* maximum size of a sensor message */
#define MAX_CMD_MSG (100)       /* maximum size of a command message */

/* local function declarations */

void
    process_timeout();          /* process sensor read timeout */

/* module-wide data */

int
    timeout_id[3];              /* marktime timeout timer IDs */

/* Main routine for program */

main ()
{
int
    cmd_size,                   /* number of bytes in command message */
    msg_size,                   /* number of bytes in sensor message */
    qid,                        /* message queue identifier */
    sensor_1_fd,                /* file descriptors for the sensors */
    sensor_2_fd,
    sensor_3_fd;

char
    cmd_msg[MAX_CMD_MSG],       /* buffer for reading command queue */
    sensor_msg[MAX_SENSOR_MSG]; /* buffer for reading sensor data */

/* open the sensor input files */

sensor_1_fd=open(SENSOR_1_DEV,O_RDWR|O_NDELAY);
sensor_2_fd=open(SENSOR_2_DEV,O_RDWR|O_NDELAY);
sensor_3_fd=open(SENSOR_3_DEV,O_RDWR|O_NDELAY);
/* open the command queue */

qid=msgget(1,IPC_CREAT|0666);

/* establish initial timeouts for each sensor */

timeout_id[0]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)1);
timeout_id[1]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)2);
timeout_id[2]=marktime(TIMEOUT,(int*)NULL,process_timeout,(char*)3);

/* loop forever */

while (TRUE)
    {
    /* Read (or try) each sensor and process the input.  */

    if ( (msg_size=read(sensor_1_fd, sensor_msg, MAX_SENSOR_MSG)) > 0)
        process_sensor (sensor_msg, msg_size, 1);
    if ( (msg_size=read(sensor_2_fd, sensor_msg, MAX_SENSOR_MSG)) > 0)
        process_sensor (sensor_msg, msg_size, 2);
    if ( (msg_size=read(sensor_3_fd, sensor_msg, MAX_SENSOR_MSG)) > 0)
        process_sensor (sensor_msg, msg_size, 3);

    /* check for input on the message queue */

    if ( (cmd_size=msgrcv(qid,cmd_msg,MAX_CMD_MSG,0,IPC_NOWAIT)) >= 0)
        process_cmd_msg (cmd_msg, cmd_size);

    /* delay the program before continuing loop */

    nap (3);
    }
}

/* Function process_cmd_msg ()
**
** This is a stub routine for handling command queue input.  It suspends
** timeouts while processing the input and then resumes them when it is
** done.
*/

static process_cmd_msg (msg, size)

char
    *msg;                       /* INPUT: pointer to command message */

int size;                       /* INPUT: size of *msg */

{
timer_disable();
/* (do some appropriate processing on the command) */
timer_enable();
return;
}
/* Function process_sensor()
**
** This is a stub routine for handling sensor input.  It cancels the
** outstanding timeout timer and reschedule a new timeout.  It also
** suspends timeout interrupts while it is processing the input.
*/

static process_sensor (msg, size, sensor_num)

char
    *msg;                       /* INPUT: pointer to sensor data */

int
    size,                       /* INPUT: size of *msg */
    sensor_num;                 /* INPUT: sensor number  */

{
timer_disable();
cancel_marktime (timeout_id[sensor_num-1]);
/* (do some appropriate processing on the message) */
timeout_id[sensor_num]=marktime(TIMEOUT,(int*)NULL,
        process_timeout,(char*)sensor_num);
timer_enable();
return;
}

/* Function process_timeout()
**
** This is a stub for the sensor input timeout timer.  It is called as a
** completion routine from marktime(), which passes the sensor number
** as an argument.  The function suspends timeouts while it processing
** the error and resumes processing when it is done.  It also resets the
** timeout before exiting, something that a real program may or may not
** want to do in real life, depending on the appication.
*/

static void process_timeout (sensor_num)

char
    *sensor_num;                /* INPUT: sensor number which has */
                                /* timed out (declared char* because */
                                /*     that's what marktime() calls */
                                /*     it with.  Value is really int */
{
timer_disable();
/* (do some appropriate processing on the error) */
timeout_id[(int)sensor_num]=marktime(TIMEOUT,(int*)NULL,
        process_timeout, sensor_num);
timer_enable();
return;
}



<a name="0113_0011"><a name="0113_0011">
<a name="0113_0012">
[LISTING TWO]
<a name="0113_0012">
/* Listing Two -- nap() */

#include <stdio.h>              /* standard Unix I/O header */

#include <termio.h>             /* port file control headers */
#include <fcntl.h>

#define TRUE (1==1)             /* boolean constants */
#define FALSE (1==0)

/* Function nap()
**
** This function provides a program delay function with resolution
** to a tenth of a second.  It works by opening a file to /dev/clk
** (which should be linked to some unused /dev/tty), setting the
** input parameters to non-canonical, and setting the VTIME value
** to the passed argument.  It then does a read on the file which
** will time out after the indicated delay time.
*/

void nap (delay)

int
    delay;                      /* INPUT: delay in tenths of a second */
{
static int
    clock_opened=FALSE;         /* has the clock device been opened? */

static int
    fd;                         /* clock file descriptor */

int
    flags;                      /* file control flags */

char
    buff[10];                   /* dummy read buffer */

struct termio
    clock_termio;               /* terminal I/O parameters */

/*
** the first time through the routine, open the clock port
** and set the port parameters.
*/

if (!clock_opened)
    {
    fd=open("/dev/clk",O_RDONLY|O_NDELAY);
    ioctl (fd, TCGETA, &clock_termio);
    clock_termio.c_cflag |= CLOCAL;
    clock_termio.c_lflag &= ~ICANON;
    ioctl (fd, TCSETA, &clock_termio);
    flags = fcntl (fd, F_GETFL, 0) & ~O_NDELAY;
    fcntl (fd, F_SETFL, flags);    clock_opened = TRUE;
    }

/* set the VTIME delay to the indicated value */

ioctl (fd, TCGETA, &clock_termio);
clock_termio.c_cc[VMIN] = 0;
clock_termio.c_cc[VTIME] = delay;
ioctl (fd, TCSETAF, &clock_termio);

/* perform the dummy read */

read (fd, buff, 10);

return;
}



<a name="0113_0013"><a name="0113_0013">
<a name="0113_0014">
[LISTING THREE]
<a name="0113_0014">
/* Listing Three -- marktime() and related functions */

#include <stdio.h>              /* standard input/output include */
#include <sys/signal.h>         /* signal definitions  */

#define MAX_ELEMENT 10          /* maximum number of queued events */
#define TRUE (1==1)
#define FALSE (1==0)

struct tmq                      /* timer queue element structure */
    {
    unsigned long time;         /* expiration time (UNIX) of element */
    void        (*ast)();       /* user ast to call at expiration */
    char        *arg;           /* value passed to ast() (if called) */
    struct tmq  *next;          /* pointer to next element */
    int         *flag;          /* event count to bump at expiration */
    };

static struct tmq
    *queue_free,                /* first available element in queue */
    *queue_head,                /* first element in clock queue */
    *queue_tail,                /* last element in clock queue */
    timer_queue[MAX_ELEMENT];   /* table of queue elements */

static int
    q_busy = FALSE,             /* queue update in progress */
    q_enabled = FALSE,          /* queue countdown operations enabled */
    queue_init = FALSE;         /* queue initialized flag */

static unsigned long
    next_event;                 /* last known value of expiration */
                                /*    time of head element (may */
                                /*    change during ast) */

extern unsigned long
    time();                     /* get Unix time */

extern unsigned int
    alarm();                    /* set system alarm clock */

void
    queue_ast();                /* internal asynchronous trap */

/* Function marktime()
**
** This function inserts an element into the timer queue (performing
** queue initialization if necessary).  If the new element is at the
** top of the queue, it will reset the alarm clock.
**
**      Return values:
**              -1      queue full
**              >=0     element ID
*/

int marktime (time_of_event, flag, user_ast, user_arg)
unsigned long
    time_of_event;              /* INPUT: seconds until event */

char
    *user_arg;                  /* INPUT: user-supplied argument (in */
                                /*    reality, this could be a */
                                /*    pointer to any data type, but */
                                /*    char* is a good enough */
                                /*    description */

void
    (*user_ast)();              /* INPUT: user function to call at */
                                /*    expiration (may be NULL) */
int
    *flag;                      /* INPUT: pointer to flag to make */
                                /*    TRUE on expiration (may be NULL)*/

{
int
    element,                    /* offset into timer_queue */
    found_slot,                 /* loop termination flag */
    ret_val;                    /* >=0 element ID, -1 queue full */

register struct tmq
    *new_element,               /* pointer to newly added element */
    *last_ptr,                  /* previous pointer into timer_queue */
    *q_ptr;                     /* pointer into timer_queue */


/* if the queue is uninitialized, then initialize it */

if (!queue_init)
    {
    queue_free = &timer_queue[0];
    for (element=0; element<MAX_ELEMENT; element++)
        timer_queue[element].next = &timer_queue[element+1];
    timer_queue[MAX_ELEMENT-1].next = NULL;
    queue_head = NULL;
    queue_tail = NULL;
    q_busy = FALSE;
    q_enabled = TRUE;
    signal (SIGALRM, queue_ast);
    queue_init = TRUE;
    }

/* insert the new element into the linked list */

if ( (new_element=queue_free) != NULL)
    {
    q_busy = TRUE;
    queue_free = queue_free->next;
    new_element->time = time_of_event + time((long*)0);
    if (flag != NULL)
        {        new_element->flag = flag;
        *flag = FALSE;
        }
    new_element->ast = user_ast;
    new_element->arg = user_arg;
    q_ptr = queue_head;
    last_ptr = queue_head;
    found_slot = FALSE;
    while ( (q_ptr!=NULL) && !found_slot)
        {
        if (new_element->time < q_ptr->time)
            found_slot = TRUE;
        else
            {
            last_ptr = q_ptr;
            q_ptr = q_ptr->next;
            }
        }
    /* if the new element is first, then reset alarm for this element */
    if (q_ptr == queue_head)
        {
        new_element->next = queue_head;
        queue_head = new_element;
        next_event = new_element->time;
        if (q_enabled)
            alarm ((unsigned int)(new_element->time - time((long*)0)));
        }
    else
        {
        new_element->next = q_ptr;
        last_ptr->next = new_element;
        }
    ret_val = new_element - timer_queue;
    q_busy = FALSE;
    }
else
    ret_val = (-1);

return (ret_val);

}

/* Function queue_ast()
**
** This function is called when the clock reaches the time
** at the head of the timer queue.  It sets the indicated
** flag (if non-NULL), and removes the head element
** from the queue.  It reschedules the internal timeout to the
** time of the new queue head element, if one exists. (If the
** time of the next element is less than the present time, the
** function schedule the event in one second.) Finally,
** if the user-supplied ast is non-NULL, it calls that routine,
** passing the user-supplied argument.
**
** The function checks the q_busy flag (set by marktime() and** cancel_marktime()) to verify that the program isn't in the
** middle of a queue update.  If the flag is TRUE, queued_ast()
** reschedules itself 1 second from now, rather than attempting
** to hack at the queue blindly.
**
*/
static void queue_ast ()

{
register struct tmq
    *q_ptr;                     /* temporary queue pointer */

unsigned int
    nexttime;                   /* seconds until next timeout */

/*
** If it is safe to fiddle with the queue, pull out the next
** element and schedule the next timeout, if any.
*/

if (!q_busy)
    {
    q_ptr = queue_head;
    queue_head = q_ptr->next;
    q_ptr->next = queue_free;
    queue_free = q_ptr;
    if (queue_head != NULL)
        {
        if (q_enabled)
            {
            if ( (nexttime=(unsigned int)queue_head->time -
                    time((long*)0)) > 0)
                alarm (nexttime);
            else
                alarm (1);
            }
        }
    /* set the user's flag, if any is specified */

    if (q_ptr->flag != NULL)
        *q_ptr->flag = TRUE;

    /* call the user's completion routine, if any specified */

    if (q_ptr->ast != NULL)
        (*q_ptr->ast)(q_ptr->arg);
    }

/* if the queue is busy, reschedule the timeout for later */

else
    {
    if (q_enabled)
        alarm (1);
    }
/* reset signal catcher */

signal (SIGALRM, queue_ast);

return;

}
/* Function cancel_marktime()
**
** This function removes the specified timer request from the
** timer queue.
**
** Return values:
**      >=0             time remaining till expiration
**      <0xFFFFFFFF     no such element
**
** If the queue hasn't been initialized, the function returns
** 'no such element'.  It does NOT initialize the queue.
**
** The element ID, used for identifying the event to be canceled
** is returned by marktime.
*/

unsigned long cancel_marktime (id)

int
    id;                         /* INPUT: element id (returned from */
                                /*    marktime) */

{
register struct tmq
    *last_ptr,                  /* previous pointer into timer_queue */
    *q_ptr;                     /* pointer into timer_queue */

int
    found;                      /* loop termination flag */

unsigned long
    ret_val;                    /* >=0 for success, <0 for failure */

unsigned int
    neyvtime;                   /* time until next event expiration */

/* make sure that the queue is initialized */
if (queue_init)
    {
    q_busy = TRUE;
    /* make sure that the ID is legitimate */
    if ( (id >= 0) && (id < MAX_ELEMENT) )
        {
        /*
        ** Traverse the event queue until we find the requested element.
        ** This ensures that the element has really been used and also
        ** gives us the pointers for relinking the list.        */
        for (q_ptr=queue_head, last_ptr=q_ptr, found=FALSE;
                q_ptr!=NULL && !found;
                last_ptr=q_ptr, q_ptr=q_ptr->next)
            /* do we have a match? */
            if (q_ptr == &timer_queue[id])
                {
                ret_val = q_ptr->time - time((long*)0);
                /*
                ** if the cancelled element was at the head, then
                ** reschedule the next timeout, if any exist.
                */
                if (q_ptr==queue_head)
                    {
                    queue_head = q_ptr->next;
                    if (queue_head != NULL)
                        {
                        if (q_enabled)
                            {
                            if ((nexttime=(unsigned int)queue_head->time
                                    - time((long*)0)) > 0)
                                alarm (nexttime);
                            else
                                alarm (1);
                            }
                        }
                    else
                        alarm (0);
                    }
                else
                    last_ptr->next = q_ptr->next;
                q_ptr->next = queue_free;
                queue_free = q_ptr;
                found = TRUE;
                }
        /* did we ever find a match? */
        if (!found)
            ret_val = (-1);
        }
    else
        ret_val = (-1);
    q_busy = FALSE;
    }%]se
    ret_val = (-1);

return (ret_val);

}
/* Function timer_enable()
**
** This function enables normal timer queue operations.  If
** there is an element on the top of the timer queue, it sets
** up the appropriate alarm; otherwise, just marks the queue
** as enabled.  If the time of the next event has already passed,
** it will schedule it to happen in one second (prevents unexpected** interrupt).
*/

void timer_enable()
{

unsigned int
    nexttime;                   /* seconds till next timeout */

q_enabled = TRUE;
if (queue_head != NULL)
    {
    if ( (nexttime=(unsigned int)queue_head->time - time((long*)0)) > 0)
        alarm (nexttime);
    else
        alarm (1);
    }
else
    alarm (0);

return;

}
/* Function timer_disable()
**
** This function disables normal timer queue operations.  If
** there is an element on the top of the timer queue, it cancels
** the alarm() timer; otherwise, just marks the queue as disabled.
*/
void timer_disable()
{
q_enabled = FALSE;
if (queue_head != NULL)
    ai19m (0);
return;
}



<a name="0113_0015"><a name="0113_0015">
<a name="0113_0016">
[LISTING FOUR]
<a name="0113_0016">
/*Listing Four -- Sensor message definition */

/* sensor message command queue message structure */

struct message_rec
    {
    long        mtype;          /* message type */
    int         func;           /* message function code */
    int         sensor_num;     /* sensor number */
    char        data[100];      /* message data */
    };


/* sensor command queue function codes */

#define SENSOR_INPUT (1)
#define SENSOR_TIMEOUT (2)
#define SENSOR_COMMAND (3)



<a name="0113_0017"><a name="0113_0017">
<a name="0113_0018">
[LISTING FIVE]
<a name="0113_0018">
/* Listing Five -- Main routine for Solution B  */

#include <stdio.h>              /* standard Unix I/O header */

#include <termio.h>             /* file/port control headers */
#include <fcntl.h>

#include <sys/types.h>          /* required headers for IPC */
#include <sys/ipc.h>
#include <sys/msg.h>

#include "sensor.h"             /* defines sensor messages */

/* local constants */

#define TRUE (1==1)             /* boolean values */
#define FALSE (1==0)

/* Main routine for program */

main ()
{
int
    cmd_size,                   /* number of bytes in command message */
    qid;                        /* message queue identifier */

struct message_rec
    cmd_msg;                    /* buffer for reading queue message */

/* open the command queue */

qid=msgget(1,IPC_CREAT|0666);

/* loop forever */

while (TRUE)
    {
    /* wait for a new message on the command queue */

    /* check for input on the message queue */

    cmd_size = msgrcv (qid, &cmd_msg, sizeof(cmd_msg), 0, 0);
    switch (cmd_msg.func)
        {
        case SENSOR_INPUT :
            process_sensor (cmd_msg.data, cmd_size, cmd_msg.sensor_num);
            break;
        case SENSOR_TIMEOUT :
            process_timeout (cmd_msg.sensor_num);
            break;
        case SENSOR_COMMAND :
            process_cmd_msg (&cmd_msg, cmd_size);
            break;
            break;        }
    }
}


/* Function process_cmd_msg ()
**
** This is a stub routine for handling command queue input.
*/

static process_cmd_msg (msg, size)

char
    *msg;                       /* INPUT: pointer to command message */

int
    size;                       /* INPUT: size of *msg */

{
/* (do some appropriate processing on the command) */
}


/* Function process_sensor
**
** This is a stub routine for handling sensor input.
*/

static process_sensor (msg, size, sensor_num)

char
    *msg;                       /* INPUT: pointer to sensor data */

int
    size,                       /* INPUT: size of *msg */
    sensor_num;                 /* INPUT: sensor number  */

{
/* (do some appropriate processing on the message) */
}


/* Function process_timeout()
**
** This is a stub for the sensor input timeout timer.
*/

static process_timeout (sensor_num)

int
    sensor_num;                 /* INPUT: sensor which has timed out */
{
/* (do some appropriate processing on the error) */
}



<a name="0113_0019"><a name="0113_0019">
<a name="0113_001a">
[LISTING SIX]
<a name="0113_001a">
/* Listing Six -- Sensor input process (one per sensor) */

#include <stdio.h>              /* standard Unix I/O header */
#include <termio.h>             /* file/port control headers */
#include <fcntl.h>

#include <sys/types.h>          /* required headers for IPC */
#include <sys/ipc.h>
#include <sys/msg.h>
#include "sensor.h"             /* sensor command message structure */

#define TRUE (1==1)             /* boolean true and false */
#define FALSE (1==0)

#define TIMEOUT (10L)           /* sensor timeout delay */

main(argc,argv)
int
    argc;                       /* INPUT: number of command line arguments */

char
    *argv[];                    /* INPUT: pointers to command line arguments */
{

int
    flags,                      /* file control flags */
    marktime_id,                /* timeout timer ID */
    qid,                        /* IPC message queue ID */
    sensor_fd,                  /* file descriptor for sensor port */
    timeout;                    /* marktime timeout flag */

char
    filename[20];               /* name of port file */

struct message_rec
    sensor_msg;                 /* buffer for sensor message */

struct termio
    clock_termio;               /* terminal I/O parameters */

/* open the sensor port */

sprintf (filename, "/dev/tty%d", atoi(argv[1]));
sensor_fd=open(filename,O_RDWR);

/* open IPC queue */

qid=msgget(1,IPC_CREAT|0666);
sensor_msg.mtype = 1;
sensor_msg.sensor_num = atoi(argv[1]);

/* main loop */

while (TRUE)
    {
    /* set up timeout */
    marktime_id=marktime(TIMEOUT,&timeout,(void*)NULL,(char*)NULL);

    /* read until data received or timeout timer expires */

    read (sensor_fd, sensor_msg.data, sizeof(sensor_msg.data));
    if (!timeout)
        {
        /* got data -- cancel timeout and pass to main program */
        cancel_marktime (marktime_id);
        sensor_msg.func = SENSOR_INPUT;
        msgsnd (qid, &sensor_msg, sizeof(sensor_msg), 0);
        }
    else
        {
        /* timeout -- inform main program */
        sensor_msg.func = SENSOR_TIMEOUT;
        msgsnd (qid, &sensor_msg, sizeof(sensor_msg), 0);
        marktime_id=marktime(TIMEOUT,&timeout,(void*)NULL,(char*)NULL);

        }
    }
}






Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.