Multithreaded drivers are written and installed so that they can execute on any CPU in a multiprocessor configuration. Two tasks are required:
Critical code is a section of code that must be executed atomically; see ``Critical code section''. On uniprocessing configurations, only an interrupt generated by the hardware can interfere with the atomic execution, so issuing the spl(D3) or spl(D3oddi) functions to prevent interrupts while the code is executing is adequate to protect the critical code.
On multiprocessing systems, interrupts can come from any processor, or two task-level processes executing on different processors may access the same resource, so resources must be protected with schemes that are appropriate for multiprocessor systems.
Single-threaded drivers can run on multiprocessor configurations without being modified; they will always run on the default CPU and may degrade system performance for multiprocessor configurations. Multithreaded drivers execute without modification on a uniprocessor configuration.
The term ``MP-safe'' refers to either a multithreaded driver or a single-threaded driver that has been successfully tested on a multiprocessor configuration.
The term ``MP-aware'' refers to drivers that use a gross-level lock around the entire driver. Such a driver can use single-threaded synchronization primitives but can execute on any CPU in the system. This approach is less apt to cause a performance bottleneck on a multiprocessor configuration than a purely single-threaded driver, although performance is not usually as good as for a fully multithreaded driver. The MDI interface includes functions to implement an MP-aware driver; see mdi_tx_if_init(D3mdi) for the SVR5 version and mditx_register(D3mdi) and mditx_registerMT(D3mdi) for the SCO OpenServer 5 version.
The design of a multithreaded driver requires that you identify threads of execution in the driver; in other words, identifying operations that can execute on different CPUs in a multiprocessing configuration. The driver must be coded to ensure that resources that are shared between different threads of execution are accessed appropriately.
The ``producer/consumer'' model is a clever way to handle a resource that is shared by two separate execution threads without using locks. As long as one thread produces the resource and the other thread consumes the resource and both resources access the resource in the same order, no locks are necessary. Instead, both the producer and consumer track where they are in the list with pointers or indexes, and thus ensure that the consumer does not overrun the producer.
For example, a network card might have a series of transmit buffers that are populated by the driver's transmit thread (consumer) and freed by the driver's transmit cleanup thread (producer). Rather than setting locks on the transmit buffers, the driver can be coded to ensure that the transmit cleanup thread only cleans a buffer after the transmit has completed. The shrk driver that is included in the ndsample package of the HDK illustrates how to use the producer/consumer model for both SCO OpenServer 5 and SVR5 systems.
A DDI multithreaded driver must:
Single-threaded DDI drivers should set the ``cpu'' field of their System(DSP/4dsp) files to 0 to ensure that the driver is bound to CPU 0. In theory, the operating system should handle this, but some single-threaded drivers that run well on uniprocessor configurations cause a double panic when they are initialized on a multiprocessing configuration because somehow they execute on a different CPU. Note that, as the operating system technology evolves, it may not be possible to bind a driver to a CPU.
An ODDI multithreaded driver must:
|bdistributed(D3oddi)||register the task-time level of a block driver as multithreaded|
|cdistributed(D3oddi)||register the task-time level of a character driver as multithreaded|
|get_intr_arg(D3oddi)||get value or argument passed to intr(D2oddi) routine|
|idistributed(D3oddi)||register the interrupt handler of a driver as multithreaded|
|iopolldistributed(D3oddi)||register the xxpoll routine of a driver as multithreaded|
|ismpx(D3oddi)||determine whether this is a multiprocessor configuration|
|ldistributed(D3oddi)||register a line discipline as multithreaded|
|run_ld(D3oddi)||call line discipline from a multithreaded driver|
|scsi_distributed(D3osdi)||register the task-time level of a SCSI driver as multithreaded|
|sdistributed(D3oddi)||register the task-time level of a STREAMS driver as multithreaded|
|Sharegister(D3osdi)||register the interrupt handler of a SCSI driver as multithreaded|
The following functions are supported for support of older hardware. The are not generally required for current hardware.
|all_io(D3oddi)||check if CPUs can access the I/O bus|
|can_doio(D3oddi)||check if the current CPU can access the I/O bus|
|intralloc(D3oddi)||allocate an interprocessor interrupt used to locate the proper xxstart( ) routine for a block driver|