Page: 1 2 3 4 5 6 7

  

Chapter 2


STREAMS System Calls

2.1 INTRODUCTION

This chapter shows how to build, use, and dismantle a Stream using STREAMS-related systems calls. It also contains a section on STREAMS construction.

General and STREAMS-specific system calls provide the user level facilities required to implement application programs. This system call interface is upwardly compatible with the traditional character I/O facilities. The open(2) system call recognizes a STREAMS 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 manner as with traditional character files. The ioctl(2) system call enables users to perform functions specific to a particular device. STREAMS ioctl commands (see streamio(7)) support a variety of functions for accessing and controlling Streams. The last close(2) in a Stream dismantles a Stream.

In addition to the traditional ioctl commands and system calls, there are other system calls used by STREAMS. The poll(2) system call enables a user to poll multiple Streams for various events. The putmsg(2) and getmsg(2) system calls enable users to send and receive STREAMS messages, and are suitable for interacting with STREAMS modules and drivers through a service interface.

STREAMS provides kernel facilities and utilities to support development of 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.

2.1.1 STREAMS System Calls

A STREAMS device responds to the standard character I/O system calls, such as read(2) and write(2), by turning the request into a message. This feature ensures that STREAMS devices may be accessed from the user level in the same manner as non-STREAMS character devices. However, additional system calls provide other capabilities.

The STREAMS-related system calls are as follows:

2.1.1.1 GETMSG AND PUTMSG

The putmsg(2) and getmsg(2) system calls enable a user process to send and receive STREAMS messages, in the same form the messages have in kernel modules and drivers. read(2) and write(2) are not designed to include the message boundaries necessary to encode messages.

The advantage of this capability is that a user process, as well as a STREAMS module or driver, can implement a service interface.

2.1.1.2 POLL

The poll(2) system call allows a user process to monitor a number of streams to detect expected I/O events. Such events might be the availability of a device for writing, input data arriving from a device, a hangup occurring, an error being detected, or the arrival of a priority message.

2.2 STREAM CONSTRUCTION

STREAMS builds a Stream as a linked list of kernel resident data structures. The list is created as a set of linked queue pairs. The first queue pair is the head of the Stream and the second queue pair is the end of the Stream. The end of the Stream represents a device driver, pseudo device driver, or the other end of a STREAMS-based pipe. Kernel routines interface with the Stream head to perform operations on the Stream. Figure 2-1 depicts the upstream (read) and downstream (write) portions of the Stream. Queue H2 is the upstream half of the Stream head and Queue H1 is the downstream half of the Stream head. Queue E2 is the upstream half of the Stream end and Queue E1 is the downstream half of the Stream end.



Figure 2-1 Upstream and Downstream Stream Construction

At the same relative location in each queue is the address of the entry point, a procedure to process any message received by that queue. The procedure for Queues H1 and H2 process messages sent to the Stream head. The procedure for Queues E1 and E2 process messages received by the other end of the Stream, the Stream end (tail). Messages move from one end to the other, from one queue to the next linked queue, as the procedure specified by that queue is executed.

Figure 2-2 shows the data structures forming each queue: queue, qinit, qband, module_info, and module_stat. The qband structures have information for each priority band in the queue. The queue data structure contains various modifiable values for that queue. The qinit structure contains a pointer to the processing procedures, the module_info structure contains initial limit values, and the module_stat structure is used for statistics gathering. Each queue in the queue pair contains a different set of these data structures. There is a queue, qinit, module_info, and module_stat data structure for the upstream portion of the queue pair and a set of data structures for the downstream portion of the pair. In some situations, a queue pair may share some or all the data structures. For example, there may be a separate qinit structure for each queue in the pair and one module_stat structure that represents both queues in the pair.



Figure 2-2 Stream Queue Relationship

Figure 2-2 shows two neighboring queue pairs with links (solid vertical arrows) in both directions. When a module is pushed onto a Stream, STREAMS creates a queue pair and links each queue in the pair to its neighboring queue in the upstream and downstream direction. The linkage allows each queue to locate its next neighbor. This relation is implemented between adjacent queue pairs by the q_next pointer. Within a queue pair, each queue locates its mate (see dashed arrows in Figure 2-2) by use of STREAMS utilities, because there is no pointer between the two queues. The existence of the Stream head and Stream end is known to the queue procedures only as destinations towards which messages are sent.

Home


  

2.2.1 Opening a STREAMS Device File

One way to build a Stream is to open [see open(2)] a STREAMS-based driver file as shown in Figure 2-3. All entry points into the driver are defined by the streamtab structure for that driver. The streamtab structure has the following format:

The streamtab structure defines a module or driver. st_rdinit points to the read qinit structure for the driver and st_wrinit points to the driver's write qinit structure. st_muxrinit and st_muxwinit point to the lower read and write qinit structures if the driver is a multiplexor driver.

If the open call is the initial file open, a Stream is created. (There is one Stream per major/minor device pair.) First, an entry is allocated in the user's file table and an inode is created to represent the opened file. The file table entry is initialized to point to the allocated inode (see f_inode in Figure 2-3) and the inode is initialized to specify a file which is of the type character "special."

Second, a Stream header is created from an stdata data structure and a Stream head is created from a pair of queue structures. The content of stdata and queue are initialized with predetermined values, including the Stream head processing procedures.

