In this example, suppose that the computer supports different kinds of asynchronous terminals, each logging in on its own port. The port hardware is limited in function; for example, it detects and reports line and modem status, but does not check parity.
Communications software support for these terminals is provided using a STREAMS-implemented asynchronous protocol. The protocol includes a variety of options that are set when a terminal operator dials in to log on. The options are determined by a getty-type STREAMS user-written process, getstrm, which analyzes data sent to it through a series of dialogs (prompts and responses) between the process and terminal operator.
Note: The getstrm process used in this example is a nonexistent process. It is not supported by this system.
The process sets the terminal options for the duration of the connection by pushing modules onto the stream by sending control messages to cause changes in modules (or in the device driver) already on the stream. The options supported include:
These options are set with the following modules:
|CHARPROC||Provides input character-processing functions, including dynamically settable (using control messages passed to the module) character echo and parity checking. The module default settings are meant to echo characters and do not check character parity.|
|CANONPROC||Performs canonical processing on ASCII characters upstream and downstream, this module performs some processing in a different manner from the standard character I/O tty subsystem.|
|ASCEBC||Translates EBCDIC code to ASCII, upstream, and ASCII to EBCDIC, downstream.|
Note: The modules used in this example are nonexistent. They are not supported by this system.
At system initialization a user-written process, getstrm, is created for each tty port. The getstrm process opens a stream to its port and pushes the CHARPROC module onto the stream by use of a I_PUSH operation. Then, the process issues a getmsg system call to the stream and sleeps until a message reaches the stream head. The stream is now in its idle state.
The initial idle stream contains only one pushable module, CHARPROC. The device driver is a limited-function, raw tty driver connected to a limited-function communication port. The driver and port transparently transmit and receive one unbuffered character at a time.
Upon receipt of initial input from a tty port, the getstrm process establishes a connection with the terminal, analyzes the option requests, verifies them, and issues STREAMS subroutines to set the options. After setting up the options, the getstrm process creates a user application process. Later, when the user terminates that application, the getstrm process restores the stream to its idle state by use of subroutines.
The next step is to analyze in more detail how the stream sets up the communications options.
The getstrm process has issued a getmsg system call and is sleeping until the arrival of a message from the stream head. Such a message would result from the driver detecting activity on the associated tty port.
An incoming call arrives at port 1 and causes a ring-detect signal in the modem. The driver receives the ring signal, answers the call, and sends upstream an M_PROTO message containing information indicating an incoming call. The getstrm process is notified of all incoming calls, although it can choose to refuse the call because of system limits. In this idle state, the getstrm process will also accept M_PROTO messages indicating, for example, error conditions such as detection of line or modem problems on the idle line.
The M_PROTO message containing notification of the incoming call flows upstream from the driver into the CHARPROC module. The CHARPROC module inspects the message type, determines that message processing is not required, and passes the unmodified message upstream to the stream head. The stream head copies the message into the getmsg buffers (one buffer for control information, the other for data) associated with the getstrm process and wakes up the process. The getstrm process sends its acceptance of the incoming call with a putmsg system call, which results in a downstream M_PROTO message to the driver.
Then, the getstrm process sends a prompt to the operator with a write subroutine and issues a getmsg system call to receive the response. A read subroutine could have been used to receive the response, but the getmsg system call allows concurrent monitoring for control (M_PROTO and M_PCPROTO) information. The getstrm process will now sleep until the response characters, or information regarding possible error conditions detected by modules or driver, are sent upstream.
The first response, sent upstream in an M_DATA block, indicates that the code set is ASCII and that canonical processing is requested. The getstrm process implements these options by pushing the CANONPROC module onto the stream, above the CHARPROC module, to perform canonical processing on the input ASCII characters.
The response to the next prompt requests even-parity checking. The getstrm process sends an I_STR operation to the CHARPROC module, requesting the module to perform even-parity checking on upstream characters. When the dialog indicates that protocol-option setting is complete, the getstrm process creates an application process. At the end of the connection, the getstrm process will pop the CANONPROC module and then send an I_STR operation to the CHARPROC module requesting that module restore the no-parity idle state (the CHARPROC module remains on the stream).
As a result of the above dialogs, the terminal at port 1 operates in the following configuration:
In similar fashion, an operator at a different type of terminal on port 2 requests a different set of options, resulting in the following configuration:
The resultant streams for the two ports are shown in the Asynchronous Terminal STREAMS diagram. For port 1, the modules in the stream are CANONPROC and CHARPROC.
For port 2, the resultant modules are CANONPROC, ASCEBC, and CHARPROC. The ASCEBC module has been pushed on this stream to translate between the ASCII interface at the downstream side of the CANONPROC module and the EBCDIC interface at the upstream output side of the CHARPROC module. In addition, the getstrm process has sent an I_STR operation to the CHARPROC module in this stream requesting it to disable echo. The resultant modification to the CHARPROC function is indicated by the word "modified" in the right stream of the diagram.
Since the CHARPROC module is now performing no function for port 2, it usually would be popped from the stream to be reinserted by the getstrm process at the end of the connection. However, the low overhead of STREAMS does not require its removal. The module remains on the stream, passing unmodified messages between the ASCEBC module and the driver. At the end of the connection, the getstrm process restores this stream to its idle configuration by popping the added modules and then sending an I_STR operation to the CHARPROC module to restore the echo default.
Note: The tty driver shown in the Asynchronous Terminal STREAMS diagram handles minor devices. Each minor device has a distinct stream connected from user space to the driver. This ability to handle multiple devices is a standard STREAMS feature, similar to the minor device mechanism in character I/O device drivers.
The previous example illustrates basic STREAMS concepts. However, more efficient STREAMS calls or mechanisms could have been used in place of those described earlier.
For example, the initialization process that created a getstrm process for each tty port could have been implemented as a "supergetty" by use of the STREAMS-related poll subroutine. The poll subroutine allows a single process to efficiently monitor and control multiple streams. The "supergetty" process would handle all of the stream and terminal protocol initialization and would create application processes only for established connections.
Otherwise, the M_PROTO notification sent to the getstrm process could be sent by the driver as an M_SIG message that causes a specified signal to be sent to the process. Error and status information can also be sent upstream from a driver or module to user processes using different message types. These messages will be transformed by the stream head into a signal or error code.
Finally, a I_STR operation could be used in place of a putmsg system call M_PROTO message to send information to a driver. The sending process must receive an explicit response from an I_STR operation by a specified time period, or an error will be returned. A response message must be sent upstream by the destination module or driver to be translated into the user response by the stream head.
This section describes STREAMS kernel operations and associates them, where relevant, with user-level system calls. As a result of initializing operations and pushing a module, the stream for port 1 has the configuration shown in the Operational Stream diagram.
Here, the upstream QUEUE is also referred to as the read QUEUE, reflecting the message flow in response to a read subroutine. The downstream QUEUE is referred to as the write QUEUE.
In the example, read-side processing consists of driver processing, CHARPROC processing, and CANONPROC processing.
In the example, the user process has blocked on the getmsg subroutine while waiting for a message to reach the stream head, and the device driver independently waits for input of a character from the port hardware or for a message from upstream. Upon receipt of an input character interrupt from the port, the driver places the associated character in an M_DATA message, allocated previously. Then, the driver sends the message to the CHARPROC module by calling the CHARPROC upstream put procedure. On return from the CHARPROC module, the driver calls the allocb utility routine to get another message for the next character.
The CHARPROC module has both put and service procedures on its read side. As a result, the other QUEUEs in the modules also have put and service procedures.
When the driver calls the CHARPROC read-QUEUE put procedure, the procedure checks private data flags in the QUEUE. In this case, the flags indicate that echoing is to be performed (recall that echoing is optional and that we are working with port hardware that cannot automatically echo). The CHARPROC module causes the echo to be transmitted back to the terminal by first making a copy of the message with a STREAMS utility. Then, the CHARPROC module uses another utility to obtain the address of its own write QUEUE. Finally, the CHARPROC read put procedure calls its write put procedure and passes it the message copy. The write procedure sends the message to the driver to effect the echo and then returns to the read procedure.
This part of read-side processing is implemented with put procedures so that the entire processing sequence occurs as an extension of the driver input-character interrupt. The CHARPROC read and write put procedures appear as subroutines (nested in the case of the write procedure) to the driver. This manner of processing is intended to produce the character echo in a minimal time frame.
After returning from echo processing, the CHARPROC read put procedure checks another of its private data flags and determines that parity checking should be performed on the input character. Usually, parity would be checked as part of echo processing. However, for this example, parity is checked only when the characters are sent upstream. As a result, parity checking can be deferred along with the canonical processing. The CHARPROC module uses the putq utility to schedule the (original) message for parity-check processing by its read service procedure. When the CHARPROC read service procedure is complete, it forwards the message to the read put procedure of the CANONPROC module. If parity checking were not required, the CHARPROC put procedure would call the CANONPROC put procedure directly.
The CANONPROC module performs canonical processing. As implemented, all read QUEUE processing is performed in its service procedure. The CANONPROC put procedure simply calls the putq utility to schedule the message for its read service procedure, and then exits. The service procedure extracts the character from the message buffer and places it in the line buffer contained in another M_DATA message it is constructing. Then, the message containing the single character is returned to the buffer pool. If the character received was not an end-of-line, the CANONPROC module exits. Otherwise, a complete line has been assembled and the CANONPROC module sends the message upstream to the stream head, which unlocks the user process from the getmsg subroutine call and passes it the contents of the message.
The write side of the stream carries two kinds of messages from the user process: streamio messages for the CHARPROC module and M_DATA messages to be output to the terminal.
The streamio messages are sent downstream as a result of an I_STR operation. When the CHARPROC module receives a streamio message type, it processes the message contents to modify internal QUEUE flags and then uses a utility to send an acknowledgment message upstream (read side) to the stream head. The stream head acts on the acknowledgment message by unblocking the user from the streamio message.
For terminal output, it is presumed that M_DATA messages, sent by write subroutines, contain multiple characters. In general, STREAMS returns to the user process immediately after processing the write subroutine so that the process may send additional messages. Flow control will eventually block the sending process. The messages can queue on the write side of the driver because of character-transmission timing. When a message is received by the driver's write put procedure, the procedure will use the putq utility to place the message on its write-side service message queue if the driver is currently transmitting a previous message buffer. However, there is generally no write-QUEUE service procedure in a device driver. Driver output-interrupt processing takes the place of scheduling and performs the service procedure functions, removing messages from the queue.
For reasons of efficiency, a module implementation would generally avoid placing one character per message and using separate routines to echo and parity-check each character, as done in this example. Nevertheless, even this design yields potential benefits. Consider a case in which an alternative and more intelligent port hardware was substituted. If the hardware processed multiple input characters and performed the echo and parity-checking functions of the CHARPROC module, then the new driver could be implemented to present the same interface as the CHARPROC module. Other modules such as CANONPROC could continue to be used without modification.