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

| 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( );
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.

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

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

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

| 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.
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:
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 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.
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.
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.
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.
Dismantling a lower multiplexor is done by disconnecting (unlinking) the lower Streams. Unlinking can be initiated in three ways:
I_UNLINK ioctl references a specific StreamI_UNLINK references all lower StreamsAs 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.
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 |
|---|

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

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.
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 */
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);
}
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 |
|---|
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);
}
}
| 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:
l_qtop The upper write queue from which the
ioctl is coming. It should always equal
q.l_qbot The new lower write queue. It is the former Stream head write queue and is important because
it is where the multiplexor gets and puts its data.l_index A unique (systemwide) identifier for the link. It can be used for routing or during selective
unlinks. Since the example only supports a single link,
l_index is not used.
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 |
|---|
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
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 |
|---|
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
}
}muxlwsrv, the lower (linked) queue write
service procedure, is scheduled as a result of flow control
subsiding downstream (it is back-enabled).
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 |
|---|
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);
}
}
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);
}
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
( ioctlfd0, I_PLINK, fd1)
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.

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

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.

| Home |
|---|
The I_PUNLINK ioctl is used for dismantling the persistent link. Its format is
ioctl(fd0, I_PUNLINK, muxid)
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);
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 |
|---|
The following lists general multiplexor design guidelines:
| Home |
|---|
| Contents | Previous Chapter | Next Chapter | Index | Glossary |