A stream is created on the first open subroutine to a character special file corresponding to a STREAMS driver.
A stream is usually built in two steps. Step one creates a minimal stream consisting of just the stream head and device driver, and step two adds modules to produce an expanded stream as shown in the Stream Setup diagram. Modules which can be added to a stream are known as pushable modules.
Figure 10-10. Stream Setup. This diagram shows minimal stream setup on the left. The stream head is transmitting and receiving communication from the queue pair which sits on top of the raw tty device driver. The expanded stream on the right has a CANONPROC module between the queue pair and stream head. There is two-way communication between CANONPROC and the stream head and the queue pair.
The first step in building a stream has three parts:
If the driver performs all character and device processing required, no modules need to be added to a stream. Examples of STREAMS drivers include a raw tty driver (one that passes along input characters without change) and a driver with multiple streams open to it (corresponding to multiple minor devices opened to a character device driver).
When the driver receives characters from the device, it places them into messages. The messages are then transferred to the next stream component, the stream head, which extracts the contents of the message and copies them to user space. Similar processing occurs for downstream character output; the stream head copies data from user space into messages and sends them to the driver.
As the second step in building a stream, modules can be added to the stream. In the right-hand stream in the Stream Setup diagram, the CANONPROC module was added to provide additional processing on the characters sent between head and driver.
Modules are added and removed from a stream in last-in-first-out (LIFO) order. They are inserted and deleted at the stream head by using ioctl operations. In the stream on the left of the Module Reusability diagram, the Class 1 Transport was added first, followed by the Canonical modules. To replace the Class 1 module with a Class 0 module, the Canonical module would have to be removed first, and then the Class 1 module. Finally, a Class 0 module would be added and the Canonical module put back.
Figure 10-11. Module Reusability. This diagram shows the user process on the left where the canonical module has two-way communication with the boarder of the SAME module and SAME interface. The canonical module also has two-way communication with the class 1 transport protocol. There is also two-way communication from the transport protocol to the LAPB (link-access procedure balanced) driver. The second stream user process on the right shows a canonical module which has two-way communication with the boarder of the SAME module and SAME interface. The canonical module also has two-way communication with the raw tty driver.
Because adding and removing modules resembles stack operations, an add routine is called a push and the remove routine is called a pop. I_PUSH and I_POP are two of the operations included in the STREAMS subset of ioctl operations (the streamio operations). These operations perform various manipulations of streams. The modules manipulated in this manner are called pushable modules, in contrast to the modules contained in the stream head and stream end. This stack terminology applies only to the setup, modification, and breakdown of a stream.
Note: Subsequent use of the word module will refer to those pushable modules between stream head and stream end.
The stream head processes the streamio operation and executes the push, which is analogous to opening the stream driver. Modules are referenced by a unique symbolic name, contained in the STREAMS fmodsw module table (similar to the devsw table associated with a device file). The module table and module name are internal to STREAMS and are accessible from user space only through STREAMS ioctl subroutines. The fmodsw table points to the module template in the kernel. When a module is pushed, the template is located, the module structures for both QUEUES are allocated, and the template values are copied into the structures.
In addition to the module elements, each module contains pointers to an open routine and a close routine. The open routine is called when the module is pushed, and the close routine is called when the module is popped. Module open and close procedures are similar to a driver open and close.
As in other files, a STREAMS file is closed when the last process open to it closes the file by the close subroutine. This subroutine causes the stream to be dismantled (that is, modules are popped and the driver close routine is executed).
Modules are pushed onto a stream to provide special functions and additional protocol layers. In the Stream Set Up diagram, the stream on the left is opened in a minimal configuration with a raw tty driver and no other module added. The driver receives one character at a time from the device, places the character in a message, then sends the message upstream. The stream head receives the message, extracts the single character, then copies it into the reading process buffer to send to the user process in response to the read subroutine. When the user process wants to send characters back to the driver, it issues the write subroutine, and the characters are sent to the stream head. The head copies the characters into one or more multiple-character messages and sends these messages downstream. An application program requiring no further kernel character processing would use this minimal stream.
A user requiring a more terminal-like interface would need to insert a module to perform functions such as echoing, character-erase, and line-kill. Assuming that the CANONPROC module shown in the Stream Set Up diagram fulfills this need, the application program first opens a raw tty stream. Then the CANONPROC module is pushed above the driver to create an expanded stream of the form shown on the right of the diagram. The driver is not aware that a module has been placed above it and therefore continues to send single character messages upstream. The module receives single-character messages from the driver, processes the characters, then accumulates them into line strings. Each line is placed into a message then sent to the stream head. The head now finds more than one character in the messages it receives from downstream.
Stream head implementation accommodates this change in format automatically and transfers the multiple-character data into user space. The stream head also keeps track of messages partially transferred into user space (for example, when the current user read buffer can only hold part of the current message). Downstream operation is not affected: the head sends, and the driver receives, multiple-character messages.
The stream head provides the interface between the stream and user process. Modules and drivers do not have to implement user interface functions other than the open and close subroutines.