The sd_inode field of stdata is initialized to point to the allocated inode. The i_sptr field of the inode data structure is initialized to point to the Stream header; thus, there is a forward and backward pointer between the Stream header and the inode. There is one Stream header per Stream. The header is used by STREAMS while performing operations on the Stream. In the downstream portion of the Stream, the Stream header points to the downstream half of the Stream head queue pair. Similarly, the upstream portion of the Stream terminates at the Stream header, because the upstream half of the Stream head queue pair points to the header. Figure 2-3 shows that from the Stream header onward, a Stream is built of linked queue pairs.



Figure 2-3 Opened STREAMS-Based Driver

Next, a queue structure pair is allocated for the driver. The queue limits are initialized to those values specified in the corresponding module_info structure. The queue processing routines are initialized to those specified by the corresponding qinit structure.

Then, the q_next values are set so that the Stream head write queue points to the driver write queue and the driver read queue points to the Stream head read queue. The q_next values at the ends of the Stream are set to null. Finally, the driver open procedure (located using its read qinit structure) is called.

Home


  

If this is the initial open of this Stream, the driver open routine is called. If modules have been specified to be autopushed, they are pushed immediately after the driver open. When a Stream is already open, further opens of the same Stream results in calls to the open procedures of all pushable modules and the driver open. Note that this is done in the reverse order from the initial Stream open. In other words, the initial open processes from the Stream end to the Stream head, while later opens processes from the Stream head to the Stream end.

2.2.2 Adding and Removing Modules

As part of building a Stream, a module can be added (pushed) with an ioctl I_PUSH [see streamio(7)] system 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. First, the address of the qinit structure for the module is obtained.

Next, STREAMS allocates a pair of queue structures and initializes their contents as in the driver open.

Then q_next values are set and modified so that the module is interposed between the Stream head and its neighbor immediately downstream. Finally, the module open procedure (located using qinit) is called.

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 will be multiple occurrences of that 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 ioctl I_POP [see streamio(7)] 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). Then, STREAMS connects the Stream head to the component previously below the popped module and deallocates 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.

2.2.3 Closing the Stream

The last close to a STREAMS file dismantles the Stream. Dismantling consists of popping any modules on the Stream and closing the driver. Before a module is popped, the close 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 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) [see open(2)] is clear, close waits up to 15 seconds for each module to drain and up to 15 seconds for the driver to drain. 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 stdata structures are deallocated.

NOTE

STREAMS frees only the messages contained on a message queue. Any message or data structures used internally by the driver or module must be freed by the driver or module close procedure.

Finally, the user's file table entry and the inode are deallocated and the file is closed.

Home


  

2.2.4 Stream Construction Example

Figure 2-4 and Figure 2-6 extend the previous communications echoing device example, shown in Section 1.3, by inserting a module in the Stream. The (hypothetical) module in this example can convert (change case, delete, and/or duplicate) selected alphabetic characters.

2.2.4.1 INSERTING MODULES

An advantage of STREAMS over the traditional character I/O mechanism stems from the ability to 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 is passed a command and a corresponding string of characters by the user. All data passing through the module are inspected for instances of characters in this string; the operation identified by the command is performed on all matching characters. The necessary declarations for this program are shown in Figure 2-4.

Figure 2-4 Inserting a Module into a STREAM

The first step is to establish a Stream to the communications driver and insert the character conversion module. The following sequence of system calls accomplishes the following display.

Home


  

The I_PUSH ioctl call directs the Stream head to insert the character conversion module between the driver and the Stream head, creating the Stream shown in Figure 2-5. As with drivers, this module resides in the kernel and must have been configured into the system before it was booted, unless the system has an autoload capability.



Figure 2-5 Case Converter Module

An important difference between STREAMS drivers and modules is illustrated here. Drivers are accessed through a node or nodes in the file system and may be opened just like any other device. Modules, on the other hand, do not occupy a file system node. Instead, they are identified through a separate naming convention, and are inserted into a Stream using I_PUSH. The name of a module is defined by the module developer.

Modules are pushed onto a Stream and removed from a Stream in Last-In-First-Out (LIFO) order. Therefore, if a second module was pushed onto this Stream, it would be inserted between the Stream head and the character conversion module.

Home


  

2.2.4.2 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 done by issuing ioctl calls to the character conversion shown in Figure 2-6.

Figure 2-6 Module and Driver Control

ioctl requests are issued to STREAMS drivers and modules indirectly, using the I_STR ioctl call [see streamio(7)]. 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 defined in stropts.h and has the following format:

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 acknowledgment before timing out, ic_len is the number of bytes of data to accompany the request, and ic_dp points to that data.

In the example, two separate commands are sent to the character conversion module. The first 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 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. The ic_dp field 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, and sends the message downstream. Any module that does not understand the command in ic_cmd passes the message further downstream. The request will be processed by the module or driver closest to the Stream head that understands the command specified by ic_cmd. The ioctl call will block up to ic_timout seconds, waiting for the target module or driver to respond with either a positive or negative acknowledgment message. If an acknowledgment is not received in ic_timout seconds, the ioctl call will fail.

NOTE

Only one I_STR request can be active on a Stream at one time. Further requests will block until the active I_STR request is acknowledged and the system call completes.

Home


  

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 will be set on return to show the amount of data returned.

The remainder of this example is identical to the earlier example in this chapter:

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

The exit system call dismantles the Stream before terminating the process. The character conversion module is removed from the Stream automatically when it is closed. Alternatively, modules may be removed from a Stream using the I_POP ioctl call described in streamio(7). 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.

A few of the important ioctl requests supported by STREAMS have been discussed. Several other requests are available to support operations such as determining if a given module exists on the Stream, or flushing the data on a Stream. These requests are described fully in streamio(7).

Home

Contents Previous Chapter Next Chapter Index Glossary