STREAMS Programming Guide

Chapter 2 STREAMS Application-Level Components

This chapter shows how to construct, use, and dismantle a stream using STREAMS-related system calls. It provides a general discussion of the relationship between STREAMS components in a simple streams example.

STREAMS Interfaces

The stream head provides the interface between the stream and an application program. After a stream has been opened, STREAMS-related system calls enable a user process to insert and delete (push and pop) modules. That process can then communicate with and control the operation of the stream head, modules, and drivers. The stream head handles most system calls so that the related processing does not have to be incorporated in a module or driver.

STREAMS System Calls

Table 2-1 offers an overview of some basic STREAMS-related system calls.

Table 2–1 Summary of Basic STREAMS-related System Calls

Function 

Description 

open(2)

Opens a stream 

close(2)

Closes a stream 

read(2)

Reads data from a stream 

write(2)

Writes data to a stream 

ioctl(2)

Controls a stream  

getmsg(2)

Receives a message at the stream head 

getpmsg(2)

Receives a priority message at the stream head 

putmsg(2)

Sends a message downstream 

putpmsg(2)

Sends a priority message downstream 

poll(2)

Identifies files on which a user can send or receive messages, or on which certain events have occurred (not restricted to STREAMS, although historically it was) 

pipe(2)

Creates a bidirectional channel that provides a communication path between multiple processes  


Note –

Sections 1, 2, 3, 7, and 9 of the online manual pages (man pages) contain all the STREAMS information.


Action Summary

The open(2) system call recognizes a STREAMS special file and creates a stream to the specified driver. A user process can receive and send data on STREAMS files using read(2) and write(2) in the same way as with traditional character files. ioctl(2) lets users perform functions specific to a particular device. STREAMS ioctl(2) commands (see the streamio(7I) man page) support a variety of functions to access and control streams. The final close(2) on a stream dismantles it.

The poll(2) system call provides a mechanism for multiplexing input/output over a set of file descriptors that reference open files. putmsg(2) and getmsg(2) and the putpmsg(2) and getpmsg(2) send and receive STREAMS messages, and can act on STREAMS modules and drivers through a service interface.

Opening a STREAMS Device File

One way to construct a stream is to callopen(2) to open a STREAMS special file. If the open call is the initial file open, a stream is created. (There is one stream for each major or minor device pair.) If this open is not the initial open of this stream, the open procedures of the driver and all pushable modules on the stream are called.

Sometimes a user process needs to connect a new stream to a driver regardless of which minor device is used to access the driver. Instead of the user process polling for an available minor device node, STREAMS provides a facility called clone open. If a STREAMS driver is implemented as a clone device, a single node in the file system may be opened to access any unused device that the driver controls. This special node guarantees that the user is allocated a separate stream to the driver for every open call. Each stream is associated with an unused major or minor device, so the total number of streams that can connect to a particular clone driver is limited to the number of minor devices configured for the driver.

Clone devices are used, for example, in a networking environment where a protocol pseudo-device driver requires each user to open a separate stream to establish communication.

You can open a clone device in two ways. The first is to create a node with the major number of the clone device (--) and a minor number corresponding to the major number of the device to be cloned. For example /dev/ps0 might have a major number of 50 and a minor number of 0 for normal opens. The clone device may have a major number of 40. By creating a node /dev/ps with a major number of 40 and a minor number of 50, a clonable device is created. In this case, the driver is passed a special flag (CLONEOPEN) that tells it to return a unique minor device number.

The second way is to have the driver open itself as a clone device, that is, the driver returns a unique minor number.

When a stream is already open, further opens of the same device call the open routines of all modules and the driver on that stream. In this case, a driver is opened and a module is pushed on a stream. When a push occurs, the module open routine is called. If another open of the same device is made, the open routine of the module is called, followed by the open routine of the driver. This is opposite to the initial order of opens when the stream is created.

STREAMS also offers autopush. On an open(2) system call, a preconfigured list is checked for modules to be pushed. All modules in this list are pushed before the open(2) returns. For more information see the autopush(1M) and sad(7D) man pages.

Initializing Details

There is one stream head per stream. The stream head, which is initiated by the first open call, is created from a data structure and a pair of queue structures. The content of the stream head and queues is initialized with predetermined values, including the stream head processing procedures.

Queue Allocation

STREAMS queues are allocated in pairs. One queue is always the upstream or read-side; the other is the downstream or write-side. These queues hold the messages, and tell the kernel which processing routines apply to each message passing through a module. The queue structure type is queue_t. Fields in the queue data structure are detailed in queue(9S).

