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:
Overview of the STREAMS framework
Description of the application interface to STREAMS
Description of the kernel-level functions
Instructions on how to configure modules or drivers
Description of the Tru64 UNIX synchronization mechanism
Information on how to create device special files
Description of error and event logging
Information about STREAMS reference pages
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:
A programming interface, or set of system calls, used by application programs to access the STREAMS framework
Kernel resources, such as the Stream head, and queue data structures used by the Stream
Kernel utilities that handle tasks such as Stream queue scheduling and flow control, memory allocation, and error logging
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:
Stream head
Stream end
Modules
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:
Interprets a standard subset of STREAMS system calls, such
as
write
and
putmsg
.
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.
Sends the messages downstream to the next module. Eventually the messages reach the Stream end, or driver.
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:
They can handle interrupts (although they do not have to).
Device drivers can have one or more interrupt routines. Interrupt routines should queue data on the read side service routine for later processing.
They can be connected to multiple Streams.
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.
They are initialized and deinitialized by the
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 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
High priority
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:
A module or a driver responds with a positive acknowledgement (M_IOCACK) or a negative acknowledgement (M_IOCNAK)
The request times out (no message is received)
The user process interrupts the
ioctl
An error condition 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:
The
<sys/stream.h>
header file must
be included for all modules and Streams applications.
The
<stropts.h>
header file must be
included when an application uses the
ioctl
system call.
The
<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 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);
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");
Use
the STREAMS-based
mkfifo
function to create a unidirectional
STREAMS-based file descriptor.
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. Seemkfifo
(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.
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:
Non-STREAMS pipes are unidirectional
STREAMS operations (such as
streamio
and
putmsg
) can not be performed on them
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. Seepipe
(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. |
This section contains information with which the kernel programmer who writes STREAMS modules and drivers must be familiar. It contains information about:
Module data structures
Message data structures
STREAMS processing routines for modules and drivers
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 */ };
Tru64 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.
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.
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.
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:
Open processing
Close processing
Configuration processing
Read side put processing
Write side put processing
Read side service processing
Write side service processing
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:
Process the message
Pass the message to the next queue (using
putnext
)
Delay processing of the message by putting the message on
the module's service routine (using
putq
)
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:
Synchronization
Timeout
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:
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.
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.
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 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
.
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:
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 */
Comment out the following line:
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; }
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]
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]
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]
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]
Other options for the
sa.sync_level
field are described in
Section 5.3.4.
[Return to example]
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.
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.
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>
Define a pointer to a controller structure; for example:
struct controller *XXinfo;
For information on the controller structure, see the Writing Device Drivers manual.
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.
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.
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.
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
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 instrsetup.conf
(4).To determine the major number of the
clone
device on your system, run thestrsetup -c
command.
STREAMS error and event logging involves the following:
The error logger daemon
The trace logger
The
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);