Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24

Chapter 9


STREAMS Multiplexing

9.1 INTRODUCTION

This section describes how STREAMS multiplexing configura

tions are created and also discusses multiplexing drivers. A STREAMS multiplexor is a driver with multiple Streams connected to it. The primary function of the multiplexing driver is to switch messages among the connected Streams. Multiplexor configurations are created at user level by system calls.

STREAMS-related system calls set up the "plumbing," or Stream interconnections, for multiplexing drivers. The subset of these calls that allows a user to connect (and disconnect) Streams below a driver is referred to as the multiplexing facility. This type of connection is referred to as a 1-to-M, or lower, multiplexor configuration. This configuration must always contain a multiplexing driver, which is recognized by STREAMS as having special characteristics.

Multiple Streams can be connected above a driver by open(2) calls. This was done for the loop-around driver and for the driver handling multiple minor devices in STREAMS drivers. There is no difference between the connections to these drivers, only the functions performed by the driver are different. In the multiplexing case, the driver routes data between multiple Streams. In the device driver case, the driver routes data between user processes and associated physical ports. Multiplexing with Streams connected above is referred to as an

N-to-1, or upper, multiplexor. STREAMS does not provide any facilities beyond open(2) and close(2) to connect or disconnect upper Streams for multiplexing purposes.

From the driver's perspective, upper and lower configurations differ only in how they are initially connected to the driver. The implementation requirements are the same: route the data and handle flow control. All multiplexor drivers require special developer-provided software to perform the multiplexing data routing and to handle flow control. STREAMS does not directly support flow control among multiplexed Streams.

M-to-N multiplexing configurations are implemented by using both of the above mechanisms in a driver.

As discussed in Chapter 8, Streams Drivers, the multiple Streams that represent minor devices are actually distinct Streams in which the driver keeps track of each Stream attached to it. The STREAMS subsystem does not recognize any relationship between the Streams. The same is true for STREAMS multiplexors of any configuration. The multiplexed Streams are distinct and the driver must be implemented to do most of the work.

In addition to upper and lower multiplexors, more complex configurations can be created by connecting Streams containing multiplexors to other multiplexor drivers. With such a diversity of needs for multiplexors, it is not possible to provide general-purpose multiplexor drivers. STREAMS provides a general purpose multiplexing facility that allows users to set up the intermodule/driver plumbing to create multiplexor configurations of generally unlimited interconnection.

9.1.1 Building a Multiplexor

This section builds a protocol multiplexor with the multiplexing configuration shown in Figure 9-1. To free users from the need to know about the underlying protocol structure, a user-level daemon process is built to maintain the multiplexing configuration. Users can then access the transport protocol directly by opening the transport protocol (TP) driver device node.

Home


  

An internetworking protocol driver (IP) routes data from a single upper Stream to one of two lower Streams. This driver supports two STREAMS connections beneath it. These connections are for two distinct networks; one for the IEEE 802.3 standard with the 802.3 driver, and the other to the IEEE 802.4 standard with the 802.4 driver. The TP driver multiplexes upper Streams over a single Stream to the IP driver.



Figure 9-1 Protocol Multiplexor

Home


  

The following example shows how this daemon process sets up the protocol multiplexor. The necessary declarations and initialization for the daemon program are shown in Figure 9-2.

#include <fcntl.h>

#include <stropts.h>

main( )