Adding and Removing Modules

As part of constructing a stream, a module can be added (pushed) with an I_PUSH ioctl(2) (see streamio(7I)) call. The push inserts a module beneath the stream head. Because of the similarity of STREAMS components, the push operation is similar to the driver open.

Each push of a module is independent, even in the same stream. If the same module is pushed more than once on a stream, there are multiple occurrences of the module in the stream. The total number of pushable modules that may be contained on any one stream is limited by the kernel parameter nstrpush.

An I_POP ioctl(2) (see streamio(7I))) system call removes (pops) the module immediately below the stream head. The pop calls the module close procedure. On return from the module close, any messages left on the module's message queues are freed (deallocated). The stream head then connects to the component previously below the popped module and releases the module's queue pair.

I_PUSH and I_POP enable a user process to dynamically alter the configuration of a stream by pushing and popping modules as required. For example, a module may be removed and a new one inserted below the stream head. Then the original module can be pushed back after the new module has been pushed.

You can also restrict which modules can be popped with I_POP calls by placing an anchor in the stream at any module you want to “lock down.” The I_ANCHOR ioctl prevents a module from being popped except by a privileged process. See STREAMS Anchors for more information about working with anchors.

Closing the Stream

The last close to a STREAMS device dismantles the stream. Dismantling consists of popping any modules on the stream and closing the driver. Before a module is popped, the close(2) may delay to allow any messages on the write message queue of the module to be drained by module processing. Similarly, before the driver is closed, the close(2) may delay to allow any messages on the write message queue of the driver to be drained by driver processing. If O_NDELAY (or O_NONBLOCK) is clear, close(2) waits up to 15 seconds for each module to drain and up to 15 seconds for the driver to drain, see open(2) and fcntl(2). The default close delay is 15 seconds, but this can be changed on a per-stream basis with the I_SETCLTIME ioctl(2).

The close delay is independent of any delay that the module or driver's close routine itself chooses to impose. If O_NDELAY (or O_NONBLOCK) is set, the pop is performed immediately and the driver is closed without delay.

Messages can remain queued, for example, if flow control is inhibiting execution of the write queue service procedure. When all modules are popped and any wait for the driver to drain is completed, the driver close routine is called. On return from the driver close, any messages left on the driver's queues are freed, and the queue and stream head structures are released.

Stream Construction Example

This example extends the communications device-echoing example shown in Simple Stream Example. The module in this example converts (change case, delete, duplicate) selected alphabetic characters.


Note –

The complete listing of the module is on the CD.


Inserting Modules

An application can insert various modules into a stream to process and manipulate data that pass between a user process and the driver. In the example, the character conversion module receives a command and a corresponding string of characters from the user. All data passing through the module is inspected for instances of characters in this string. Whatever operation the command requires is performed on all characters that match the string.


Example 2–1 Module Header File Definition

#include <string.h>
#include <fcntl.h>
#include <stropts.h>
#define	BUFLEN		1024
/*
 * These definitions would typically be
 * found in a header file for the module
 */
