An individual Stream is composed of various pieces:
The advantage of this approach is that the modules are essentially completely independent. You can arrange a Stream to do whatever kind of data processing you want by putting the appropriate modules into the Stream in the right order. There is a default set (for tty Streams), but user-level ioctl() calls can push any desired modules onto any Streams they own. Here is a simple diagram of a Stream. Figure 25-1. Stream diagram
User-level code will create the Stream initially by opening a STREAMS device. The user program can control and change the modules that are placed on the Stream as well as their order. The user program uses normal system calls to do this: ioctl() , write() , open () , close() , read() . Note that all the pieces of a Stream live in kernel space: the driver, the Stream head, the queues, and the modules are all kernel text or data. The "head" of the Stream is the connection to user space. Data coming from a user process and going to the driver (and probably eventually to a real device) is noted as coming downstream. The opposite direction is called, strangely enough, upstream. Modules, drivers, and Stream heads have a queue for data going from one to the other in each direction. In other words, a queue connects modules (and the driver at one end, and the Stream head at the other). In between any two of these elements is a structure to handle the data being passed from one to the next one in the sequence. Each queue is essentially treated as a read "list" for the appropriate piece, and moving data from one Stream element to another involves putting data (messages) onto the read queue of the appropriate element. Thus, every module will have two queues associated with it: one for data going downstream, which is to be read and processed by this module; and another for data going upstream, which also must be handled. A module will also be able to pass data on to the next module's read queue once any filtering operation is done. |