Next | Prev | Up | Top | Contents | Index

Waiting for a General Event

There are causes for synchronization other than time, block I/O, and memory allocation. For example, there is no defined interface comparable to biowait()/biodone() to mediate between an interrupt handler and the pfxread() or pfxwrite() entry points. You must design a mechanism of your own, using either a synchronization variable or the sleep()/wakeup() function pair.


Using sleep() and wakeup()

The sleep() and wakeup() function pair are the simplest, oldest, and least efficient of the general synchronization mechanisms. They are summarized in Table 9-23.

Functions for Synchronization: sleep/wakeup
Function NameHeader FilesCan Sleep?Purpose
sleep(D3) ddi.h & param.hYSuspend execution pending an event.
wakeup(D3) ddi.hNWaken a process waiting for an event.

Used carefully, these functions are suitable for simple character device drivers. However, when you are writing new code or converting a driver to multiprocessing you should avoid them and use synchronization variables instead (see "Using Synchronization Variables").

The basic concept is that the upper-layer routine calls sleep(n) in order to wait for an event that is keyed to an arbitrary address n. Typically n is a pointer to a data structure related to an I/O operation. The interrupt handler executes wakeup(n) to cause the sleeping process to resume execution.

The main reason to avoid sleep() is that, in a multiprocessor system, it is hard to ensure that sleeping always begins before wakeup() is called. The usual intended sequence of events is as follows:

  1. Upper-half routine initiates a device operation that will lead to an interrupt.

  2. Upper-half routine executes sleep(n).

  3. Interrupt occurs, and handler executes wakeup(n).
In a multiprocessor-aware driver (one with D_MP in its pfxdevflag constant; see "Driver Flag Constant"), there is a small chance that the interrupt can occur, calling wakeup(n), before the sleep(n) call has been completed. Because sleep() has not been called, the wakeup() is lost. When the sleep() call completes, the process sleeps forever. Synchronization variables are designed to handle this case.


Using Synchronization Variables

Synchronization variables, a feature of UNIX SVR4, are supported by IRIX beginning with release 6.2. These functions are summarized in Table 9-24.

Functions for Synchronization: Synchronization Variables
Function NameHeader FilesCan Sleep?Purpose
SV_ALLOC(D3) types.h & sema.hYAllocate and initialize a synchronization variable.
SV_DEALLOC(D3) types.h & sema.hNDeinitialize and deallocate a synchronization variable.
SV_INITtypes.h & sema.hNInitialize an existing synchronization variable.
SV_DESTROYtypes.h & sema.h Deinitialize a synchronization variable.
SV_BROADCAST(D3) types.h & sema.hNWake all processes sleeping on a synchronization variable.
SV_SIGNAL(D3) types.h & sema.hNWake one process sleeping on a synchronization variable.
SV_WAIT(D3) types.h & sema.hYSleep until a synchronization variable is signalled.
SV_WAIT_SIG(D3) types.h & sema.hYSleep until a synchronization variable is signalled or a signal is received.

A synchronization variable is a memory object of type sv_t, representing the occurrence of an event. You can allocate objects of this type dynamically, or declare them as static variables or as fields of structures.

One or more processes may wait for an event using SV_WAIT(). An interrupt handler or timer callback function can signal the occurrence of an event using SV_SIGNAL (to wake up only one waiting process) or SV_BROADCAST (to wake up all of them).

SV_WAIT is specifically designed to handle the difficult case that arises when the driver needs to initiate an I/O operation and then sleep, and do these things in such a way that it always begins to sleep before the SV_SIGNAL can possibly be issued. The procedure is done as follows:

  1. The driver seizes a basic lock (see "Basic Locks") or a mutex lock (see "Using Mutex Locks")that is also used by the interrupt handler.

    A LOCK() call returns an integer that is needed later.

  2. The driver initiates an I/O operation that can lead to an interrupt.

  3. The driver calls SV_WAIT, passing the lock it holds and an integer, either the value returned by LOCK() or a zero if the lock is a mutex lock.

  4. In one indivisible operation, SV_WAIT releases the lock and begins waiting on the synchronization variable.

  5. The interrupt handler or other process is entered, and seizes the lock.

    This step ensures that, if the interrupt handler or other process is entered preceding the SV_WAIT call, it will not proceed until SV_WAIT has completed.

  6. The interrupt handler or other process does its work and calls SV_SIGNAL to release the waiting driver.
This process is sketched in Example 9-2.

Example 9-2 : Skeleton Code for Use of SV_WAIT

lock_t seize_it;
sv_t wait_on_it;
initiator(...)
{
   int lock_cookie;
   for( as often as necessary )
   {
      lock_cookie = LOCK(&seize_it,PL_ZERO);
      [do something that causes a later interrupt]
      SV_WAIT(&wait_on_it, 0, &seize_it, lock_cookie);
      [interrupt has been handled]
   }
}

void handler(...)
{
   int lock_cookie = LOCK(&seize_it,PL_ZERO);
   [handle the interrupt]
   SV_SIGNAL(&seize_it);
   UNLOCK(&seize_it);
}
If it is necessary to use a semaphore as the lock, the header file sys/sema.h declares versions of SV_WAIT that accept a semaphore and a synchronization variable. The combination of a mutual exclusion object and a synchronization variable ensures that even in a multiprocessor, the interrupt handler cannot exit before the driver has entered a predictable wait state.

Tip: When a debugging kernel is used, you can display statistics about the use of a given synchronization variable. See "Including Lock Metering in the Kernel Image".


Next | Prev | Up | Top | Contents | Index