Multiple Streams are created above a driver/multiplexer by use of the open system call on either different minor devices, or on a cloneable device file. Note that any driver that handles more than one minor device is considered an upper multiplexer.
To connect Streams below a multiplexer requires additional software in the multiplexer. The main difference between STREAMS lower multiplexers and STREAMS device drivers is that multiplexers are pseudo-devices and multiplexers have two additional qinit structures, pointed to by fields in streamtab(9S): the lower half read-side qinit(9S) and the lower half write-side qinit(9S).
The multiplexer is conceptually divided into two parts: the lower half (bottom) and the upper half (top). The multiplexer queue structures that have been allocated when the multiplexer was opened, use the usual qinit entries from the multiplexer's streamtab(9S). This is the same as any open of the STREAMS device. When a lower Stream is linked beneath the multiplexer, the qinit structures at the Stream head are substituted by the bottom half qinit(9S) structures of the multiplexers. Once the linkage is made, the multiplexer switches messages between upper and lower Streams. When messages reach the top of the lower Stream, they are handled by put and service routines specified in the bottom half of the multiplexer.
A lower multiplexer is connected as follows: the initial open to a multiplexing driver creates a Stream, as in any other driver. open uses the first two streamtab structure entries to create the driver queues. At this point, the only distinguishing characteristics of this Stream are non-NULL entries in the streamtab(9S) st_muxrinit and st_muxwinit fields.
These fields are ignored by open. Any other Stream subsequently opened to this driver will have the same streamtab and thereby the same mux fields.
Next, another file is opened to create a (soon-to-be) lower Stream. The driver for the lower Stream is typically a device driver This Stream has no distinguishing characteristics. It can include any driver compatible with the multiplexer. Any modules required on the lower Stream must be pushed onto it now.
Next, this lower Stream is connected below the multiplexing driver with an I_LINK ioctl(2) (see streamio(7I)). The Stream head points to the Stream head routines as its procedures (through its queue). An I_LINK to the upper Stream, referencing the lower Stream, causes STREAMS to modify the contents of the Stream-head's queues in the lower Stream. The pointers to the Stream-head routines, and other values, in the Stream-head's queues are replaced with those contained in the mux fields of the multiplexing driver's streamtab. Changing the Stream-head routines on the lower Stream means that all subsequent messages sent upstream by the lower Stream's driver are, ultimately, passed to the put procedure designated in st_muxrinit, the multiplexing driver. The I_LINK also establishes this upper Stream as the control Stream for this lower Stream. STREAMS remembers the relationship between these two Streams until the upper Stream is closed, or the lower Stream is unlinked.
Finally, the Stream head sends an M_IOCTL message with ioc_cmd set to I_LINK to the multiplexing driver. The M_DATA part of the M_IOCTL contains a linkblk(9S) structure. The multiplexing driver stores information from the linkblk(9S) structure in private storage and returns an M_IOCACK message (acknowledgment). l_index is returned to the process requesting the I_LINK. This value is used later by the process to disconnect the Stream.
An I_LINK is required for each lower Stream connected to the driver. Additional upper Streams can be connected to the multiplexing driver by open calls. Any message type can be sent from a lower Stream to user processes along any of the upper Streams. The upper Streams provide the only interface between the user processes and the multiplexer.
No direct data structure linkage is established for the linked Streams. The read queue's q_next is NULL and the write queue's q_next points to the first entity on the lower Stream. Messages flowing upstream from a lower driver (a device driver or another multiplexer) will enter the multiplexing driver put procedure with l_qbot as the queue value. The multiplexing driver has to route the messages to the appropriate upper (or lower) Stream. Similarly, a message coming downstream from user space on any upper Stream has to be processed and routed, if required, by the driver.
In general, multiplexing drivers should be implemented so that new Streams can be dynamically connected to (and existing Streams disconnected from) the driver without interfering with its ongoing operation. The number of Streams that can be connected to a multiplexer is implementation dependent.
An I_UNLINK ioctl(2) referencing a specific Stream
An I_UNLINK indicating all lower Streams
The last close of the control Stream
As in the link, an unlink sends a linkblk(9S) structure to the driver in an M_IOCTL message. The I_UNLINK call, which unlinks a single Stream, uses the l_index value returned in the I_LINK to specify the lower Stream to be unlinked. The latter two calls must designate a file corresponding to a control Stream, which causes all the lower Streams that were previously linked by this control Stream to be unlinked. However, the driver sees a series of individual unlinks.
If no open references exist for a lower Stream, a subsequent unlink will automatically close the Stream. Otherwise, the lower Stream must be closed by close(2) following the unlink. STREAMS will automatically dismantle all cascaded multiplexers (below other multiplexing Streams) if their controlling Stream is closed. An I_UNLINK leaves lower, cascaded multiplexing Streams intact unless the Stream file descriptor was previously closed.