Your MT SAFE modules should use perimeters and avoid using module private locks. Should you opt to use module private locks, you need to read "MT SAFE Modules Using Explicit Locks" along with this section.
For the purpose of controlling and restricting the concurrency for the synchronous entry points, the STREAMS framework defines two MT perimeters. The STREAMS framework provides the concepts of inner and outer perimeters. A module can be configured either to have no perimeters, to have only an inner or an outer perimeter, or to have both an inner and outer perimeter. For inner perimeters there are different scope perimeters to choose from. Unrestricted concurrency can be obtained by configuring no perimeters.
Both the inner and outer perimeters act as readers/writer locks allowing multiple readers or a single writer. Thus, each perimeter can be entered in two modes: shared (reader) or exclusive (writer). By default, all synchronous entry points enter the inner perimeter exclusively and the outer perimeter shared.
The inner and outer perimeters are entered when one of the synchronous entry points is called. The perimeters are retained until the call returns from the entry point. Thus, for example, the thread does not leave the perimeter of one module when it calls putnext to enter another module.
When a thread is inside a perimeter and it calls putnext(9F) (or putnextctl1(9F)), the thread can "loop around" through other STREAMS modules and try to reenter a put procedure inside the original perimeter. If this reentry conflicts with the earlier entry (for example if the first entry has exclusive access at the inner perimeter), the STREAMS framework defers the reentry while preserving the order of the messages attempting to enter the perimeter. Thus, putnext(9F) returns without the message having been passed to the put procedure and the framework passes the message to the put procedure when it is possible to enter the perimeters.
The optional outer perimeter spans all queues in a module is illustrated in Figure 12-3.
Several flags are used to specify the perimeters. These flags fall into three categories:
Define the presence and scope of the inner perimeter.
Define the presence of the outer perimeter (which can have only one scope).
Modify the default concurrency for the different entry points.
The inner perimeter is controlled by these mutually exclusive flags:
D_MTPERMOD: The module has an inner perimeter that encloses all the module's queues.
D_MTAPAIR: The module has an inner perimeter around each read/write pair of queues.
D_MTPERQ: The module has an inner perimeter around each queue.
None of the above: The module has no inner perimeter.
The presence of the outer perimeter is configured using:
D_MTOUTEPERIM: In addition to any inner perimeter (or none), the module has an outer perimeter that encloses all the module's queues. This can be combined with all the inner perimeter options except D_MTPERMOD.
Recall that by default all synchronous entry points enter the inner perimeter exclusively and enter the outer perimeter shared. This behavior can be modified in two ways:
D_MTOCEXCL: The framework invokes the open and close procedures with exclusive access at the outer perimeter (instead of the default shared access at the outer perimeter.)
D_MTPUTSHARED: The framework invokes the put procedures with shared access at the inner perimeter (instead of the default exclusive access at the inner perimeter).
To configure the driver as being MT SAFE, the cb_ops(9S) and dev_ops(9S) data structures must be initialized. This code must be in the header section of your module. For more information, see Example 12-1, and dev_ops(9S).
The driver is configured to be MT SAFE by setting the cb_flag field to D_MP. It also configures any MT STREAMS perimeters by setting flags in the cb_flag field. (See mt-streams(9F). The corresponding configuration for a module is done using the f_flag field in fmodsw(9S).
The routines qprocson(9F) and qprocsoff(9F)) respectively enable and disable the put and service procedures of the queue pair. Before calling qprocson(9F), and after calling qprocsoff(9F)), the module's put and service procedures are disabled; messages flow around the module as if it were not present in the Stream.
qprocson(9F) must be called by the first open of a module, but only after allocation and initialization of any module resources on which the put and service procedures depend. The qprocsoff routine must be called by the close routine of the module before deallocating any resources on which the put and service procedures depend.
To avoid deadlocks, modules must not hold private locks across the calls to qprocson(9F) or qprocsoff(9F).
The timeout(9F) and bufcall(9F) callbacks are asynchronous. For a module using MT STREAMS perimeters, the timeout(9F) and bufcall(9F) callback functions execute outside the scope of the perimeters. This makes it complex for the callbacks to synchronize with the rest of the module.
To make timeout(9F) and bufcall(9F) functionality easier to use for modules with perimeters, there are additional interfaces that use synchronous callbacks. These routines are qtimeout(9F), quntimeout(9F), qbufcall(9F), and qunbufcall(9F). When using these routines, the callback functions are executed inside the perimeters, hence with the same concurrency restrictions as the put and service procedures.
Modules can use the qwriter(9F) function to upgrade from shared to exclusive access at a perimeter. For example, a module with an outer perimeter can use qwriter(9F) in the put procedure to upgrade to exclusive access at the outer perimeter. A module where the put procedure runs with shared access at the inner perimeter (D_MTPUTSHARED) can use qwriter(9F) in the put procedure to upgrade to exclusive access at the inner perimeter.
Note that qwriter(9F) cannot be used in the open or close procedures. If a module needs exclusive access at the outer perimeter in the open and/or close procedures, it has to specify that the outer perimeter should always be entered exclusively for open and close (using D_MTOCEXCL).
The STREAMS framework guarantees that all deferred qwriter(9F) callbacks associated with a queue have executed before the module's close routine is called for that queue.
A module that uses perimeters and must wait in its open or close procedure for a message from another STREAMS module has to wait outside the perimeters; otherwise, the message would never be allowed to enter its put and service procedures. This is accomplished by using the qwait(9F) interface. See qwriter(9F) man page for an example.
Interrupt handlers and other asynchronous callback functions require special care by the module writer, since they can execute asynchronously to threads executing within the module open, close, put, and service procedures.
For modules using perimeters, use qtimeout(9F) and qbufcall(9F) instead of timeout(9F) and bufcall(9F). The qtimeout and qbufcall callbacks are synchronous and consequently introduce no special synchronization requirements.
Since a thread can enter the module at any time, you must ensure that the asynchronous callback function acquires the proper private locks before accessing private module data structures, and releases these locks before returning. You must cancel any outstanding registered callback routines before the data structures on which the callback routines depend are deallocated and the module closed.
For hardware device interrupts, this involves disabling the device interrupts.
The module cannot hold certain private locks across calls to untimeout(9F) or unbufcall(9F). These locks are those which the module's timeout(9F) or bufcall(9F) callback functions acquire. See "MT SAFE Modules Using Explicit Locks".
Outstanding callbacks from esballoc(9F), if associated with a particular Stream, must be allowed to complete before the module close routine deallocates those private data structures on which they depend.
Since the callback functions are by nature asynchronous, they can be executing or about to execute at the time the module close routine is called. You must cancel all outstanding callback and interrupt conditions before deallocating those data structures or returning from the close routine.
The callback functions scheduled with timeout(9F) and bufcall(9F) are guaranteed to have been canceled by the time untimeout(9F) and unbufcall(9F) return. The same is true for qtimeout(9F) and qbufcall(9F) by the time quntimeout(9F) and qunbufcall(9F) return. You must also take responsibility for other asynchronous routines, including esballoc(9F) callbacks and hardware, as well as software interrupts.
The STREAMS framework prevents a module or driver text from being unloaded while there are open instances of the module or driver. If a module does not cancel all callbacks in the last close routine, it has to refuse to be unloaded.
This is an issue mainly for modules and drivers using esballoc since esballoc callbacks cannot be canceled. Thus modules and drivers using esballoc have to be prepared to handle calls to the esballoc callback free function after the last instance of the module or driver has been closed.
Modules and drivers can maintain a count of outstanding callbacks. They can refuse to be unloaded by having their _fini(9E) routine return EBUSY if there are outstanding callbacks.
The q_next field in the queue_t structure can be referenced in open, close, put, and service procedures as well as the synchronous callback procedures (scheduled with qtimeout(9F), qbufcall(9F), and qwriter(9F)). However, the value in the q_next field should not be trusted. It is relevant to the STREAMS framework, but may not be relevant to a specific module.
All other module code, such as interrupt routines, timeout(9F) and esballoc(9F) callback routines, cannot dereference q_next. Those routines have to use the "next" version of all functions. For instance, use canputnext(9F) instead of dereferencing q_next and using canput(9F).