Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
13.  STREAMS Multiplex Drivers STREAMS Multiplexers Building a Multiplexer  Previous   Contents   Next 
   
 

The transport driver supports several simultaneous streams. These streams are multiplexed over the single stream connected to the IP multiplexer. The mechanism for establishing multiple streams above the transport multiplexer is actually a by-product of the way in which streams are created between a user process and a driver. By opening different minor devices of a STREAMS driver, separate streams will be connected to that driver. The driver must be designed with the intelligence to route data from the single lower stream to the appropriate upper stream.

The daemon process maintains the multiplexed stream configuration through an open stream (the controlling stream) to the transport driver. Meanwhile, other users can access the services of the transport protocol by opening new streams to the transport driver; they are freed from the need for any unnecessary knowledge of the underlying protocol configurations and subnetworks that support the transport service.

Multilevel multiplexing configurations should be assembled from the bottom up. That is because the passing of ioctl(2) through the multiplexer is determined by the nature of the multiplexing driver and cannot generally be relied on.

Dismantling a Multiplexer

Streams connected to a multiplexing driver from above with open(2), can be dismantled by closing each stream with close(2). The mechanism for dismantling streams that have been linked below a multiplexing driver is less obvious, and is described in "Disconnecting Lower Streams".

I_UNLINK ioctl(2) disconnects each multiplexer link below a multiplexing driver individually. This command has the form:

ioctl(fd, I_UNLINK, muxid);

where fd is a file descriptor associated with a stream connected to the multiplexing driver from above, and muxid is the identifier that was returned by I_LINK when a driver was linked below the multiplexer. Each lower driver may be disconnected individually in this way, or a special muxid value of MUXID_ALL can be used to disconnect all drivers from the multiplexer simultaneously.

In the multiplexing daemon program, the multiplexer is never explicitly dismantled. That is because all links associated with a multiplexing driver are automatically dismantled when the controlling stream associated with that multiplexer is closed. Because the controlling stream is open to a driver, only the final call of close for that stream will close it. In this case, the daemon is the only process that has opened the controlling stream, so the multiplexing configuration will be dismantled when the daemon exits.

For the automatic dismantling mechanism to work in the multilevel, multiplexed stream configuration, the controlling stream for each multiplexer at each level must be linked under the next higher-level multiplexer. In the example, the controlling stream for the IP driver was linked under the TP driver. This resulted in a single controlling stream for the full, multilevel configuration. Because the multiplexing program relied on closing the controlling stream to dismantle the multiplexed stream configuration instead of using explicit I_UNLINK calls, the muxid values returned by I_LINK could be ignored.

An important side effect of automatic dismantling on the close is that a process cannot build a multiplexing configuration with I_LINK and then exit. exit(2) closes all files associated with the process, including the controlling stream. To keep the configuration intact, the process must exist for the life of that multiplexer. That is the motivation for implementing the multiplexer as a daemon processs, see "Multiplexing Driver Example".

If the process uses persistent links through I_PLINK ioctl(2), the multiplexer configuration remains intact after the process exits. These links are described in "Persistent Links".

Routing Data Through a Multiplexer

As demonstrated, STREAMS provides a mechanism for building multiplexed stream configurations. However, the criteria by which a multiplexer routes data are driver dependent. For example, the protocol multiplexer might use address information found in a protocol header to determine the subnetwork over which data should be routed. You must define its routing criteria.

One routing option available to the multiplexer is to use the muxid value to determine the stream to which data is routed (remember that each multiplexer link has a muxid). I_LINK passes the muxid value to the driver and returns this value to the user. The driver can therefore specify that the muxid value accompany data routed through it. For example, if a multiplexer routed data from a single upper stream to one of several lower streams (as did the IP driver), the multiplexer can require the user to insert the muxid of the desired lower stream into the first four bytes of each message passed to it. The driver can then match the muxid in each message with the muxid of each lower stream, and route the data accordingly.

Connecting And Disconnecting Lower Streams

Multiple streams are created above a driver/multiplexer by use of the open system call on either different minor device, 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 divided into two parts: the lower half and the upper half. The multiplexer queue structures 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 lower half qinit(9S) structures identified in the streamstab for 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.