#define	XCASE		1		/* change alphabetic case of char */
#define	DELETE		2		/* delete char */
#define	DUPLICATE	3		/* duplicate char */
main()
{
 	char buf[BUFLEN];
 	int fd, count;
 	struct strioctl strioctl;

The first step is to establish a stream to the communications driver and insert the character conversion module. This is accomplished by first opening (fd = open) then calling ioctl(2) to push the chconv module, as shown in the sequence of system calls in Example 2–2.


Example 2–2 Pushing a Module

if ((fd = open("/dev/term/a", O_RDWR)) < 0) {
 	perror("open failed");
 	exit(1);
}
if (ioctl(fd, I_PUSH, "chconv") < 0) {
 	perror("ioctl I_PUSH failed");
 	exit(2);
}

The I_PUSH ioctl(2) call directs the stream head to insert the character conversion module between the driver and the stream head. The example illustrates an important difference between STREAMS drivers and modules. Drivers are accessed through a node or nodes in the file system (in this case /dev/term/a) and are opened just like other devices. Modules, on the other hand, are not devices. Identify modules through a separate naming convention, and insert them into a stream using I_PUSH or autopush. Figure 2–1 shows creation of the stream.

Figure 2–1 Pushing the Character Conversion Module

Diagram shows the insertion of a character conversion module
into a stream.

Modules are stacked onto a stream and removed from a stream in last-in, first-out (LIFO) order. Therefore, if a second module is pushed onto this stream, it is inserted between the stream head and the character conversion module.

Module and Driver Control

The next step in this example is to pass the commands and corresponding strings to the character conversion module. This can be accomplished by calling ioctl(2) to invoke the character conversion module.

Example 2–3 uses the conventional I_STR ioctl(2), an indirect way of passing commands and data pointers. Example 2–4 shows the data structure for I_STR.

Instead of I_STR, some systems support transparent ioctls in which calls can be made directly. For example, a module calls I_PUSH. Both modules and drivers can process ioctls without requiring user programs to first encapsulate them with I_STR (that is, the ioctls in the examples would look like ioctl(fd,DELETE,"AEIOU");). This style of call works only for modules and drivers that have been converted to use the new facilities that also accept the I_STR form.


Example 2–3 Processing ioctl(2)

/* change all uppercase vowels to lowercase */
strioctl.ic_cmd = XCASE;
strioctl.ic_timout = 0; /* default timeout (15 sec) */
strioctl.ic_dp = "AEIOU";
strioctl.ic_len = strlen(strioctl.ic_dp);
if (ioctl(fd, I_STR, &strioctl) < 0) {
   perror("ioctl I_STR failed");
   exit(3);
}
/* delete all instances of the chars 'x' and 'X' */
strioctl.ic_cmd = DELETE;
strioctl.ic_dp = "xX";
strioctl.ic_len = strlen(strioctl.ic_dp);
if (ioctl(fd, I_STR, &strioctl) < 0) {
   perror("ioctl I_STR failed");
   exit(4);
}

In Example 2–3, the module changes all uppercase vowels to lowercase, and deletes all instances of either uppercase or lowercase “x”. ioctl(2) requests are issued indirectly, using I_STR ioctl(2) (see streamio(7I)). The argument to I_STR must be a pointer to a strioctl structure, which specifies the request to be made to a module or driver. This structure is described in streamio(7I) and has the format shown in the following example.


Example 2–4 strioctl Structure


struct strioctl {
	int         ic_cmd;             /* ioctl request */
	int         ic_timout;          /* ACK/NAK timeout */
	int         ic_len;             /* length of data argument */
	char        *ic_dp;             /* ptr to data argument */
};

where:

ic_cmd

Identifies the command intended for a module or driver.

ic_timout

Specifies the number of seconds an I_STR request should wait for an acknowledgement before timing out.

ic_len

The number of bytes of data to accompany the request.

ic_dp

Points to the data. In the example, two separate commands are sent to the character-conversion module:

  • The first command sets ic_cmd to the command XCASE and sends as data the string “AEIOU.” It converts all uppercase vowels in data passing through the module to lowercase.

  • The second command sets ic_cmd to the command DELETE and sends as data the string “xX.” It deletes all occurrences of the characters “x” and “X” from data passing through the module.

For each command, the value of ic_timout is set to zero, which specifies the system default timeout value of 15 seconds. ic_dp points to the beginning of the data for each command; ic_len is set to the length of the data.

I_STR is intercepted by the stream head, which packages it into a message using information contained in the strioctl structure, then sends the message downstream. Any module that cannot process the command in ic_cmd passes the message further downstream. The request is processed by the module or driver closest to the stream head that understands the command specified by ic_cmd. ioctl(2) blocks up to ic_timout seconds, waiting for the target module or driver to respond with either a positive or negative acknowledgement message. If an acknowledgement is not received in ic_timout seconds, ioctl(2) fails.


Note –

Only one ioctl(2) can be active on a stream at one time, regardless of whether it is issued with I_STR. Further requests will block until the active ioctl(2) is acknowledged and the system call concludes.


The strioctl structure is also used to retrieve the results, if any, of an I_STR request. If data is returned by the target module or driver, ic_dp must point to a buffer large enough to hold that data, and ic_len is set on return to indicate the amount of data returned. The remainder of this example is identical to Example 1–1 in Chapter 1, Overview of STREAMS.


Example 2–5 Process Input

while ((count = read(fd, buf, BUFLEN)) > 0) {
			if (write(fd, buf, count) != count) {
				perror("write failed");
				break;
			}
	}
	exit(0);
}

Notice that the character-conversion processing was realized with no change to the communications driver.

exit(2) dismantles the stream before terminating the process. The character conversion module is removed from the stream automatically when it is closed. Alternatively, remove modules from a stream using I_POP ioctl(2) which is described in streamio(7I). This call removes the topmost module on the stream, and enables a user process to alter the configuration of a stream dynamically by popping modules as needed.

Several other ioctl(2) requests support STREAMS operations, such as determining if a given module is on a stream, or flushing the data on a stream. streamio(7I) describes these requests.