B.8.3 Customizing the STREAMS Stack for Windows NT

In the UNIX kernel, process synchronization uses the sleep and wakeup functions, in conjunction with flag variables and timers. Since the Kernel is preemptive and a driver cannot wait while holding a spin lock, sleep is difficult to emulate without introducing a timing window between the timeout call and the sleep call.

Since common use of sleep and wakeup within transport stacks effectively emulates Kernel event objects, these functions are not provided for STREAMS. STREAMS stacks must use the appropriate synchronization facility instead. Usually, this facility consists of event objects.

Example: Orderly Release Within a Close Procedure

Consider a STREAMS driver implementing a connection-oriented protocol with orderly release. Suppose the original pseudocode for its close procedure is:

int thing;
egclose(rq, ...)
{
    flag = CLOSING;
    send release indication;
 
    id = timeout(timeout_handler, ...);
    sleep(&thing, ...);
 
    untimeout(id);
    if (flag != CLOSED || sleep was interrupted) {
            recover;
    }
    else {
        finish closing;
    }
}
 
handle_orderly_response()
{
        flag = CLOSED;
        wakeup(&thing);
}
 
timeout_handler()
{
        wakeup(&thing);
}
 

For Windows NT, the STREAMS stack must use a Kernel synchronization event to indicate that the response to the orderly release request has arrived. This object has the desirable property that it remains set until a thread waits on it and then automatically resets itself when the wait has been satisfied.

If several threads must wait on an object, then a Kernel notification event provides nearly the same semantics as sleep and wakeup. All waits for the event are satisfied when it is set to signaled, but it remains in the Signaled state until explicitly reset. The converted pseudocode is:

#include <ntddk.h>
#include <stream.h>
 
KEVENT CloseEvent;
 
egopen(...)
{
    KeInitializeEvent(&CloseEvent, SynchronizationEvent, FALSE);
}
 
egclose(
    IN queue_t *rq,
    ...
)
{
    NTSTATUS status;
    LARGE_INTEGER timeout_value;
 
    timeout_value = StrmConvertCentisecondsToRelativeTimeout(1000);
    send release indication;
 
    status = StrmWaitForSingleObject(
                rq,                 // current queue
                &CloseEvent,        // pointer to the dispatcher object
                UserRequest,        // reason for the wait
                KernelMode,         // processor mode to wait in
                FALSE,              // is this wait alertable?
                &timeout_value);    // timeout period
 
if (status == STATUS_TIMEOUT) {
    recover;
}
else if (!NT_SUCCESS(status)) {
    recover;
}
else {
    finish closing;
}
 
handle_orderly_release_response()
{
    KeSetEvent(&CloseEvent, 0, FALSE);
}
 

The test for STATUS_TIMEOUT after the wait returns is necessary because the NT_SUCCESS macro does not consider STATUS_TIMEOUT an error and will return TRUE. Once again, please remember that a driver absolutely must not wait while holding a spin lock. However, issuing a wait call with a timeout of 0 guarantees that the call will not block and can be made while a spin lock is being held. Under some circumstances while a spin lock is being held, it can be more efficient for the driver to make a 0 timeout call first. Only if this wait fails with a STATUS_TIMEOUT is the driver required to relinquish the spin lock and issue a real wait call.