Connecting Lower Streams

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 st_rdinit and st_wrinit elements of the streamtab structure to initialize 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 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 acknowledgement. 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 the queue represented in l_qbot as the queue_t for the put procedure. 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.


Note - It is the responsibility of the driver to handle routing of messages between the upper and lower streams, or between any lateral stream that is part of the multiplexer. This operation is not handled by the STREAMS framework.


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.

Disconnecting Lower Streams

Dismantling a lower multiplexer is accomplished by disconnecting (unlinking) the lower streams. Unlinking can be initiated in three ways:

  • 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 automatically dismantles 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.

Multiplexer Construction Example

This section describes an example of multiplexer construction and usage. Multiple upper and lower streams interface to the multiplexer driver.

The Ethernet, LAPB, and IEEE 802.2 device drivers terminate links to other nodes. The multiplexer driver is an Internet Protocol (IP) multiplexer that switches data among the various nodes or sends data upstream to users in the system. The net modules typically provide a convergence function that matches the multiplexer driver and device driver interface.

Streams A, B, and C are opened by the process, and modules are pushed as needed. Two upper streams are opened to the IP multiplexer. The rightmost stream represents multiple streams, each connected to a process using the network. The stream second from the right provides a direct path to the multiplexer for supervisory functions. The control stream, leading to a process, sets up and supervises this configuration. It is always directly connected to the IP driver. Although not shown, modules can be pushed on the control stream.

After the streams are opened, the supervisory process typically transfers routing information to the IP drivers (and any other multiplexers above the IP), and initializes the links. As each link becomes operational, its stream is connected below the IP driver. If a more complex multiplexing configuration is required, the IP multiplexer stream with all its connected links can be connected below another multiplexer driver.

Multiplexing Driver Example

This section contains an example of a multiplexing driver that implements an N-to-1 configuration. This configuration might be used for terminal windows, where each transmission to or from the terminal identifies the window. This resembles a typical device driver, with two differences: the device-handling functions are performed by a separate driver, connected as a lower stream, and the device information (that is, relevant user process) is contained in the input data rather than in an interrupt call.

Each upper stream is created by open(2). A single lower stream is opened and then it is linked by use of the multiplexing facility. This lower stream might connect to the TTY driver. The implementation of this example is a foundation for an M-to-N multiplexer.

As in the loop-around driver (Chapter 9, STREAMS Drivers), flow control requires the use of standard and special code because physical connectivity among the streams is broken at the driver. Different approaches are used for flow control on the lower stream, for messages coming upstream from the device driver, and on the upper streams, for messages coming downstream from the user processes.


Note - The code presented here for the multiplexing driver represents a single-threaded, uniprocessor implementation. See Chapter 12, Multithreaded STREAMS for details on multiprocessor and multithreading issues such as locking for data corruption and to prevent race conditions.


Example 13-2 is of multiplexer declarations:


Example 13-2 Multiplexer Declarations

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

static int muxopen (queue_t*, dev_t*, int, int, cred_t*);
static int muxclose (queue_t*, int, cred_t*);
static int muxuwput (queue_t*, mblk_t*);
static int muxlwsrv (queue_t*);
static int muxlrput (queue_t*, mblk_t*);
static int muxuwsrv (queue_t*);

static struct module_info info = {
	0xaabb, "mux", 0, INFPSZ, 512, 128 };

static struct qinit urinit = {							/* upper read */
	NULL, NULL, muxopen, muxclose, NULL, &info, NULL };

static struct qinit uwinit = {							/* upper write */
	muxuwput, muxuwsrv, NULL, NULL, NULL, &info, NULL };

static struct qinit lrinit = { /* lower read */
	muxlrput, NULL, NULL, NULL, NULL, &info, NULL };

static struct qinit lwinit = { /* lower write */
	NULL, muxlwsrv, NULL, NULL, NULL, &info, NULL };

struct streamtab muxinfo = {
	&urinit, &uwinit, &lrinit, &lwinit };

struct mux {
	queue_t *qptr;					/* back pointer to read queue */
	int		bufcid;					/* bufcall return value */
};
extern struct mux mux_mux[];
extern int mux_cnt;     /* max number of muxes */

static queue_t *muxbot; 			/* linked lower queue */
static int muxerr;					/* set if error of hangup on
lower strm */

 
 
 
  Previous   Contents   Next