[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


5    Digital UNIX STREAMS

Digital UNIX provides a STREAMS framework as specified by AT&T's System V, Version 4.0 release of STREAMS. This framework, which provides an alternative to traditional UNIX character input/output (I/O), allows you to implement I/O functions in a modular fashion. Modularly developed I/O functions allow applications to build and reconfigure communications services easily.

Note that STREAMS refers to the entire framework whereas Stream refers to the entity created by an application program with the open system call.

This chapter contains the following information:

This chapter provides detailed information about areas where the Digital UNIX implementation of STREAMS differs from that of AT&T System V, Version 4.0. Where the Digital UNIX implementation does not differ significantly from that of AT&T, it provides pointers to the appropriate AT&T documentation.

Note that this chapter does not explain how to program using the STREAMS framework. For detailed programming information you should refer to the Programmer's Guide: STREAMS.


[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


5.1    Overview of the STREAMS Framework

The STREAMS framework consists of:

Figure 5-1 highlights the STREAMS framework and shows its place in the network programming environment.

Figure 5-1: The STREAMS Framework


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.1.1    A Review of STREAMS Components

To communicate using Digital UNIX STREAMS, an application creates a Stream, which is a full-duplex communication path between a user process and a device driver. The Stream itself is a kernel device and is represented to the application as a character special file. Like any other character special file, the Stream must be opened and otherwise manipulated with system calls.

Every Stream has at least a Stream head at the top and a Stream end at the bottom. Additional modules, which consist of linked pairs of queues, can be inserted between the Stream head and Stream end if they are required for processing the data being passed along the Stream. Data is passed between modules in messages.

This section briefly describes the following STREAMS components:

It also describes messages and their role in the STREAMS framework.

Figure 5-2 illustrates a typical stream. Note that data traveling from the Stream head to the Stream end (STREAMS driver in Figure 5-2) is said to be traveling downstream, or in the write direction. Data traveling from the Stream end to the Stream head is said to be traveling upstream, or in the read direction.

Figure 5-2: Example of a Stream

The Stream head is a set of routines and data structures that provides an interface between user processes and the Streams in the kernel. It is created when your application issues an open system call. The following are the major tasks that the Stream head performs:

  1. Interprets a standard subset of STREAMS system calls, such as write and putmsg.

  2. Translates them from user space into a standard range of STREAMS messages (such as M_PROTO and M_DATA) which consist of both data and control information.

  3. Sends the messages downstream to the next module. Eventually the messages reach the Stream end, or driver.

  4. Receives messages sent upstream from the driver and transforms the STREAMS message from kernel space to a format appropriate to the system call (such as getmsg or read) made by the application. The format varies depending on the system call.

The Stream end is a special form of STREAMS module and can be either a hardware or pseudodevice driver. If a hardware device driver, the Stream end provides communication between the kernel and an external communication device. If a pseudodevice driver, the Stream end is implemented in software and is not related to an external device. Regardless of whether it is a hardware device driver or a pseudodevice driver, the Stream end receives messages sent by the module above it, interprets them, and performs the requested operations. It then returns data and control information to the application by creating a message of the appropriate type which it sends upstream toward the Stream head.

Drivers are like any other STREAMS modules except for the following:

For detailed information on device drivers and device driver routines, see the Writing Device Drivers: Tutorial and the Programmer's Guide: STREAMS.

Modules process data as it passes from the Stream head to the Stream end and back. A Stream can have zero or more modules on it, depending on the amount and type of processing that the data requires. If the driver can perform all of the necessary processing on the data, no additional modules are required.

Modules consist of a pair of queues that contain data and pointers to other structures that define what each module does. One queue handles data moving downstream toward the driver and the other handles data moving upstream toward the Stream head and application. Pointers link each module's downstream and upstream queues to the next module's downstream and upstream queues.

Depending on their processing requirements, applications request that particular modules be pushed onto the Stream. The Stream head assembles the modules requested by the application and then routes the messages through the pipeline of modules.

Information is passed from module to module using messages. Several different types of messages are defined within the STREAMS environment. All message types, however, fall into the following categories:

Normal messages, such as M_DATA and M_IOCTL, are processed in the order that they are received, and are subject to STREAMS flow control and queuing mechanisms. Priority messages are passed along the stream in an expedited manner.

For more information on messages and message data structures, see Section 5.3.2


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2    Application Interface to STREAMS

The application interface to the STREAMS framework allows STREAMS messages to be sent and received by applications. The following sections describe the application interface, including pointers to the STREAMS header files and data types, and descriptions of the STREAMS and STREAMS-related system calls.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.1    Header Files and Data Types

Definitions for the basic STREAMS data types are included in the following header files:

Note

Typically, header file names are enclosed in angle brackets (< >). To obtain the absolute path to the header file, prepend /usr/include/ to the information enclosed in the angle brackets. In the case of <sys/stream.h>, stream.h is located in the /usr/include/sys directory.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2    STREAMS Functions

Your application accesses and manipulates STREAMS kernel resources through the following functions:

This section briefly describes these functions. For detailed information about these functions, see the Digital UNIX reference pages and the Programmer's Guide: STREAMS.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.1    The open Function

Use the open function to open a Stream. The following is the syntax for the open function:

int open (
const char *path ,
int oflag [ ,
mode_t mode ] );

In the preceding statement:

path
Specifies the device pathname supplied to the open function. The device pathnames are located in the /dev/streams directory. To determine which devices are configured on your system issue the following command as root:

/usr/sbin/strsetup -c

oflag
Specifies the type of access, special open processing, type of update, and the initial state of the open file.

mode
Specifies the permissions of the file that open is creating.

See open(2) for more information.

The following example shows how the open function is used:

int fd;
fd = open("/dev/streams/echo", O_RDWR);


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.2    The close Function

Use the close function to close a Stream.

The following is the syntax for the close function:

int close (
int filedes );

In the preceding statement:

filedes
Specifies a valid open file descriptor

See close(2) for more information.

The last close for a stream causes the stream associated with the file descriptor to be dismantled. Dismantling a stream includes popping any modules on the stream and closing the driver.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.3    The read Function

Use the read function to receive the contents of M_DATA messages waiting at the Stream head.

The following is the syntax for the read function:

int read (
int filedes ,
char *buffer ,
unsigned int nbytes );

In the preceding statement:

filedes
Specifies a file descriptor that identifies the file to be read.

*buffer
Points to the buffer to receive the data being read.

nbytes
Specifies the number of bytes that can be read from the file associated with filedes.

See read(2) for more information.

The read function fails on message types other than M_DATA, and errno is set to EBADMSG.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.4    The write Function

Use the write function to create one or more M_DATA messages from the data buffer.

The following is the syntax for the write function:

int write (
int filedes ,
char *buffer .
unsigned int nbytes );

In the preceding statement:

filedes
Specifies a file descriptor that identifies the file to be read.

*buffer
Points to the buffer to receive the data being read.

nbytes
Specifies the number of bytes to write from the file associated with filedes.

See write(2) for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.5    The ioctl Function

Use the ioctl function to perform a variety of control functions on Streams.

The following is the syntax of the ioctl function:

#include <stropts.h>
.
.
.
int ioctl ( filedes , command , arg )
int fildes , command ;

In the preceding statement:

filedes
Specifies an open file descriptor that refers to a Stream.

command
Determines the control function for the Stream head or module to perform. Many of the valid ioctl commands are handled by the Stream head; others are passed downstream to be handled by the modules and driver.

arg
Specifies additional information. The type depends on the command parameter.

See streamio(7) for more information.

The following example shows how the ioctl call is used:

int fd;
fd = open("/dev/streams/echo", O_RDWR, 0);
ioctl(fd,I_PUSH,"pass");


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.6    The mkfifo Function

Use the STREAMS-based mkfifo function to create a unidirectional STREAMS-based file descriptor.

The following is the syntax of the STREAMS-based mkfifo function:

int mkfifo (
const char *path ,
mode_t mode );

In the preceding statement:

path
Specifies the file name supplied to the mkfifo function.

mode
Specifies the type, attributes, and access permissions of the file.

Note

The default version of the mkfifo function in the libc library is not STREAMS-based. To use the STREAMS version of the mkfifo function the application must link with the sys5 library. See the mkfifo(2) reference page for more information. Also note that the mkfifo function requires that the File on File Mount File System (FFM_FS) kernel option is configured. See the System Administration manual for information about configuring kernel options.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.7    The pipe Function

Use the STREAMS-based pipe function to create a bidirectional, STREAMS-based, communication channel. Non-STREAMS pipes and STREAMS-based pipes differ in the following ways:

The following is the syntax of the pipe function:

int pipe (
int filedes [2]);

In the preceding statement:

filedes Specifies the address of an array of two integers into which new file descriptors are placed.

Note

The default version of the pipe function in the libc library is not STREAMS-based. To use the STREAMS version of the pipe function the application must link with the sys5 library. See the pipe(2) reference page for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.8    The putmsg and putpmsg Functions

Use the putmsg and putpmsg functions to generate a STREAMS message block by using information from specified buffers.

The following is the syntax of the putmsg function:

int putmsg (
int filedes ;
struct strbuf *ctlbuf ;
struct strbuf *databuf ;
int flags ;)

In the preceding statement:

filedes
Specifies the file descriptor that references an open Stream.

ctlbuf
Points to a strbuf structure that holds the control part of the message.

databuf
Points to a strbuf structure that holds the data part of the message.

flags
An integer that specifies the type of message the application wants to send.

See putmsg(2) for more information.

Use the putpmsg function to send priority banded data down a Stream.

The following is the syntax of the putpmsg function:

int putpmsg (
int filedes ;
struct strbuf *ctlbuf ;
struct strbuf *databuf ;
int band ;
int flags ;)

The arguments have the same meaning as for the putmsg function. The band argument specifies the priority band of the message.

See putpmsg(2) for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.9    The getmsg and getpmsg Functions

Use the getmsg and getpmsg functions to retrieve the contents of a message located at the Stream head read queue and place them into user specified buffer(s).

The following is the syntax of the getmsg function:

int getmsg (
int filedes
struct strbuf *ctlbuf
struct strbuf *databuf
int *flags );

In the preceding statement:

filedes
Specifies a file descriptor that references an open Stream.

ctlbuf
Points to a strbuf structure that returns the control part of the message.

databuf
Points to a strbuf structure that returns the data part of the message.

flags
Points to an integer that specifies the type of message the application wants to retrieve.

See getmsg(2) for more information.

Use the getpmsg function to receive priority banded data from a Stream.

The following is the syntax of the getpmsg function:

int getpmsg (
int filedes
struct strbuf *ctlbuf
struct strbuf *databuf
int band ;
int *flags );

The arguments have the same meaning as for the getmsg function. The band argument points to an integer that specifies the priority band of the message being received.

See getpmsg(2) for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.10    The poll Function

Use the poll function to identify the Streams to which a user can send data and from which a user can receive data.

The following is the syntax for the poll function:

#include <sys/poll.h>
 
int poll (
struct pollfd filedes [ ] ,
unsigned int nfds ,
int timeout );

In the preceding statement:

filedes
Points to an array of pollfd structures, one for each file descriptor you are polling. By filling in the pollfd structure, the caller can specify a set of events about which to be notified.

nfds
Specifies the number of pollfd structures in the filedes array.

timeout
Specifies the maximum length of time (in milliseconds) to wait for at least one of the specified events to occur.

See poll(2) for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.11    The isastream Function

Use the isastream function to determine if a file descriptor refers to a STREAMS file.

The following is the syntax for the isastream routine:

int isastream (
int filedes ;);

In the preceding statement:

filedes
Specifies an open file desciptor.

The following example shows how to use the isastream function to verify that you have opened a STREAMS-based pipe instead of a sockets-based pipe:

int fds[2];

 
pipe(fds); if (isastream(fds[0])) printf("STREAMS based pipe\n"); else printf("Sockets based pipe\n");

See the isastream(3) reference page for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.12    The fattach Function

Use the fattach function to attach a STREAMS-based file descriptor to an object in the file system name space.

The following is the syntax of the fattach function:

int fattach (
int fd ,
const char *path );

In the preceding statement:

fd
Specifies an open STREAMS-based file descriptor.

path
Specifies the pathname of an existing file system object. The pathname must reference a regular file. It can not reference, for example, a directory or pipe.

The following example shows how to use the fattach function to name a STREAMS-based pipe:

int fds[2];

 
pipe(fds); fattach(fd[0], "/tmp/pipe1");

Note

The fattach function requires that the FFM_FS kernel option be configured. See the System Administration manual for information about configuring kernel options.

See the fattach(3) reference page for more information.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.2.2.13    The fdetach Function

Use the fdetach function to detach a STREAMS-based file descriptor from a file name. A STREAMS-based file descriptor may have been attached by using the fattach function.

The following is the syntax of the fdetach function:

int fdetach (
const char *path );

In the preceding statement:

path
Specifies the pathname of a file system object that was previously attached.

Note

The fdetach function requires that the File on File Mount File System (FFM_FS) kernel option is configured. See the System Administration manual for information about configuring kernel options.

See the fdetach(3) reference page for more information.

Table 5-1 lists and briefly describes the reference pages that contain STREAMS-related information. For further information about each component, refer to the appropriate reference page.

Table 5-1: STREAMS Reference Pages

Reference Page Description
autopush(8) Command that manages the system's database of automatically pushed STREAMS modules.
clone(7) STREAMS software driver that finds and opens an unused major/minor device on another STREAMS driver.
* close(2) Function that closes the file associated with a designated file descriptor.
dlb(7) STREAMS pseduodevice driver that provides a communication path between BSD-style device drivers and STREAMS protocol stacks.
fattach(8) Command that attaches a STREAMS-based file descriptor to a node in the file system.
fdetach(8) Command that detaches a STREAMS-based file descriptor from a file name.
fdetach(3) Function that detaches a STREAMS-based file descriptor from a file name.
getmsg(2)
getpmsg(2)
Functions that reference a message positioned at the Stream head read queue.
ifnet(7) STREAMS-based module that provides a bridge between STREAMS-based device drivers written to the Data Link Provider Interface (DLPI) and sockets.
isastream(3) Function that determines if a file descriptor refers to a STREAMS file.
mkfifo(2) Function that creates a unidirectional STREAMS-based file descriptor.
* open(2) Function that establishes a connection between a file and a file descriptor.
pipe(2) Function that creates a bidirectional, STREAMS-based, interprocess communication channel.
poll(2) Function that provides a general mechanism for reporting I/O conditions associated with a set of file descriptors and for waiting until one or more specified conditions becomes true.
putmsg(2)
putpmsg(2)
Functions that generate a STREAMS message block.
* read(2) Function that reads data from a file into a designated buffer.
strace(8) Application that retrieves STREAMS event trace messages from the STREAMS log driver.
strchg(1) Command that alters the configuration of a Stream.
strclean(8) Command that removes STREAMS error log files.
strconf(1) Command that queries about a Stream's configuration.
streamio(7) Command that performs a variety of control functions on Streams.
strerr(8) Daemon that receives error messages from the STREAMS log driver.
strlog(7) Interface that tracks log messages used by STREAMS error logging and event tracing daemons.
strsetup(8) Command that creates the appropriate STREAMS pseudodevices and displays the setup of your STREAMS modules.
timod(7) Module that converts ioctl calls from a transport user supporting the Transport Interface (TI) into messages that a transport protocol provider supporting TI can consume.
tirdwr(7) Module that provides a transport user supporting the TI with an alternate interface to a transport protocol provider supporting TI.
* write(2) Function that writes data to a file from a designated buffer.

Table Notes: An asterisk (*) means that the page is not STREAMS specific.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3    Kernel Level Functions

This section contains information with which the kernel programmer who writes STREAMS modules and drivers must be familiar. It contains information about:


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.1    Module Data Structures

When a module or driver is configured into the system, it must define its read and write queues and other module information.

The qinit, module_info, and streamtab data structures, all of which are located in the <sys/stream.h> header file, define read and write queues. STREAMS modules must fill in these structures in their declaration sections. See Appendix A for an example.

The only external data structure a module must provide is streamtab.

The qinit structure, shown in the following example, defines the interface routines for a queue. The read queue and write queue each have their own set of structures.

 struct  qinit {
    int     (*qi_putp)();           /* put routine */
    int     (*qi_srvp)();           /* service routine */
    int     (*qi_qopen)();          /* called on each open */
                                    /* or a push */
    int     (*qi_qclose)();         /* called on last close */
                                    /* or a pop */
    int     (*qi_qadmin)();         /* reserved for future use */
    struct module_info * qi_minfo;  /* information structure */
    struct module_stat * qi_mstat;  /* statistics structure (op-
                                    /* tional) */
};

The module_info structure, shown in the following example, contains module or driver identification and limit values:

 struct  module_info {
    unsigned short  mi_idnum;       /* module ID number */
    char           *mi_idname;      /* module name */
    long            mi_minpsz;      /* min packet size, for */
                                    /* developer use */
    long            mi_maxpsz;      /* max packet size, for */
                                    /* developer use */
    ulong           mi_hiwat;       /* hi-water mark, for */
                                    /* flow control */
    ulong           mi_lowat;       /* lo-water mark, for */
                                    /* flow control */
};

The streamtab structure, shown in the following example, forms the uppermost part of the declaration and is the only part which needs to be visible outside the module or driver:

 struct streamtab {
    struct qinit    * st_rdinit;    /* defines read QUEUE */
    struct qinit    * st_wrinit;    /* defines write QUEUE */
    struct qinit    * st_muxrinit;  /* for multiplexing drivers only */
    struct qinit    * st_muxwinit;  /* ditto */
};


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.2    Message Data Structures

Digital UNIX STREAMS messages consist of one or more linked message blocks. Each message block consists of a triplet with the following components:

The Stream head creates and fills in the message data structures when data is traveling downstream from an application. The Stream end creates and fills in
the message data structures when data is traveling upstream, as in the case of data coming from an external communications device.

The mblk_t and dblk_t structures, shown in the following examples, are located in the <sys/stream.h> header file:

/* message block */
struct  msgb {
        struct msgb *   b_next;    /* next message on queue */
        struct msgb *   b_prev;    /* previous message on queue */
        struct msgb *   b_cont;    /* next message block of message */
        unsigned char * b_rptr;    /* first unread data byte in buffer */
        unsigned char * b_wptr;    /* first unwritten data byte */
        struct datab *  b_datap;   /* data block */
        unsigned char   b_band;    /* message priority */
        unsigned char   b_pad1;
        unsigned short  b_flag;    /* message flags */
        long            b_pad2;
        MSG_KERNEL_FIELDS
};
typedef struct msgb     mblk_t;

 
/* data descriptor */ struct datab { union { struct datab * freep; struct free_rtn * frtnp; } db_f; unsigned char * db_base; /* first byte of buffer */ unsigned char * db_lim; /* last byte+1 of buffer */ unsigned char db_ref; /* count of messages pointing */ /* to block */ unsigned char db_type; /* message type */ unsigned char db_iswhat; /* message status */ unsigned int db_size; /* used internally */ caddr_t db_msgaddr; /* used internally */ long db_filler; }; #define db_freep db_f.freep #define db_frtnp db_f.frtnp
 
typedef struct datab dblk_t;
 
/* Free return structure for esballoc */ typedef struct free_rtn { void (*free_func)(char *, char *); /* Routine to free buffer */ char * free_arg; /* Parameter to free_func */ } frtn_t;

When a message is on a STREAMS queue, it is part of a list of messages linked by b_next and b_prev pointers. The q_next pointer points to the first message on the queue and the q_last pointer points to the last message on the queue.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.3    STREAMS Processing Routines for Drivers and Modules

A module or driver can perform processing on the Stream that an application requires. To perform the required processing, the STREAMS module or driver must provide special routines whose behavior is specified by the STREAMS framework. This section describes the STREAMS module and driver routines, and the following kinds of processing they provide:

Note

STREAMS modules and drivers must provide open, close, and configuration processing. The other kinds of processing described in this section are optional.

The format used to describe each routine in this section is XX_routine_name. Digital recommends that you substitute the name of a user-written STREAMS module or driver for the XX. For example, the open routine for the user-written STREAMS pseudodevice driver echo would be echo_open.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.3.1    open and close Processing

Only the open and close routines provide access to the u_area of the kernel. They are allowed to sleep only if they catch signals.


open processing

Modules and drivers must have open routines. The read side qinit structure, st_rdinit defines the open routine in its qi_qopen field. A driver's open routine is called when the application opens a Stream. The Stream head calls the open routine in a module when an application pushes the module onto the Stream.

The open routine has the following format:

 XX_open(q, devp, flag, sflag, credp)
       queue_t *q;   /* pointer to the read queue */
       dev_t *devp;  /* pointer to major/minor number
                        for devices */
       int flag;     /* file flag */
       int sflag;    /* stream open flag */
       cred_t *credp /* pointer to a credentials structure */

The open routine can allocate data structures for internal use by the STREAMS driver or module. A pointer to the data structure is commonly stored in the q_ptr field of the queue_t structure. Other parts of the module or driver can access this pointer later.


close processing

Modules and drivers must have close routines. The read side qinit structure, st_rdinit, defines the close routine in its qi_qclose field. A driver calls the close routine when the application that opened the Stream closes it. The Stream head calls the close routine in a module when it pops the module from the stack.

The close routine has the following format:

XX_close(q, flag, credp)
        queue_t *q;    /* pointer to read queue */
        int flag;      /* file flag */
        cred_t *credp  /* pointer to credentials structure */

The close routine may want to free and clean up internally used data structures.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.3.2    Configuration Processing

The configure routine is used to configure a STREAMS module or driver into the kernel. It is specific to Digital UNIX and its use is illustrated in Section 5.4.

The configure routine has the following format:

XX_configure(op, indata, indatalen, outdata, outdatalen)
    sysconfig_op_t  op;          /* operation - should be */
                                 /* SYSCONFIG_CONFIGURE */
    str_config_t *  indata;      /* for drivers - describes the device */
    size_t          indatalen;   /* sizeof(str_config_t) */
    str_config_t *  outdata;     /* pointer to returned data */
    size_t          outdatalen;  /* sizeof(str_config_t) */


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.3.3    Read Side Put and Write Side Put Processing

There are both read side and write side XX_Xput routines; XX_wput for write side put processing and XX_rput for read side put processing.


Write Side Put Processing

The write side put routine, XX_wput, is called when the upstream module's write side issues a putnext call. The XX_wput routine is the only interface for messages to be passed from the upstream module to the current module or driver.

The XX_wput routine has the following format:

 XX_wput(q, mp)
       queue_t *q;  /* pointer to write queue */
       mblk_t *mp;  /* message pointer */


Read Side Put Processing

The read side put routine, XX_rput, is called when the downstream modules read side issues a putnext call. Because there is no downstream module, drivers that are Stream ends do not have read side put routines. The XX_rput routine is the only interface for messages to be passed from the downstream module to the current module.

The XX_rput routine has the following format:

 XX_rput(q, mp)
       queue_t *q;  /* pointer to read queue */
       mblk_t *mp;  /* message pointer */

The XX_Xput routines must do at least one of the following:

The XX_Xput routine should leave any large amounts of processing to the service routine.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.3.4    Read Side Service and Write Side Service Processing

If an XX_Xput routine receives a message that requires extensive processing, processing it immediately could cause flow control problems. Instead of processing the message immediately, the XX_rput routine (using the putq call) places the message on its read side message queue and the XX_wput places the message on its write queue. The STREAMS module notices that there are messages on these queues and schedules the module's read or write side service routines to process them. If the module's XX_rput routine never calls putq, then the module does not require a read side service routine. Likewise, if the module's XX_wput routine never calls putq, then the module does not require a write side service routine.

The code for a basic service routine, either read side or write side, has the following format:

      XXXsrv(q)
      queue_t *q;
    {
            mblk_t *mp;

 
while ((mp = getq(q)) != NULL) { /* * If flow control is a problem, return * the message to the queue */
 
if (!(canput(q->q_next)) return putbq(q, mp); /* * process message */
 
putnext(q, mp); } return 0; }


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.4    Digital UNIX STREAMS Concepts

The following STREAMS concepts are unique to Digital UNIX. This section describes these concepts and how they are implemented in Digital UNIX:


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.4.1    Synchronization

Digital UNIX supports the use of more than one kernel STREAMS thread. Exclusive access to STREAMS queues and associated data structures is not guaranteed. Messages can move up and down the same Stream simultaneously, and more than one process can send messages down the same Stream.

To synchronize access to the data structures, each STREAMS module or driver chooses the synchronizaion level it can tolerate. The synchronization level determines the level of parallel activity allowed in the module or driver. Synchronization levels are defined in the sa.sa_syn_level field of the streamadm data structure which is defined in the module's or driver's configuration routine. The sa.sa_syn_level field must have one of the following values:

SQLVL_QUEUE

Queue Level Synchronizaton. This allows one thread of execution to access any instance of the module or driver's write queue at the same time another thread of execution can access any instance of the module or driver's read queue. Queue level synchronization can be used when the read and write queues do not share common data. The SQLVL_QUEUE argument provides the lowest level of synchronization available in the Digital UNIX STREAMS framework.

For example, the q_ptr field of the read and write queues do not point to the same memory location.

SQLVL_QUEUEPAIR

Queue Pair Level Synchronizaion. Only one thread at a time can access the read and write queues for each instance of this module or driver. This synchronization level is common for most modules or drivers which process data and have only per-stream state.

For example, within an instance of a module, the q_ptr field of the read and write queues points to the same memory location. There is no other shared data within the module.

SQLVL_MODULE

Module Level Synchronization. All code within this module or driver is single threaded. No more than one thread of execution can access all instances of the module or driver. For example, all instances of the module or driver are accessing data.

SQLVL_ELSEWHERE

Arbitrary Level Synchronization. The module or driver is synchronized with some other module or driver. This level is used to synchronize a group of modules or drivers that access each other's data. A character string is passed with this option in the sa.sync_info field of the streamadm structure. The character string is used to associate with a set of modules or drivers. The string is decided by convention among the cooperating modules or drivers.

For example, a networking stack such as a TCP module and an IP module which share data might agree to pass the string tcp/ip. No more than one thread of execution can access all modules or drivers synchronized on this string.

SQLVL_GLOBAL

Global Level Synchronization. All modules or drivers under this level are single threaded. Note there may be modules or drivers using other levels not under the same protection. This option is available primarily for debugging.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.3.4.2    Timeout

The Digital UNIX kernel interface to timeout and untimeout is as follows:

timeout(func, arg, ticks);
untimeout(func, arg);

However, to maintain source compatibilty with AT&T System V Release 4 STREAMS, the <sys/stream.h> header file redefines timeout to be the System V interface, which is:

id = timeout(func, arg, ticks);
untimeout(id);

The id variable is defined to be an int.

STREAMS modules and drivers must use the System V interface.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.4    Configuring a User-Written STREAMS-Based Module or Driver in the Digital UNIX Kernel

For your system to access any STREAMS drivers or modules that you have written, you must configure the drivers and modules into your system's kernel.

STREAMS modules or drivers are considered to be configurable kernel subsystems; therefore, follow the guidelines in the Programmer's Guide manual for configuring kernel subsystems.

The following sample procedure shows how to add to the kernel a STREAMS-based module (which can be a pushable module or a hardware or pseudodevice driver) called mymod, with it's source files mymodule1.c and mymodule2.c.

  1. Declare a configuration routine in your module source file, in this example, /sys/streamsm/mymodule1.c.

    Example 5-1 shows a module (mymod_configure) that can be used by a module. To use the routine with a driver, do the following:

    1. Remove the comment signs from the following line:

      /* sa.sa_flags    = STR_IS_DEVICE | STR_SYSV4_OPEN; */
      

      This line follows the following comment line:

      /* driver */
      

    2. Comment out the following line:

      sa.sa_flags       = STR_IS_MODULE | STR_SYSV4_OPEN;
      

      This line follows the following comment line:

      /* module */
      

    Example 5-1: Sample Module

    /*
    *  Sample mymodule.c
    */
    
    .
    .
    .
    #include <sys/sysconfig.h> #include <sys/errno.h>
     
    struct streamtab mymodinfo = { &rinit, &winit };
     
    cfg_subsys_attr_t mymod_attributes[] = { [1] {,0,0,0,0,0,0} /* required last element */ };
     
    int mymod_configure( cfg_op_t op; caddr_t indata; ulong indata_size; caddr_t outdata; ulong outdata_size) { dev_t devno = NODEV; [2]
     
    struct streamadm sa;
     
    if (op != CFG_OP_CONFIGURE) [3] return EINVAL;
     
    sa.sa_version = OSF_STREAMS_10; /* module */ [4] sa.sa_flags = STR_IS_MODULE | STR_SYSV4_OPEN; /* driver */ /* sa.sa_flags = STR_IS_DEVICE | STR_SYSV4_OPEN; */ sa.sa_ttys = NULL; sa.sa_sync_level = SQLVL_MODULE; [5] sa.sa_sync_info = NULL; strcpy(sa.sa_name, "mymod");
     
    if ((devno = strmod_add(devno, &mymodinfo, &sa)) == NODEV) { return ENODEV; }
     
    return ESUCCESS; }

    1. The subroutine in this example supplies an empty attribute table and no attributes are expected to be passed to the subroutine. If you want to develop attributes for your module, refer to the Programmer's Guide manual. [Return to example]

    2. The first available slot in the cdevsw table is automatically allocated for your module. If you wish to reserve a specific device number, you should define it after examining the cdevsw table in the conf.c program. For more information on the cdevsw table and how to add device driver entries to it, see the Writing Device Drivers: Tutorial. [Return to example]

    3. This example routine only supports the CFG_OP_CONFIGURE option. See the Programmer's Guide manual for information on other configuration routine options. [Return to example]

    4. The STR_SYSV4_OPEN option specifies to call the module's or device's open and close routines, using the AT&T System V Release 4 calling sequence. If this bit is not specified, the AT&T System V Release 3.2 calling sequence is used. [Return to example]

    5. Other options for the sa.sync_level field are described in Section 5.3.4. [Return to example]

  2. Statically link your module with the kernel.

    If you want to make the STREAMS module dynamically loadable, see the Programmer's Guide for information on configuring kernel subsystems. If the module you are configuring is a hardware device driver, also see the Writing Device Drivers: Tutorial.

    To statically link your module with the kernel, put your module's source files (mymodule1.c and mymodule2.c) into the /sys/streamsm directory and add an entry for each file to the /sys/conf/files file.
    The following example shows the entries in the /sys/conf/files file for mymodule1.c and mymodule2.c:

    streamsm/mymodule1.c   optional mymod Notbinary
    streamsm/mymodule2.c   optional mymod Notbinary
    

    Add the MYMOD option to the kernel configuration file. The default kernel configuration file is /sys/conf/HOSTNAME (where HOSTNAME is the name of your system in uppercase letters.) For example, if your system is named DECOSF, add the following line to the /sys/conf/DECOSF configuration file:

    options  MYMOD
    

    If you are configuring a hardware device driver continue with step 3; if not, got to step 4.

  3. If you are configuring a hardware device driver, complete steps 3a to 3d.

    If you are not configuring a hardware device driver, go to step 4.

    If you are configuring a hardware device driver, you should already have an XXprobe and an interrupt routine defined. See the Writing Device Drivers: Tutorial for information about defining probe and interrupt routines.

    1. Add the following line to the top of the device driver configuration file, which for this example is /sys/streams/mydriver.c:

      #include <io/common/devdriver.h>
      

    2. Define a pointer to a controller structure; for example:

      struct controller *XXinfo;
      

      For information on the controller structure, see the Writing Device Drivers: Tutorial.

    3. Declare and initialize a driver structure; for example:

       struct driver XXdriver =
      {
         XXprobe, 0, 0, 0, 0, XXstd, 0, 0, "XX", XXinfo
      };
      

      For information on the driver structure, see the Writing Device Drivers: Tutorial.

    4. Add the controller line to the kernel configuration file.

      The default kernel configuration file is /sys/conf/HOSTNAME (where HOSTNAME is the name of your system in uppercase letters). For example, if your system name is DECOSF, would add a line
      similar to the following to the /sys/conf/DECOSF configuration file:

      controller XX0 at bus vector XXintr
      

      For information about the possible values for the bus keyword, see the System Administration manual.

  4. Reconfigure, rebuild, and boot the new kernel for this system by using the doconfig command. See the doconfig(8) reference page or the System Administration manual for information on reconfiguring your kernel.

  5. Run the strsetup -c command to verify that the device is configured properly:

    /usr/sbin/strsetup -c

    STREAMS Configuration Information...Wed Jun 2 09:30:11 1994
     
    Name Type Major Minor Module ID ---- ---- ----- ----- --------- clone 32 0 ptm device 37 0 7609 pts device 6 0 7608 log device 36 0 44 nuls device 38 0 5001 echo device 39 0 5000 sad device 40 0 45 pipe device 41 0 5304 kinfo device 42 0 5020 xtisoUDP device 43 0 5010 xtisoTCP device 44 0 5010 dlb device 49 0 5010 bufcall module 0 timod module 5006 tirdwr module 0 ifnet module 5501 ldtty module 7701 null module 5003 pass module 5003 errm module 5003 spass module 5007 rspass module 5008 pipemod module 5303
     
    Configured devices = 11, modules = 11


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


5.5    Device Special Files

This section describes the STREAMS device special files and how they are created. It also provides an overview of the clone device.

All STREAMS drivers must have a character special file created on the system. These files are usually in the /dev/streams directory and are created at installation, or by running the /usr/sbin/strsetup utility.

A STREAMS driver has a device major number associated with it which is determined when the driver is configured into the system. Drivers other than STREAMS drivers usually have a character special file defined for each major and minor number combination. The following is an example of an entry in the /dev directory:

crw-------   1 root     system      8,   1024 Aug 25 15:38 rrz1a
crw-------   1 root     system      8,   1025 Aug 25 15:38 rrz1b
crw-------   1 root     system      8,   1026 Aug 25 15:38 rrz1c

In this example, rrz1a has a major number of 8 and a minor number of 1024. The rrz1b device has a major number of 8 and a minor number of 1025, and rrz1c has a major number of 8 and a minor number 1026.

You can also define character special files for each major and minor number combination for STREAMS drivers. The following is an example of an entry in the /dev/streams directory:

crw-rw-rw-   1 root   system   32,  0 Jul 13 12:00 /dev/streams/echo0
crw-rw-rw-   1 root   system   32,  1 Jul 13 12:00 /dev/streams/echo1

In this example, echo0 has a major number of 32 and a minor number of 0, while echo1 has a major number of 32, and a minor number of 1.

For an application to open a unique Stream to a device, it must open a minor version of that device that is not already in use. The first application can do an open on /dev/streams/echo0 while the second application can do an open on /dev/streams/echo1. Since each of these devices has a different minor number, each application acquires a unique Stream to the echo driver. This method requires that each device (in this case, echo) have a character special file for each minor device that can be opened to it. This method also requires that the application determine which character special file it should open; it does not want to open one that is already in use.

The clone device offers an alternative to defining device special files for each minor device that can be opened. When the clone device is used, each driver needs only one character special file and, instead of an application having to determine which minor devices are currently available, clone allows a second (or third) device to be opened using its (clone device's) major number. The minor number is associated with the device being opened (in this case, echo). Each time a device is opened using clone device's major number, the STREAMS driver interprets it as a unique Stream.

The strsetup command sets up the entries in the /dev/streams directory to use the clone device. The following is an example entry in the /dev/streams file:

crw-rw-rw-   1 root   system   32,  18 Jul 13 12:00 /dev/streams/echo

In this example, the system has assigned the major number 32 to the clone device. The number 18 is the major number associated with echo. When an application opens /dev/streams/echo, the clone device intercepts the call. Then, clone calls the open routine for the echo driver. Additionally, clone notifies the echo driver to do a clone open. When the echo driver realizes it is a clone open it will return its major number, 18, and the first available minor number.

Note

The character special files the /usr/sbin/strsetup command creates are created by default in the /dev/streams directory with clone as the major number. If you configure into your kernel a STREAMS driver that either does not use clone open, or uses a different name, you must modify the /etc/strsetup.conf file described in the strsetup.conf(4) reference page. To determine the major number of the clone device on your system, run the strsetup -c command.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Chapter] [Index] [Help]


5.6    Error and Event Logging

STREAMS error and event logging involves the following:

The error logger daemon, strerr, logs in a file any error messages sent to the STREAMS error logging and event tracing facility.

The trace logger, strace, writes to standard output trace messages sent to the STREAMS error logging and event tracing facility.

The strclean command can be run to clean up any old log files generated by the strerr daemon.

A STREAMS module or driver can send error messages and event tracing messages to the STREAMS error logging and event tracing facility through the strlog kernel interface. This involves a call to strlog.

The following example shows a STREAMS driver printing its major and minor device numbers to both the STREAMS error logger and the event tracing facility during its open routine:

#include <sys/strlog.h>

 
strlog(MY_DRIVER_ID, 0, 0, SL_ERROR 1 SL_TRACE, "My driver: mydriver_open() - major=%d,minor=%d", major(dev,minor(dev));

A user process can also send a message to the STREAMS error logging and event tracing facility by opening a Stream to /dev/streams/log and calling putmsg. The user process must contain code similar to the following to submit a log message to strlog:

struct strbuf ctl, dat;
struct log_ctl lc;
char *message = "Last edited by <username> on <date>";

 
ctl_len = ctl.maxlen = sizeof (lc); ctl.buf = (char *)&lc;
 
dat.len = dat.maxlen = strlen(message); dat.buf = message; lc.level = 0; lc.flags = SL_ERROR|SL_NOTIFY;
 
putmsg (log, &ctl, &dat, 0);