{

int fd_802_4,

fd_802_3,

fd_ip,

fd_tp;

/* daemon-ize this process */

switch (fork( )) {

case 0:

break;

case -1:

perror("fork failed");

exit(2);

default:

exit(0);

}

setsid( );

Figure 9-2 Daemon Program Declarations and Initialization

This multilevel multiplexed Stream configuration is built from the bottom up. Therefore, Figure 9-2 begins by first constructing the Internal Protocol (IP) multiplexor. This multiplexing device driver is treated like any other software driver. It owns a node in the UNIX file system and is opened just like any other STREAMS device driver.

The first step is to open the multiplexing driver and the 802.4 driver, thus creating separate Streams above each driver as shown in Figure 9-3. The Stream to the 802.4 driver may now be connected below the multiplexing IP driver using the I_LINK ioctl call.



Figure 9-3 Before Link

Home


  

The sequence of instructions to this point is:

if ((fd_802_4 = open("/dev/802_4", O_RDWR)) < 0) {

perror("open of /dev/802_4 failed");

exit(1);

}

if ((fd_ip = open("/dev/ip", O_RDWR)) < 0) {

perror("open of /dev/ip failed");

exit(2);

}

/* now link 802.4 to underside of IP */

if (ioctl(fd_ip, I_LINK, fd_802_4) < 0) {

perror("I_LINK ioctl failed");

exit(3);

}

I_LINK takes two file descriptors as arguments. The first file descriptor, fd_ip, must reference the Stream connected to the multiplexing driver, and the second file descriptor, fd_802_4, must reference the Stream to be connected below the multiplexor. Figure 9-4 shows the state of these Streams following the I_LINK call. The complete Stream to the 802.4 driver has been connected below the IP driver. The Stream head's queues of the 802.4 driver is used by the IP driver to manage the lower half of the multiplexor.



Figure 9-4 Multiplexor after First Link

Home


  

I_LINK returns an integer value, called muxid, which is used by the multiplexing driver to identify the Stream just connected below it. This muxid is ignored in the example, but is useful for dismantling a multiplexor or routing data through the multiplexor. Its significance is discussed later.

The following sequence of system calls is used to continue building the internetworking protocol multiplexor (IP):

if ((fd_802_3 = open("/dev/802_3", O_RDWR)) < 0) {

perror("open of /dev/802_3 failed");

exit(4);

}

if (ioctl(fd_ip, I_LINK, fd_802_3) < 0) {

perror("I_LINK ioctl failed");

exit(5);

}

All links below the IP driver have now been established, giving the configuration in Figure 9-5.



Figure 9-5 IP Multiplexor

The Stream above the multiplexing driver used to establish the lower connections is the controlling Stream and has special significance when dismantling the multiplexing configuration. This will be illustrated later in this section. The Stream referenced by fd_ip is the controlling Stream for the IP multiplexor.

NOTE

The order in which the Streams in the multiplexing configuration are opened is unimportant. If it is necessary to have intermediate modules in the Stream between the IP driver and media drivers, these modules must be added to the Streams associated with the media drivers (using I_PUSH) before the media drivers are attached below the multiplexor.

Home


  

The number of Streams that can be linked to a multiplexor is restricted by the design of the particular multiplexor. The manual page describing each driver describes such restrictions. However, only one I_LINK operation is allowed for each lower Stream; a single Stream cannot be linked below two multiplexors simultaneously.

Continuing with the example, the IP driver is now linked below the transport protocol (TP) multiplexing driver. As seen in Figure 9-5, only one link is supported below the transport driver. This link is formed by the following sequence of system calls:

if ((fd_tp = open("/dev/tp", O_RDWR)) < 0) {

perror("open of /dev/tp failed");

exit(6);

}

if (ioctl(fd_tp, I_LINK, fd_ip) < 0) {

perror("I_LINK ioctl failed");

exit(7);

}

The multilevel multiplexing configuration shown in Figure 9-6 has now been created.



Figure 9-6 TP Multiplexor

Home


  

Because the controlling Stream of the IP multiplexor has been linked below the TP multiplexor, the controlling Stream for the new multilevel multiplexor configuration is the Stream above the TP multiplexor.

At this point, the file descriptors associated with the lower drivers can be closed without affecting the operation of the multiplexor. If these file descriptors are not closed, all later read, write, ioctl, poll, getmsg and putmsg (or getmsg(2) and putmsg(2)) system calls issued to them will fail because I_LINK associates the Stream head of each linked Stream with the multiplexor, so the user may not access that Stream directly for the duration of the link.

The following sequence of system calls completes the daemon example:

close(fd_802_4);

close(fd_802_3);

close(fd_ip);

/* Hold multiplexor open forever */

pause();

}

The transport driver supports several simultaneous Streams. These Streams are multiplexed over the single Stream connected to the IP multiplexor. The mechanism for establishing multiple Streams above the transport multiplexor 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 are connected to that driver. Of course, 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 because the passing of ioctls through the multiplexor is determined by the multiplexing driver and generally cannot be relied on.

9.1.2 Dismantling a Multiplexor

Streams connected to a multiplexing driver from above with open, can be dismantled by closing each Stream with close. The mechanism for dismantling Streams that have been linked below a multiplexing driver is less obvious, and is described below.

The I_UNLINK ioctl call disconnects each multiplexor link below a multiplexing driver individually. This command has the form:

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 multiplexor. Each lower driver may be disconnected individually in this way, or a special muxid value of -1 may disconnect all drivers from the multiplexor simultaneously.

Home


  

In the multiplexing daemon program shown in Figure 9-1, the multiplexor is never explicitly dismantled because all links associated with a multiplexing driver are automatically dismantled when the controlling Stream associated with that multiplexor is closed. Because the controlling Stream is open to a driver, only the final call of close for that Stream closes it. In this example, the daemon is the only process that opens the controlling Stream, so the multiplexing configuration is dismantled when the daemon exits.

