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.
The STREAMS framework consists of:
Figure 5-1 highlights the STREAMS framework and shows its place in the network programming environment.
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.
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:
write
and
putmsg
.
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:
Device drivers can have one or more interrupt routines. Interrupt routines should queue data on the read side service routine for later processing.
A driver can be implemented as a multiplexor, meaning that it is connected to multiple Streams in either the upstream or downstream direction. See the Programmer's Guide: STREAMS for more information.
open
and
close
system calls. (Other modules use the
I_PUSH
and
I_POP
commands of the
ioctl
system call.)
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
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.
Definitions for the basic STREAMS data types are included in the following header files:
<sys/stream.h>
header file must be included for all modules and Streams applications.
<stropts.h>
header file must be included when an application uses the
ioctl
system call.
<strlog.h>
header file must be included when an application uses the STREAMS error
logger and trace facility.
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.
Your application accesses and manipulates STREAMS kernel resources through the following functions:
open
close
read
write
ioctl
mkfifo
pipe
putmsg
and
putpmsg
getmsg
and
getpmsg
poll
isastream
fattach
fdetach
This section briefly describes these functions. For detailed information about these functions, see the Digital UNIX reference pages and the Programmer's Guide: STREAMS.
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
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
mode
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);
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
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.
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
*buffer
nbytes
filedes
.
See
read
(2)
for more information.
The
read
function fails on message
types other than M_DATA, and
errno
is set to EBADMSG.
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
*buffer
nbytes
filedes
.
See
write
(2)
for more information.
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
command
ioctl
commands are handled by the Stream head; others
are passed downstream to be handled by the modules and driver.
arg
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");
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
mkfifo
function.
mode
Note
The default version of the
mkfifo
function in thelibc
library is not STREAMS-based. To use the STREAMS version of themkfifo
function the application must link with thesys5
library. See themkfifo
(2) reference page for more information. Also note that themkfifo
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.
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:
streamio
and
putmsg
)
can not be performed on them
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 thelibc
library is not STREAMS-based. To use the STREAMS version of thepipe
function the application must link with thesys5
library. See thepipe
(2) reference page for more information.
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
ctlbuf
strbuf
structure that holds the control part of the
message.
databuf
strbuf
structure that holds the data part of the
message.
flags
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.
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
ctlbuf
strbuf
structure that returns the control part of the
message.
databuf
strbuf
structure that returns the data part of the
message.
flags
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.
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
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
pollfd
structures in the
filedes
array.
timeout
See
poll
(2)
for more information.
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
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.
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
path
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.
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
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.
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.
This section contains information with which the kernel programmer who writes STREAMS modules and drivers must be familiar. It contains information about:
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 */ };
Digital UNIX STREAMS messages consist of one or more linked message blocks. Each message block consists of a triplet with the following components:
The data buffer contains the binary data that makes up the message. STREAMS imposes no alignment rules on the format of data in the data buffer, aside from those imposed by messages processed at the Stream head.
mblk_t
control structure
The
mblk_t
structure contains information that the message owner can manipulate.
Two of its fields are the read and write pointers into the data buffer.
dblk_t
control structure
The
dblk_t
structure contains information about buffer characteristics.
For example, two of its fields point to the limits of the data buffer, while
others contain the message type.
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.
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.
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.
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.
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.
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) */
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.
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 */
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:
putnext
)
putq
)
The
XX_Xput
routine should leave any large amounts of
processing to the service routine.
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; }
The following STREAMS concepts are unique to Digital UNIX. This section describes these concepts and how they are implemented in Digital UNIX:
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:
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.
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.
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.
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.
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.
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.
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
.
/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:
/* sa.sa_flags = STR_IS_DEVICE | STR_SYSV4_OPEN; */
This line follows the following comment line:
/* driver */
sa.sa_flags = STR_IS_MODULE | STR_SYSV4_OPEN;
This line follows the following comment line:
/* 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; }
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]
CFG_OP_CONFIGURE
option.
See the
Programmer's Guide
manual for information on other configuration routine
options.
[Return to example]
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]
sa.sync_level
field are described in
Section 5.3.4.
[Return to example]
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.
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.
/sys/streams/mydriver.c
:
#include <io/common/devdriver.h>
struct controller *XXinfo;
For information on the controller structure, see the Writing Device Drivers: Tutorial.
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.
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.
doconfig
command. See the
doconfig
(8)
reference page or the
System Administration
manual for information on reconfiguring your kernel.
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
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 thestrsetup.conf
(4) reference page. To determine the major number of theclone
device on your system, run thestrsetup -c
command.
STREAMS error and event logging involves the following:
strclean
command
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);