5    Tru64 UNIX STREAMS

The operating system 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 Tru64 UNIX implementation of STREAMS differs from that of AT&T System V, Version 4.0. Where the Tru64 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.

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

5.1.1    Review of STREAMS Components

To communicate using 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 manual 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

5.1.2    ioctl Processing

In STREAMS, user processes can perform control functions on specific modules and drivers in a Stream with ioctl calls. When a user process issues an ioctl command, STREAMS blocks the process, forces the Stream head to process the command, and, if necessary, send messages (M_IOCTL) downstream to be received and processed by a specific module or driver. The user process is blocked until one of the following occurs:

STEAMS provides two methods of ioctl processing: I_STR and transparent.Table 5-1 compares the two methods.

Table 5-1:  I_STR and Transparent ioctl Processing Comparison

I_STR Processing Transparent Processing
Supports user applications written for STREAMS files only Supports user applications written for either STREAMS or non-STREAMS filess
Commands described in streamio(7) Commands described in ioctl(2)
Data format and addressing restrictions Data format and addressing restrictions depend on the ioctl
Requires single pair of messages to complete ioctl processing May require multiple pairs of messages to complete ioctl processing
Timeout value is user-specified or a default Timeout value is infinite
Implies STREAMS processing More general

See the Programmer's Guide: STREAMS for more information on both methods of ioctl processing.

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.

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.

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 reference pages and the Programmer's Guide: STREAMS.

5.2.2.1    The open Function

Use the open function to open a Stream.

See open(2) for function syntax, parameters, and errors.

The following example shows how the open function is used:

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

5.2.2.2    The close Function

Use the close function to close a Stream.

See close(2) for function syntax, parameters, and errors.

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.

5.2.2.3    The read Function

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

See read(2) for function syntax, parameters, and errors.

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

5.2.2.4    The write Function

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

See write(2) for function syntax, parameters, and errors.

5.2.2.5    The ioctl Function

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

See streamio(7) for function syntax, parameters, and errors for the STREAMS ioctl function.

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");

5.2.2.6    The mkfifo Function

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

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 mkfifo(2) for function syntax, parameters, and errors.

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.

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:

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 pipe(2) for function syntax, parameters, and errors.

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.

See putmsg(2) for function syntax, parameters, and errors.

Use the putpmsg function to send priority banded data down a Stream. The arguments have the same meaning as for the putmsg function.

See putpmsg(2) for function syntax, parameters, and errors.

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

See getmsg(2) for function syntax, parameters, and errors.

Use the getpmsg function to receive priority banded data from a Stream. The arguments have the same meaning as for the getmsg function.

See getpmsg(2) for function syntax, parameters, and errors.

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.

See poll(2) for function syntax, parameters, and errors.

5.2.2.11    The isastream Function

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

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 isastream(3) for function syntax, parameters, and errors.

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 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 fattach(3) for function syntax, parameters, and errors.

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.

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 fdetach(3) for function syntax, parameters, and errors.

Table 5-2 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-2:  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) [Footnote 14] 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(3) 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) [Footnote 14] 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) [Footnote 14] 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(3) 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) [Footnote 14] Function that writes data to a file from a designated buffer.

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:

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

5.3.2    Message Data Structures

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

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

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.

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

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.

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, the module does not require a read side service routine. Likewise, if the module's XX_wput routine never calls putq, 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;
    }

5.3.4    Tru64 UNIX STREAMS Concepts

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

5.3.4.1    Synchronization

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

5.3.4.2    Timeout

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

5.4    Configuring a User-Written STREAMS-Based Module or Driver in the Tru64 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 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. [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 manual. [Return to example]

    3. This example routine only supports the CFG_OP_CONFIGURE option. See the Programmer's Guide 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 manual.

    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 TRU64, add the following line to the /sys/conf/TRU64 configuration file:

    options  MYMOD
    

    If you are configuring a hardware device driver continue with step 3; if not, go 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 manual 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 manual.

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

    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 TRU64, would add a line similar to the following to the /sys/conf/TRU64 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 doconfig(8) 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
    

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/rdisk directory:

crw-------   1 root     system      8,   1024 Aug 25 15:38 dsk1a
crw-------   1 root     system      8,   1025 Aug 25 15:38 dsk1b
crw-------   1 root     system      8,   1026 Aug 25 15:38 dsk1c

In this example, dsk1a has a major number of 8 and a minor number of 1024. The dsk1b device has a major number of 8 and a minor number of 1025, and dsk1c 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 strsetup.conf(4).

To determine the major number of the clone device on your system, run the strsetup -c command.

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