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:
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.
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:
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:
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:
#
/usr/sbin/strsetup -c
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:
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:
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:
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:
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:
Note
The default version of the mkfifo function in the libc library is not STREAMS-based. To use the STREAMS version of the mkfifo function the application must link with the sys5 library. See the mkfifo(2) reference page for more information. Also note that the mkfifo function requires that the File on File Mount File System (FFM_FS) kernel option is configured. See the System Administration manual for information about configuring kernel options.
Use the STREAMS-based pipe function to create a bidirectional, STREAMS-based, communication channel. Non-STREAMS pipes and STREAMS-based pipes differ in the following ways:
The following is the syntax of the pipe function:
int pipe
(
int
filedes
[2]);
In the preceding statement:
Note
The default version of the pipe function in the libc library is not STREAMS-based. To use the STREAMS version of the pipe function the application must link with the sys5 library. See the pipe(2) reference page for more information.
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:
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:
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:
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:
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:
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:
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.
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.
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:
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.
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; }
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.
#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.
#
/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 the strsetup.conf(4) reference page. To determine the major number of the clone device on your system, run the strsetup -c command.
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);