Controlling Data Flow
If the stream exerts flow control, the write(2) call blocks until flow control has been relieved, unless the file has been specifically advised not to. open(2) or fcntl(2) can be used to control this nonblocking behavior.
Simple Stream Example
Example 1-1 shows how an application might use a simple stream. Here, the user program interacts with a communications device that provides point-to-point data transfer between two computers. Data written to the device is transmitted over the communications line, and data arriving on the line is retrieved by reading from the device.
In this example, /dev/ttya identifies an instance of a serial communications device driver. When this file is opened, the system recognizes the device as a STREAMS device and connects a stream to the driver. Figure 1-3 shows the state of the stream following the call to open(2).
Figure 1-3 Stream to Communications Driver
This example illustrates a simple loop, with the application reading data from the communications device, then writing the input back to the same device, echoing all input back over the communications line. The program reads up to 1024 bytes at a time, and then writes the number of bytes just read.
read(2) returns the available data, which can contain fewer than 1024 bytes. If no data is currently available at the stream head, read(2) blocks until data arrives.
Note - The application program must loop on read(2) until the desired number of bytes are read. The responsibility for the application getting all the bytes it needs is that of the application developer, not the STREAMS facilities.
Similarly, the write(2) call attempts to send the specified number of bytes to /dev/ttya. The driver can implement a flow-control mechanism that prevents a user from exhausting system resources by flooding a device driver with data.
How STREAMS Works at the Kernel Level
Creating the Stream Head
The stream head is created when a user process opens a STREAMS device. It translates the interface calls of the user process into STREAMS messages, which it sends to the stream. The stream head also translates messages originating from the stream into a form that the application can process. The stream head contains a pair of queues; one queue passes messages upstream from the driver, and the other passes messages to the driver. The queues are the pipelines of the stream, passing data between the stream head, modules, and driver.
A STREAMS module does processing operations on messages passing from a stream head to a driver or from a driver to a stream head. For example, a TCP module might add header information to the front of data passing downstream through it. Not every stream requires a module. There can be zero or more modules in a stream.
Modules are stacked (pushed) onto and unstacked (popped) from a stream. Each module must provide open(), close(), and put() entries and provides a service() entry if the module supports flow control.
Like the stream head, each module contains a pair of queue structures, although a module only queues data if it is implementing flow control. Figure 1-4 shows the queue structures Au/Ad associated with Module A ("u" for upstream "d" for downstream) and Bu/Bd associated with Module B.
The two queues operate completely independently. Messages and data can be shared between upstream and downstream queues only if the module functions are specifically programed to share data.
Within a module, one queue can refer to the messages and data of the opposing queue. A queue can directly refer to the queue of the successor module (adjacent in the direction of message flow). For example, in Figure 1-4, Au (the upstream queue from Module A) can reference Bu (the upstream queue from Module B). Similarly Queue Bd can reference Queue Ad.
Figure 1-4 Stream in More Detail
Both queues in a module contain messages, processing procedures, and private data.
Structure of a STREAMS Device Driver
STREAMS device drivers are structurally similar to STREAMS modules and character device drivers. The STREAMS interfaces to driver routines are identical to the interfaces used for modules. For instance they must both declare open, close, put, and service entry points.
There are some significant differences between modules and drivers.
Must be able to handle interrupts from the device.
Is represented in file system by a character-special file.
Is initialized and disengaged using open(2) and close(2). open(2) is called when the device is first opened and for each reopen of the device. close(2) is only called when the last reference to the stream is closed.
Both drivers and modules can pass signals, error codes, and return values to processes using message types provided for that purpose.
a message header structure (msgb(9S)) that identifies the message instance.
a data block structure (datab(9S)) points to the data of the message.
the data itself
Each data block and data pair can be referenced by one or more message headers. The objects passed between STREAMS modules are pointers to messages. Messages are sent through a stream by successive calls to the put procedure of each module or driver in the stream. Messages can exist as independent units, or on a linked list of messages called a message queue. STREAMS utility routines enable developers to manipulate messages and message queues.
All STREAMS messages are assigned message types to indicate how they will be used by modules and drivers and how they will be handled by the stream head. Message types are assigned by the stream head, driver, or module when the message is created. The stream head converts the system calls read, write, putmsg, and putpmsg into specified message types, and sends them downstream. It responds to other calls by copying the contents of certain message types that were sent upstream.
Message Queueing Priority
Sometimes messages with urgent information, such as a break or alarm conditions, must pass through the stream quickly. To accommodate them, STREAMS uses message queuing priority, and high-priority message types. All messages have an associated priority field. Normal (ordinary) messages have a priority of zero, while priority messages have a priority band greater than zero. High-priority messages have a high priority by virtue of their message type, are not blocked by STREAMS flow control, and are processed ahead of all ordinary messages on the queue.
Nonpriority, ordinary messages are placed at the end of the queue following all other messages that can be waiting. Priority messages can be either high priority or priority band messages. High-priority messages are placed at the head of the queue but after any other high-priority messages already in the queue. Priority band messages enable support of urgent, expedited data. Priority band messages are placed in the queue in the following order:
after high-priority messages but before ordinary messages.
below all messages that have a priority greater than or equal to their own.
above any messages with a lesser priority.
Figure 1-5 shows the message queueing priorities.
Figure 1-5 Message Priorities
High-priority message types cannot be changed into normal or priority band message types. Certain message types come in equivalent high-priority or ordinary pairs (for example, M_PCPROTO and M_PROTO), so that a module or device driver can choose between the two priorities when sending information.