For the automatic dismantling mechanism to work in the multilevel, multiplexed Stream configuration, the controlling Stream for each multiplexor at each level must be linked under the next higher level multiplexor. In the example, the controlling Stream for the IP driver was linked under the TP driver, which 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 it is not possible for a process to build a multiplexing configuration with I_LINK and then exit. This is because 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 multiplexor. That is the motivation for implementing the example as a daemon process.

However, if the process uses persistent links with the I_PLINK ioctl call, the multiplexor configuration remains intact after the process exits. Persistent links are described later in this chapter.

9.1.3 Routing Data through a Multiplexor

STREAMS provides a mechanism for building multiplexed Stream configurations. However, the criteria on which a multiplexor routes data is driver-dependent. For example, the protocol multiplexor shown before might use address information found in a protocol header to determine over which subnetwork data should be routed. It is the multiplexing driver's responsibility to define its routing criteria.

One routing option available to the multiplexor is to use the muxid value to determine to which Stream data should be routed (remember that each multiplexor link is associated with 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 must accompany data routed through it. For example, if a multiplexor routed data from a single upper Stream to one of several lower Streams (as did the IP driver), the multiplexor could 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 could then match the muxid in each message with the muxid of each lower Stream, and route the data accordingly.

9.2 CONNECTING/DISCONNECTING LOWER STREAMS

Multiple Streams are created above a driver/multiplexor with the open system call on either different minor devices, or on a clonable device file. Note that any driver that handles more than one minor device is considered an upper multiplexor.

To connect Streams below a multiplexor requires additional software within the multiplexor. The main difference between STREAMS lower multiplexors and STREAMS device drivers are that multiplexors are pseudo-devices and that multiplexors have two additional qinit structures, pointed to by fields in the streamtab structure: the lower half read-side qinit and the lower half write-side qinit.

Home


  

The multiplexor is conceptually divided into two parts: the lower half (bottom) and the upper half (top). The multiplexor queue structures that have been allocated when the multiplexor was opened use the usual qinit entries from the multiplexor's streamtab. This is the same as any open of the STREAMS device. When a lower Stream is linked beneath the multiplexor, the qinit structures at the Stream head are substituted by the bottom half qinit structures of the multiplexors. Once the linkage is made, the multiplexor 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 multiplexor.

9.2.1 Connecting Lower Streams

A lower multiplexor 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 characteristic of this Stream are non-NULL entries in the streamtab st_muxrinit and st_muxwinit fields. These fields are ignored by open (see the rightmost Stream in Figure 9-7). 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 (see the leftmost Stream in Figure 9-7). This Stream has no distinguishing characteristics. It can include any driver compatible with the multiplexor. 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 call [see streamio(7)]. The Stream head points to the Stream head routines as its procedures (known by 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, eventually, 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 structure. The multiplexing driver stores information from the linkblk structure in private storage and returns an M_IOCACK message (acknowledgment). l_index is returned to the process requesting the I_LINK. This value can be used later by the process to disconnect this 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 multiplexor.

Note that 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 multiplexor) enters 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.

Home


  

Also note that the lower Stream (see the headers and file descriptors) is no longer accessible from user space. This causes all system calls to the lower Stream to return EINVAL, except for close. This is why all modules have to be in place before the lower Stream is linked to the multiplexing driver.

Finally, note that the absence of direct linkage between the upper and lower Streams means that STREAMS flow control has to be handled by special code in the multiplexing driver. The flow control mechanism cannot see across 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 multiplexor is developer-dependent.

9.2.2 Disconnecting Lower Streams

Dismantling a lower multiplexor is done by disconnecting (unlinking) the lower Streams. Unlinking can be initiated in three ways:

As in the link, an unlink sends a linkblk structure to the driver in an M_IOCTL message. In the first bulleted item, I_UNLINK uses the l_index value returned in the I_LINK to specify the lower Stream to be unlinked. In the second and third bulleted items, the 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. The driver sees a series of individual unlinks.

If no open references exist for a lower Stream, a subsequent unlink automatically closes the Stream. Otherwise, the lower Stream must be closed by close following the unlink. STREAMS automatically dismantles all cascaded multiplexors (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.

9.3 MULTIPLEXOR CONSTRUCTION EXAMPLE

This section describes an example of multiplexor construction and usage. Figure 9-7 shows the Streams before their connection to create the multiplexing configuration of Figure 9-8. Multiple upper and lower Streams interface to the multiplexor driver. The user processes of Figure 9-6 are not shown in Figure 9-7.

Home


  



Figure 9-7 Internet Multiplexor before Connecting

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

Figure 9-7 depicts only a portion of the full, larger Stream. In the dotted rectangle above the IP multiplexor, generally there is an upper transport control protocol (TCP) multiplexor, additional modules and, possibly, additional multiplexors in the Stream. Multiplexors can also be cascaded below the IP driver if the device drivers are replaced by multiplexor drivers.

Home


  



Figure 9-8 Internet Multiplexor after Connecting

Streams A, B, and C are opened by the process, and modules are pushed as needed. Two upper Streams are opened to the IP multiplexor. 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 multiplexor for supervisory functions. It is the control Stream, leading to a process that 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 multiplexors 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 multiplexor Stream with all its connected links can be connected below another multiplexor driver.

Figure 9-8 shows that the file descriptors for the lower device driver Streams are left dangling. The primary purpose in creating these Streams is to provide parts for the multiplexor. Those not used for control and not required for error recovery (by reconnecting through an I_UNLINK ioctl) have no further function. These lower Streams can be closed to free the file descriptor without affecting the multiplexor.

9.4 MULTIPLEXING DRIVER

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 example 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.

Home


  

Each upper Stream is created by open(2). A single lower Stream is opened and then linked by 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 multiplexor.

As in the loop-around driver (in Chapter 8, Streams Drivers, flow control requires the use of standard and special code, since 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.

The multiplexor declarations are shown in Figure 9-9.

#include <sys/types.h>

#ifdef MT

#include "sys/mplock.h"

#endif

#include <sys/param.h>

#include <sys/sysmacros.h>

#include <sys/stream.h>

#include <sys/stropts.h>

#include <sys/errno.h>

static int muxopen(queue_t *, dev_t *, int, int, void *);

static int muxclose(queue_t *, int, void *);

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 */

#ifdef MT

mask_t *lck; /* lock to protect mux struct */

int flag; /* used to coordinate muxlrput with muxclose*/

#endif

}

#ifdef MT

/* flag bits */

#define BUSY 0x1

#define CLOSING 0x2

#endif

extern struct mux mux_mux[];

extern mask_t mux_lck[];

extern int mux_cnt;

#ifdef MT

int muxdevflag = D_MP;

#else

int muxdevflag = 0;

#endif

#ifdef MT

extern mask_t muxtmp[];

mask_t mux_lck;

cond_t muxsv;

int muxbot_ref; /* prevents unlinks while putnext in progress */

simple_t muxsvlck;

#endif

queue_t *muxbot; /* linked lower queue */

int muxerr; /* set if error or hangup on lower stream */

Figure 9-9 Multiplexor Declarations

The four streamtab entries correspond to the upper read, upper write, lower read, and lower write qinit structures. The multiplexing qinit structures replace those in each lower Stream head after the I_LINK has completed successfully. In a multiplexing configuration, the processing performed by the multiplexing driver can be partitioned between the upper and lower queues. There must be an upper Stream write put procedure and lower Stream read put procedure. If the queue procedures of the opposite upper/lower queue are not needed, the queue can be skipped over, and the message put to the following queue.

In the example, the upper read-side procedures are not used. The lower Stream read queue put procedure transfers the message directly to the read queue upstream from the multiplexor. There is no lower write put procedure because the upper write put procedure directly feeds the lower write queue downstream from the multiplexor.

The driver uses a private data structure, mux. mux_mux[dev] points back to the opened upper read queue. This is used to route messages coming upstream from the driver to the appropriate upper queue. It is also used to find a free major/minor device for a CLONEOPEN driver open case.

Home


  

The upper queue open contains the canonical driver open code as shown in Figure 9-10.

#ifdef MT

void muxinit(void)

{

register struct mux *mux;

mask_t*mlp;

mask_init (&muxlck, muxtmp);

cond_init (&muxsv,&muxsvlck);

for (mux = mux_mux,mlp = mux_lck; mux< &mux_mux[mux_cnt]; mux++, mlp++){

lock_init(mlp,muxtmp);

mux->lck=mlp;

}

}

#endif

static int muxopen(queue_t *q, dev_t *devp, int flag, int sflag,

void *credp)

{

struct mux *mux;

dev_t device;

#ifdef MT

int pl;

#endif

if (q->q_ptr)

return(EBUSY);

if (sflag == CLONEOPEN) {

for (device = 0; device < mux_cnt; device++) {

#ifdef MT

if (mux_mux[device].lck == NULL)

continue;

mask_lock(mux_mux[device].lck);

#endif

if (mux_mux[device].qptr == NULL)

break;

/* Note that we break out of if statement */

/* with the correct lock held */

if (device >= mux_cnt){

#ifdef MT

mask_lock(&mux_mux[device].lck);

#endif

return(ENXIO);

}

}

}

else {

device = getminor(*devp);

if (device < 0 || device >= mux_cnt)

return(ENXIO);

#ifdef MT

if (mux_mux[device].lck == NULL){

mask_lock(mux_mux[device].lck,);

return (EXNIO);

#endif

}

}

#ifdef MT

/*

* Once we get here, the device is valid and we're holding its lock.

*/

#endif

mux = &mux_mux[device];

mux->qptr = q;

mux->flag = 0;

q->q_ptr = (char *) mux;

WR(q)->q_ptr = (char *) mux;

#ifdef MT

mask_unlock(mux->lck);

qprocson(q);

#endif

return(0);

}

Figure 9-10 Canonical Driver Open Code

muxopen checks for a clone or ordinary open call. It initializes q_ptr to point at the mux_mux[] structure.

The core multiplexor processing is the following: downstream data written to an upper Stream is queued on the corresponding upper write message queue if the lower Stream is flow controlled. This allows flow control to propagate towards the Stream head for each upper Stream. A lower write service procedure, rather than a write put procedure, is used so that flow control, coming up from the driver below, may be handled.

On the lower read-side, data coming up the lower Stream are passed to the lower read put procedure. The procedure routes the data to an upper Stream based on the first byte of the message. This byte holds the minor device number of an upper Stream. The put procedure handles flow control by testing the upper Stream at the first upper read queue beyond the driver. The put procedure treats the Stream component above the driver as the next queue.

Home


  

9.4.1 Upper Write put Procedure

muxuwput, the upper queue write put procedure, traps ioctls, in particular I_LINK and I_UNLINK shown in Figure 9-11.

static int muxuwput(queue_t *q, mblk_t *mp)

{

#ifdef MT

int pl;

#endif

struct mux *mux;

mux = (struct mux *) q->q_ptr;

switch (mp->b_datap->db_type) {

case M_IOCTL: {

struct iocblk *iocp;

struct linkblk *linkp;

/*

* ioctl. Only channel 0 can do ioctls. Two calls are

* recognized: LINK, and UNLINK

*/

if (mux != mux_mux)

goto iocnak;

iocp = (struct iocblk *) mp->b_rptr;

switch (iocp->ioc_cmd) {

case I_LINK:

/*

* Link. The data contains a linkblk structure

* Remember the bottom queue in muxbot.

*/

#ifdef MT

mask_lock(muxlck);

#endif

if (muxbot != NULL) {

#ifdef MT

mask_unlock(muxlck);

#endif

goto iocnak;

}

linkp = (struct linkblk *) mp->b_cont->b_rptr;

muxbot = linkp->l_qbot;

muxerr = 0;

#ifdef MT

muxbot_ref = 0;

mask_unlock(muxlck);

#endif

mp->b_datap->db_type = M_IOCACK;

iocp->ioc_count = 0;

qreply(q, mp);

break;

case I_UNLINK:

/*

* Unlink. The data contains a linkblk structure.

#ifdef MT

* If muxbot is busy, fail unlink.

#endif

*/

linkp = (struct linkblk *) mp->b_cont->b_rptr;

#ifdef MT

mask_lock(muxlck);

if (muxbot_ref) {

mp->b_datap->db_type = M_IOCNAK;

iocp->ioc_error = EAGAIN;

} else {

muxbot = NULL;

} mp->b_datap->db_type = M_IOCACK;

mask_unlock(muxlck);

#else

muxbot = NULL;

mp->b_datap->db_type = M_IOCACK;

#endif

iocp->ioc_count = 0;

qreply(q, mp);

break;

default:

iocnak:

/* fail ioctl */

mp->b_datap->db_type = M_IOCNAK;

qreply(q, mp);

}

break;

}

case M_FLUSH:

if (*mp->b_rptr & FLUSHW)

flushq(q, FLUSHDATA);

if (*mp->b_rptr & FLUSHR) {

*mp->b_rptr &= ~FLUSHW;

qreply(q, mp);

} else

freemsg(mp);

break;

case M_DATA:

/*

* Data. If we have no bottom queue > fail

* Otherwise, queue the data and invoke the lower

* service procedure.

*/

#ifdef MT

mask_lock(muxlck);

#endif

if (muxerr || muxbot == NULL) {

#ifdef MT

mask_unlock(muxlck);

#endif

goto bad;

}

#ifdef MT

if (canputnext(muxbot)) {

#else

if (canput(muxbot->q_next)) {

#endif

mblk_t *bp;

if ((bp = allocb(1, BPRI_MED)) == NULL) {

#ifdef MT

mask_unlock(muxlck);

#endif

putq(q, mp);

bufcall(1, BPRI_MED, qenable, q);

break;

}

#ifdef MT

muxbot_ref = 1;

mask_unlock(muxlck);

#endif

*bp->b_wptr++ = (struct mux *) q->q_ptr - mux_mux;

bp->b_cont = mp;

putnext(muxbot, bp);

#ifdef MT

mask_lock(muxlck);

muxbot_ref = 0;

mask_unlock(muxlck);

#endif

} else {

#ifdef MT

mask_unlock(muxlck);

#endif

putq(q, mp);

}

break;

default:

bad:

/*

* Send an error message upstream.

*/

mp->b_datap->db_type = M_ERROR;

mp->b_rptr = mp->b_wptr = mp->b_datap->db_base;

*mp->b_wptr++ = EINVAL;

qreply(q, mp);

}

}

Figure 9-11 Upper Write put Procedure

Home


  

First, there is a check to enforce that the Stream associated with minor device 0 will be the single, controlling Stream. The ioctls are only accepted on this Stream. As described previously, a controlling Stream is the one that issues the I_LINK. Having a single control Stream is a recommended practice. I_LINK and I_UNLINK include a linkblk structure containing:

For I_LINK, l_qbot is saved in muxbot and a positive acknowledgment is generated. From this point on, until an I_UNLINK occurs, data from upper queues will be routed through muxbot. Note that when an I_LINK is received, the lower Stream has already been connected. This allows the driver to send messages downstream to perform any initialization functions. Returning an M_IOCNAK message (negative acknowledgment) in response to an I_LINK will cause the lower Stream to be disconnected.

The I_UNLINK handling code nulls out muxbot and generates a positive acknowledgment. A negative acknowledgment should not be returned to an I_UNLINK. The Stream head assures that the lower Stream is connected to a multiplexor before sending an I_UNLINK M_IOCTL.

muxuwput handles M_FLUSH messages as a normal driver would, except that there are no messages enqueued on the upper read queue, so there is no need to call flushq if FLUSHR is set. M_DATA messages are not placed on the lower write message queue. They are queued on the upper write message queue. When flow control subsides on the lower Stream, the lower service procedure, muxlwsrv, is scheduled to start output. This is similar to starting output on a device driver.

Home


  

9.4.2 Upper Write service Procedure

Figure 9-12 shows the code for the upper multiplexor write service procedure.

#ifdef MT

static int muxuwsrv(queue_t *q)

{

struct mux *muxp;

mblk_t *mp;

int pl;

muxp = (struct mux *) q->q_ptr;

while (mp = getq(q)) {

mask_lock(muxlck);

if (!muxbot) {

mask_unlock(muxlck);

flushq(q, FLUSHALL);

return;

}

if (muxerr) {

mask_unlock(muxlck);

flushq(q, FLUSHALL);

return;

}

if (canputnext(muxbot)) {

muxbot_ref = 1;

mask_unlock(muxlck);

putnext(muxbot, mp);

mask_lock(muxlck);

muxbot_ref = 0;

mask_unlock(muxlck);

} else {

mask_unlock(muxlck);

putbq(q, mp);

return(0);

}

}

}

#else

static int muxuwsrv(queue_t *q)

{

struct mux *muxp;

mblk_t *mp;

muxp = (struct mux *) q->q_ptr;

if (!muxbot) {

flushq(q, FLUSHALL);

return;

}

if (muxerr) {

flushq(q, FLUSHALL);

return;

} while (mp = getq(q)) {

if (canput(muxbot->q_next)) {

putnext(muxbot, mp);

} else {

putbq(q, mp);

return(0);

}

}

}

#endif

Figure 9-12 Upper Write service Procedure

As long as there is a Stream still linked under the multiplexor and there are no errors, the service procedure takes a message off the queue and sends it downstream, if flow control allows.

Home


  

9.4.3 Lower Write service Procedure

muxlwsrv, the lower (linked) queue write service procedure, is scheduled as a result of flow control subsiding downstream (it is back-enabled).

static int muxlwsrv(queue_t *q)

{

register int i;

#ifdef MT

int pl;

#endif

for (i = 0; i < mux_cnt; i++) {

#ifdef MT

mask_lock(mux_mux[i].lck);

if (mux_mux[i].qptr)

qenable(mux_mux[i].qptr);

mask_unlock(mux_mux[i].lck);

#else

if (mux_mux[i].qptr && mux[i].qptr->q_first)

qenable(mux_mux[i].qptr);

#endif

}

}

Figure 9-13 Lower Write service Procedure

muxlwsrv steps through all possible upper queues. If a queue is active and there are messages on the queue, then the upper write service procedure is enabled by qenable.

Home


  

9.4.4 Lower Read put Procedure

The lower (linked) queue read put procedure is shown in Figure 9-14.

static int muxlrput(queue_t *q, mblk_t *mp)

{

queue_t *uq;

mblk_t *b_cont;

int device;

register struct mux *mux;

#ifdef MT

int pl;

mask_lock(muxlck);

#endif

if (muxerr) {

freemsg(mp);

#ifdef MT

mask_unlock(muxlck);

#endif

return(0);

}

#ifdef MT

mask_unlock(muxlck);

#endif

switch (mp->b_datap->db_type) {

case M_FLUSH:

/*

* Flush queues. NOTE: sense of tests is reversed since

* we are acting like a "stream head"

*/

if (*mp->b_rptr & FLUSHW) {

*mp->b_rptr &= ~FLUSHR;

qreply(q, mp);

} else

freemsg(mp);

break;

case M_ERROR:

case M_HANGUP:

#ifdef MT

mask_lock(muxlck);

#endif

muxerr = 1;

#ifdef MT

mask_unlock(muxlck);

#endif

freemsg(mp);

break;

case M_DATA:

/*

* Route message. First byte indicates device to send to.

* No flow control.

*

* Extract and delete device number. If the leading block is

* now empty and more blocks follow, strip the leading block.

*/

device = *mp->b_rptr++;

/* Sanity check. Device must be in range */

if (device < 0 || device >= mux_cnt) {

freemsg(mp);

break;

}

/*

* If upper stream is open and not backed up, send the

* message there, otherwise discard it.

*/

mux = &mux_mux[device];

#ifdef MT

mask_lock(mux->lck);

#endif

uq = mux->qptr;

#ifdef MT

if (uq != NULL && canputnext(uq)) {

mux->flag |= BUSY;

mask_unlock(mux->lck);

#else

if (uq != NULL && canput(uq->q_next)) {

#endif

putnext(uq, mp);

#ifdef MT

mask_lock(mux->lck);

mux->flag &= ~BUSY;

if (mux->flag & CLOSING)

cond_signal(muxsv);

#endif

} else

freemsg(mp);

#ifdef MT

mask_unlock(mux->lck);

#endif

break;

default:

freemsg(mp);

}

}

Figure 9-14 Lower Read put Procedure

muxlrput receives messages from the Stream linked below the multiplexor. Here, it needs to act as the Stream head of the lower stream. This means that during M_FLUSH handling, the sense of the tests are reversed. If FLUSHW is set, then FLUSHR is turned off and the message is sent back downstream. Otherwise, the message is freed. No flushing is necessary in this example because no messages are enqueued on the lower queues of the multiplexor.

muxlrput also handles M_ERROR and M_HANGUP messages. If one is received, it locks up the upper Streams by setting muxerr.

M_DATA messages are routed by looking at the first data byte of the message. This byte contains the minor device of the upper Stream. Several sanity checks are made to see if the device is in range and the upper Stream is open and not full.

This multiplexor does not support flow control on the read-side. It is merely a router. If everything checks out, the message is put to the proper upper queue. Otherwise, the message is discarded.

Home


  

The upper Stream close routine simply clears the mux entry so this queue will no longer be found. See Figure 9-15.

/*

* Upper queue close

*/

static int muxclose(queue_t *q, int flag, void *credp)

{

register struct mux *mux;

#ifdef MT

int pl;

#endif

mux = (struct mux *) q->q_ptr;

#ifdef MT

qprocsoff(q);

mask_lock(mux->lck);

/*

* coordinate with muxlwrput. Use a global sync. variable since this

* case is unlikely and not worth the overhead of having 1 per

* minor.

*/

while (mux->flag & BUSY) {

mux->flag |= CLOSING;

/* don't allow signals - this should be a short wait */

cond_wait(muxsv, primed, PZERO-5);

mux->flag &= ~CLOSING;

}

#endif

mux->qptr = NULL;

#ifdef MT

mask_unlock(mux->lck);

#endif

q->q_ptr = NULL;

WR(q)->q_ptr = NULL;

return(0);

}

Figure 9-15 Clean Upper Queue

9.5 PERSISTENT LINKS

With I_LINK and I_UNLINK ioctls, the file descriptor associated with the Stream above the multiplexor used to set up the lower multiplexor connections must remain open for the duration of the configuration. Closing the file descriptor associated with the controlling Stream dismantles the whole multiplexing configuration. Some applications may not want to keep a process running merely to hold the multiplexor configuration together. Therefore, "free-standing" links below a multiplexor are needed. A persistent link is such a link. It is similar to a STREAMS multiplexor link, except that a process is not needed to hold the links together. After the multiplexor has been set up, the process may close all file descriptors and exit, and the multiplexor remains intact.

Two ioctls, I_PLINK and I_PUNLINK, are used to create and remove persistent links associated with the Stream above the multiplexor. close(2) and I_UNLINK are not able to disconnect the persistent links.

Home


  

The format of I_PLINK is

The first file descriptor, fd0, must reference the Stream connected to the multiplexing driver and the second file descriptor, fd1, must reference the Stream to be connected below the multiplexor. The persistent link can be created in the following way:

upper_stream_fd = open("/dev/mux", O_RDWR);

lower_stream_fd = open("/dev/driver", O_RDWR);

muxid = ioctl(upper_stream_fd, I_PLINK, lower_stream_fd);

/*

* save muxid in a file

*/

exit(0);

Figure 9-16 shows how open(2) establishes a Stream between the device and the Stream head.



Figure 9-16 open() of MUXdriver and Driver1

The persistent link can still exist even if the file descriptor associated with the upper Stream to the multiplexing driver is closed. The I_PLINK ioctl returns an integer value, muxid, that can be used for dismantling the multiplexing configuration. If the process that created the persistent link still exists, it may pass the muxid value to some other process to dismantle the link, if the dismantling is desired, or it can leave the muxid value in a file so that other processes may find it later. Figure 9-17 shows a multiplexor after I_PLINK.

Home


  



Figure 9-17 Multiplexor after I_PLINK

Several users can open the MUXdriver and send data to Driver1 since the persistent link to Driver1 remains intact, as shown in Figure 9-18.



Figure 9-18 Other Users Opening a MUXdriver

Home


  

The I_PUNLINK ioctl is used for dismantling the persistent link. Its format is

where the fd0 is the file descriptor associated with Stream connected to the multiplexing driver from above. The muxid is returned by the I_PLINK ioctl for the Stream that was connected below the multiplexor. The I_PUNLINK removes the persistent link between the multiplexor referenced by the fd0 and the Stream to the driver designated by the muxid. Each of the bottom persistent links can be disconnected individually. An I_PUNLINK ioctl with the muxid value of MUXID_ALL removes all persistent links below the multiplexing driver referenced by fd0.

Figure 9-19 dismantles the previously given configuration.

fd = open("/dev/mux", O_RDWR);

/*

* retrieve muxid from the file

*/

ioctl(fd, I_PUNLINK, muxid);

exit(0);

Figure 9-19 Retrieving the MUX ID from the File

The use of the ioctls I_PLINK and I_PUNLINK should not be intermixed with I_LINK and I_UNLINK. Any attempt to unlink a regular link with I_PUNLINK or to unlink a persistent link with I_UNLINK ioctl causes the errno value of EINVAL to be returned.

Because multilevel multiplexing configurations are allowed in STREAMS, it is possible to have a situation where persistent links exist below a multiplexor whose Stream is connected to the above multiplexor by regular links. Closing the file descriptor associated with the controlling Stream removes the regular link but not the persistent links below it. On the other hand, regular links are allowed to exist below a multiplexor whose Stream is connected to the above multiplexor with persistent links. In this example, the regular links are removed if the persistent link above is removed and no other references to the lower Streams exist.

The construction of cycles is not allowed when creating links. A cycle could be constructed by creating a persistent link of multiplexor 2 below multiplexor 1 and then closing the controlling file descriptor associated with the multiplexor 2 and reopening it again, and then linking the multiplexor 1 below the multiplexor 2, but this is not allowed. The operating system prevents a multiplexor configuration from containing a cycle to ensure that messages cannot be routed infinitely, thus creating an infinite loop or overflowing the kernel stack.

Home


  

9.6 DESIGN GUIDELINES

The following lists general multiplexor design guidelines:

Home

Contents Previous Chapter Next Chapter Index Glossary