text:
Part I of this manual contains:
Chapter 1, Overview of STREAMS, Overview of STREAMS
Describes how to construct, use, and dismantle a stream using STREAMS-related system calls
Chapter 2, STREAMS Application-Level Components, STREAMS Application-Level Components
Describes how the kernel interprets system calls being passed from an application
Chapter 3, STREAMS Application-Level Mechanisms, STREAMS Application-Level Mechanisms
Shows how the kernel interprets system calls being passed from an application
Chapter 4, Application Access to the STREAMS Driver and Module Interfaces, STREAMS Driver and Module Interfaces
Describes communication between processes using STREAMS-based pipes and named pipes.
Chapter 5, STREAMS Administration, STREAMS Administration
Describes the tools available to administer STREAMS.
Chapter 6, Pipes and Queues, Pipes and Queues
Describes communication between processes using STREAMS-based pipes and named pipes.
This chapter provides a foundation for later chapters. Background and simple definitions are followed by an overview of the STREAMS mechanisms. Because the application developer is concerned with a different subset of STREAMS interfaces than the kernel-level developer, application and kernel levels are described separately.
STREAMS is a general, flexible programming model for UNIX system communication services. STREAMS defines standard interfaces for character input/output (I/O) within the kernel, and between the kernel and the rest of the UNIX system. The mechanism consists of a set of system calls, kernel resources, and kernel routines.
STREAMS enables you to create modules to provide standard data communications services and then manipulate the modules on a stream. From the application level, modules can be dynamically selected and interconnected. No kernel programming, compiling, and link editing are required to create the interconnection.
STREAMS provides an effective environment for kernel services and drivers requiring modularity. STREAMS parallels the layering model found in networking protocols. For example, STREAMS is suitable for:
Implementing network protocols
Developing character device drivers
Developing network controllers (for example, for an Ethernet card)
I/O terminal services
The fundamental STREAMS unit is the stream. A stream is a full-duplex bidirectional data-transfer path between a process in user space and a STREAMS driver in kernel space. A stream has three parts: a stream head, zero or more modules, and a driver.
The capitalized word "STREAMS" refers to the STREAMS programming model and facilities. The word "stream" refers to an instance of a full-duplex path using the model and facilities between a user application and a driver.
A stream is a data path that passes data in both directions between a STREAMS driver in kernel space, and a process in user space. An application creates a stream by opening a STREAMS device (see Figure 1–1).
A stream head is the end of the stream nearest the user process. It is the interface between the stream and the user process. When a STREAMS device is first opened, the stream consists of only a stream head and a STREAMS driver.
A STREAMS module is a defined set of kernel-level routines and data structures. A module does "black-box" processing on data that passes through it. For example, a module converts lowercase characters to uppercase, or adds network routing information. A STREAMS module is dynamically pushed on the stream from the user level by an application. Full details on modules and their operation are covered in Chapter 10, STREAMS Modules.
A STREAMS device driver is a character device driver that implements the STREAMS interface. A STREAMS device driver exists below the stream head and any modules. It can act on an external I/O device, or it can be an internal software driver, called a pseudo-device driver. The driver transfers data between the kernel and the device. The interfaces between the driver and kernel are known collectively as the Solaris operating environment Device Driver Interface/Driver Kernel Interface (Solaris operating environment DDI/DKI). The relationship between the driver and the rest of the UNIX kernel is explained in Writing Device Drivers. Details of device drivers are explained in Chapter 9, STREAMS Drivers.
Data on a stream is passed in the form of messages. Messages are the means by which all I/O is done under STREAMS. Each stream head, STREAMS module, and driver has a read sideand a write side. When messages go from one module's read side to the next module's read side, they are said to be traveling upstream. Messages passing from one module's write side to the next module's write side are said to be traveling downstream. Kernel-level operation of messages is discussed in Message Components.
Each stream head, driver, and module has its own pair of queues, one queue for the read side and one queue for the write side. Messages are ordered into queues, generally on a first-in, first-out basis (FIFO), according to priorities associated with them. Kernel-level details of queues are covered in Structure of a Message Queue.
To communicate with a STREAMS device, an application's process uses read(2), write(2), getmsg(2), getpmsg(2), putmsg(2), putpmsg(2), and ioctl(2) to transmit or receive data on a stream.
From the command line, configure a stream with autopush(1M). From within an application, configure a stream with ioctl(2) as described in streamio(7I).
The ioctl(2) interface performs control operations on and through device drivers that cannot be done through the read(2) and write(2) interfaces. ioctl(2) operations include pushing and popping modules on and off the stream, flushing the stream, and manipulating signals and options. Certain ioctl(2) commands for STREAMS operate on the whole stream, not just the module or driver. The streamio(7I) manual page describes STREAMS ioctl(2) commands. Chapter 4, Application Access to the STREAMS Driver and Module Interfaces details interstream communications.
The modularity of STREAMS allows one or more upper streams to route data into one or more lower streams. This process is defined as multiplexing (mux). Example configurations of multiplexers are described in Configuring Multiplexed Streams.
Polling within STREAMS enables a user process to detect events occurring at the stream head, specifying the event to look for and the amount of time to wait for it to happen. An application might need to interact with multiple streams. The poll(2) system call enables applications to detect events that occur at the head of one or more streams. Chapter 3, STREAMS Application-Level Mechanisms describes polling.
Flow control regulates the rate of message transfer between the user process, stream head, modules, and driver. With flow control, a module that cannot process data at the rate being sent can queue the data to avoid flooding modules upstream. Flow control is local to each module or driver, and is voluntary. Chapter 8, STREAMS Kernel-Level Mechanisms describes flow control.
The STREAMS framework is most useful when modularity and configurability are issues. For instance, network drivers, terminal drivers, and graphics I/O device drivers benefit from using STREAMS. Modules can be pushed (added) and popped (removed) to create desired program behavior.
STREAMS is general enough to provide modularity between a range of protocols. It is a major component in networking support utilities for UNIX System V because it facilitates communication between network protocols.
An application opens a STREAMS device, which creates the stream head to access the device driver. The stream head packages the data from the user process into STREAMS messages, and passes it downstream into kernel space. One or more cooperating modules can be pushed on a stream between the stream head and driver to customize the stream and perform any of a range of tasks on the data before passing it on. On the other hand, a stream might consist solely of the stream head and driver, with no module at all.
To a user application, a STREAMS device resembles an ordinary character I/O device, as it has one or more nodes associated with it in the file system, and is opened by calling open(2).
The file system represents each device as a special file. There is an entry in the file for the major device number, identifying the actual device driver that will activate the device. There are corresponding separate minor device numbers for each instance of a particular device, for example, for a particular port on a serial card, or a specific pseudo-terminal such as those used by a windowing application.
Different minor devices of a driver cause a separate stream to be connected between a user process and the driver. The first open call creates the stream; subsequent open calls respond with a file descriptor referencing that stream. If the same minor device is opened more than once, only one stream is created.
However, drivers can support a user process getting a dedicated stream without the application distinguishing which minor device is used. In this case, the driver selects any unused minor device to be used by the application. This special use of a minor device is called cloning. Chapter 9, STREAMS Drivers describes properties and behavior of clone devices.
Once a device is opened, a user process can send data to the device by calling write(2), and receive data from the device by calling read(2). Access to STREAMS drivers using read and write is compatible with the traditional character I/O mechanism. STREAMS-specific applications also can call getmsg(2), getpmsg(2), putmsg(2), and putpmsg(2) to pass data to and from the stream.
The close(2) interface closes a device and dismantles the associated stream when the last open reference to the stream is closed. The exit(2) interface terminates the user process and closes all open files.
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.
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.
#include <sys/fcntl.h> #include <stdio.h> main() { char buf[1024]; int fd, count; if ((fd = open("/dev/ttya", O_RDWR)) < 0) { perror("open failed"); exit(1); } while ((count = read(fd, buf, sizeof(buf))) > 0) { if (write(fd, buf, count) != count) { perror("write failed"); break; } } exit(0); }
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).
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.
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.
Developers implementing STREAMS device drivers and STREAMS modules use a set of STREAMS-specific functions and data structures. This section describes some basic kernel-level STREAMS concepts.
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.
Both queues in a module contain messages, processing procedures, and private data.
Blocks of data that pass through, and can be operated on by, a module.
Individual put and service routines on the read and write queues process messages. The put procedure passes messages from one queue to the next in a stream and is required for each queue. It can do additional message processing. The service procedure is optional and does deferred processing of messages. These procedures can send messages either upstream or downstream. Both procedures can also modify the private data in their module.
Data private to the module (for example, state information and translation tables).
Entry points must be provided. The open routine is invoked when the module is pushed onto the stream or the stream is reopened. The close is invoked when the module is popped or the stream is closed.
A module is initialized by either an I_PUSH ioctl(2), or pushed automatically during an open if a stream has been configured by the autopush(1M) mechanism, or if that stream is reopened.
A module is disengaged by close or the I_POP ioctl(2).
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.
A driver:
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.
All kernel-level input and output under STREAMS is based on messages. STREAMS messages are built in sets of three:
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.
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.
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.
A queue is an interface between a STREAMS driver or module and the rest of the stream (see queue(9S)). The queue structure holds the messages, and points to the STREAMS processing routines that should be applied to a message as it travels through a module. STREAMS modules and drivers must explicitly place messages on a queue, for example, when flow control is used.
Each open driver or pushed module has a pair of queues allocated, one for the read side and one for the write side. Queues are always allocated in pairs. Kernel routines are available to access each queue's mate. The queue's put or service procedure can add a message to the current queue. If a module does not need to queue messages, its put procedure can call the neighboring queue's put procedure.
The queue's service procedure deals with messages on the queue, usually by removing successive messages from the queue, processing them, and calling the put procedure of the next module in the stream to pass the message to the next queue. Chapter 7, STREAMS Framework – Kernel Level discusses the service and put procedures in more detail.
Each queue also has a pointer to an open and close routine. The open routine of a driver is called when the driver is first opened and on every successive open of the stream. The open routine of a module is called when the module is first pushed on the stream and on every successive open of the stream. The close routine of the module is called when the module is popped (removed) off the stream, or at the time of the final close. The close routine of the driver is called when the last reference to the stream is closed and the stream is dismantled.
Previously, streams were described as stacks of modules, with each module (except the head) connected to one upstream module and one downstream module. While this can be suitable for many applications, others need the ability to multiplex streams in a variety of configurations. Typical examples are terminal window facilities, and internetworking protocols (that might route data over several subnetworks).
An example of a multiplexer is a module that multiplexes data from several upper streams to a single lower stream. An upper stream is one that is upstream from the multiplexer, and a lower stream is one that is downstream from the multiplexer. A terminal windowing facility might be implemented in this fashion, where each upper stream is associated with a separate window.
A second type of multiplexer might route data from a single upper stream to one of several lower streams. An internetworking protocol could take this form, where each lower stream links the protocol to a different physical network.
A third type of multiplexer might route data from one of many upper streams to one of many lower streams.
The STREAMS mechanism supports the multiplexing of streams through special pseudo-device drivers. A user can activate a linking facility mechanism within the STREAMS framework to dynamically build, maintain, and dismantle multiplexed stream configurations. Simple configurations like those shown previously can be combined to form complex, multilevel multiplexed stream configurations.
STREAMS multiplexing configurations are created in the kernel by interconnecting multiple streams. Conceptually, a multiplexer can be divided into two components—the upper multiplexer and the lower multiplexer. The lower multiplexer acts as a stream head for one or more lower streams. The upper multiplexer acts as a device for one or more upper streams. How data is passed between the upper and lower multiplexer is up to the implementation. Chapter 13, STREAMS Multiplex Drivers covers implementing multiplexers.
The Solaris operating environment kernel is multithreaded to make effective use of symmetric shared-memory multiprocessor computers. All parts of the kernel, including STREAMS modules and drivers, must ensure data integrity in a multiprocessing environment. For the most part, developers must ensure that concurrently running kernel threads do not attempt to manipulate the same data at the same time. The STREAMS framework provides multithreaded (MT) STREAMS perimeters, which provides the developer with control over the level of concurrency allowed in a module. The DDI/DKI provides several advisory locks for protecting data. See Chapter 12, Multithreaded STREAMS for more information.
Using STREAMS, you can create modules that present a service interface to any neighboring module or device driver, or between the top module and a user application. A service interface is defined in the boundary between two neighbors.
In STREAMS, a service interface is a set of messages and the rules that allow these messages to pass across the boundary. A module using a service interface, for example, receives a message from a neighbor and responds with an appropriate action (perhaps sending back a request to retransmit) depending on the circumstances.
You can stack a module anywhere in a stream, but connecting sequences of modules with compatible protocol service interfaces is better. For example, a module that implements an X.25 protocol layer, as shown in Figure 1–6, presents a protocol service interface at its input and output sides. In this case, other modules should be connected to the input and output side if they have the compatible X.25 service interface only.
With STREAMS, you can manipulate modules from the user application level, interchange modules with common service interfaces, and change the service interface to a STREAMS user process. These capabilities yield further benefits when working with networking services and protocols:
User-level programs can be independent of underlying protocols and physical communication media.
Network architectures and higher-level protocols can be independent of underlying protocols, drivers, and physical communication media.
Higher-level services can be created by selecting and connecting lower-level services and protocols.
The following examples show the benefits of STREAMS capabilities for creating service interfaces and manipulating modules. These examples are only illustrations and do not necessarily reflect real situations.
Figure 1–6 shows how an X.25 protocol module can work with different drivers on different machines by using compatible service interfaces. The X.25 protocol module interfaces are Connection Oriented Network Service (CONS) and Link Access Protocol – Balanced (LAPB).
You can alternate protocol modules and device drivers on a system if the alternates are implemented to an equivalent service interface.
Figure 1–7 shows how STREAMS can move functions between kernel software and front-end firmware. A common downstream service interface lets the transport protocol module be independent of the number or type of modules below it. The same transport module will connect without modification to either an X.25 module or X.25 driver that has the same service interface.
By shifting functions between software and firmware, you can produce cost-effective, functionally equivalent systems over a wide range of configurations. This means you can swiftly incorporate technological advances. The same transport protocol module can be used on a lower-capacity machine, where economics preclude the use of front-end hardware, and also on a larger scale system where a front-end is economically justified.
Figure 1–8 shows the same canonical module (for example, one that provides delete and kill processing on character strings) reused in two different streams. This module would typically be implemented as a filter, with no service interface. In both cases, a TTY interface is presented to the stream's user process because the module is nearest the stream head.
This chapter shows how to construct, use, and dismantle a stream using STREAMS-related system calls. It provides a general discussion of the relationship between STREAMS components in a simple streams example.
The stream head provides the interface between the stream and an application program. After a stream has been opened, STREAMS-related system calls enable a user process to insert and delete (push and pop) modules. That process can then communicate with and control the operation of the stream head, 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.
Table 2-1 offers an overview of some basic STREAMS-related system calls.Table 2–1 Summary of Basic STREAMS-related System Calls
Opens a stream
Closes a stream
Reads data from a stream
Writes data to a stream
Controls a stream
Receives a message at the stream head
Receives a priority message at the stream head
Sends a message downstream
Sends a priority message downstream
Identifies files on which a user can send or receive messages, or on which certain events have occurred (not restricted to STREAMS, although historically it was)
Creates a bidirectional channel that provides a communication path between multiple processes
Sections 1, 2, 3, 7, and 9 of the online manual pages (man pages) contain all the STREAMS information.
The open(2) system call recognizes a STREAMS special 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 way as with traditional character files. ioctl(2) lets users perform functions specific to a particular device. STREAMS ioctl(2) commands (see the streamio(7I) man page) support a variety of functions to access and control streams. The final close(2) on a stream dismantles it.
The poll(2) system call provides a mechanism for multiplexing input/output over a set of file descriptors that reference open files. putmsg(2) and getmsg(2) and the putpmsg(2) and getpmsg(2) send and receive STREAMS messages, and can act on STREAMS modules and drivers through a service interface.
One way to construct a stream is to callopen(2) to open a STREAMS special file. If the open call is the initial file open, a stream is created. (There is one stream for each major or minor device pair.) If this open is not the initial open of this stream, the open procedures of the driver and all pushable modules on the stream are called.
Sometimes a user process needs to connect a new stream to a driver regardless of which minor device is used to access the driver. Instead of the user process polling for an available minor device node, STREAMS provides a facility called clone open. If a STREAMS driver is implemented as a clone device, a single node in the file system may be opened to access any unused device that the driver controls. This special node guarantees that the user is allocated a separate stream to the driver for every open call. Each stream is associated with an unused major or minor device, so the total number of streams that can connect to a particular clone driver is limited to the number of minor devices configured for the driver.
Clone devices are used, for example, in a networking environment where a protocol pseudo-device driver requires each user to open a separate stream to establish communication.
You can open a clone device in two ways. The first is to create a node with the major number of the clone device (--) and a minor number corresponding to the major number of the device to be cloned. For example /dev/ps0 might have a major number of 50 and a minor number of 0 for normal opens. The clone device may have a major number of 40. By creating a node /dev/ps with a major number of 40 and a minor number of 50, a clonable device is created. In this case, the driver is passed a special flag (CLONEOPEN) that tells it to return a unique minor device number.
The second way is to have the driver open itself as a clone device, that is, the driver returns a unique minor number.
When a stream is already open, further opens of the same device call the open routines of all modules and the driver on that stream. In this case, a driver is opened and a module is pushed on a stream. When a push occurs, the module open routine is called. If another open of the same device is made, the open routine of the module is called, followed by the open routine of the driver. This is opposite to the initial order of opens when the stream is created.
STREAMS also offers autopush. On an open(2) system call, a preconfigured list is checked for modules to be pushed. All modules in this list are pushed before the open(2) returns. For more information see the autopush(1M) and sad(7D) man pages.
There is one stream head per stream. The stream head, which is initiated by the first open call, is created from a data structure and a pair of queue structures. The content of the stream head and queues is initialized with predetermined values, including the stream head processing procedures.
STREAMS queues are allocated in pairs. One queue is always the upstream or read-side; the other is the downstream or write-side. These queues hold the messages, and tell the kernel which processing routines apply to each message passing through a module. The queue structure type is queue_t. Fields in the queue data structure are detailed in queue(9S).
As part of constructing a stream, a module can be added (pushed) with an I_PUSH ioctl(2) (see streamio(7I)) 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.
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 are multiple occurrences of the 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 I_POP ioctl(2) (see streamio(7I))) 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). The stream head then connects to the component previously below the popped module and releases 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.
You can also restrict which modules can be popped with I_POP calls by placing an anchor in the stream at any module you want to "lock down." The I_ANCHOR ioctl prevents a module from being popped except by a privileged process. See STREAMS Anchors for more information about working with anchors.
The last close to a STREAMS device dismantles the stream. Dismantling consists of popping any modules on the stream and closing the driver. Before a module is popped, the close(2) 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(2) 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) is clear, close(2) waits up to 15 seconds for each module to drain and up to 15 seconds for the driver to drain, see open(2) and fcntl(2). The default close delay is 15 seconds, but this can be changed on a per-stream basis with the I_SETCLTIME ioctl(2).
The close delay is independent of any delay that the module or driver's close routine itself chooses to impose. 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 stream head structures are released.
This example extends the communications device-echoing example shown in Simple Stream Example. The module in this example converts (change case, delete, duplicate) selected alphabetic characters.
The complete listing of the module is on the CD.
An application can 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 receives a command and a corresponding string of characters from the user. All data passing through the module is inspected for instances of characters in this string. Whatever operation the command requires is performed on all characters that match the string.
#include <string.h> #include <fcntl.h> #include <stropts.h> #define BUFLEN 1024 /* * These definitions would typically be * found in a header file for the module */ #define XCASE 1 /* change alphabetic case of char */ #define DELETE 2 /* delete char */ #define DUPLICATE 3 /* duplicate char */ main() { char buf[BUFLEN]; int fd, count; struct strioctl strioctl;
The first step is to establish a stream to the communications driver and insert the character conversion module. This is accomplished by first opening (fd = open) then calling ioctl(2) to push the chconv module, as shown in the sequence of system calls in Example 2–2.
if ((fd = open("/dev/term/a", O_RDWR)) < 0) { perror("open failed"); exit(1); } if (ioctl(fd, I_PUSH, "chconv") < 0) { perror("ioctl I_PUSH failed"); exit(2); }
The I_PUSH ioctl(2) call directs the stream head to insert the character conversion module between the driver and the stream head. The example illustrates an important difference between STREAMS drivers and modules. Drivers are accessed through a node or nodes in the file system (in this case /dev/term/a) and are opened just like other devices. Modules, on the other hand, are not devices. Identify modules through a separate naming convention, and insert them into a stream using I_PUSH or autopush. Figure 2–1 shows creation of the stream.
Modules are stacked onto a stream and removed from a stream in last-in, first-out (LIFO) order. Therefore, if a second module is pushed onto this stream, it is inserted between the stream head and the character conversion module.
The next step in this example is to pass the commands and corresponding strings to the character conversion module. This can be accomplished by calling ioctl(2) to invoke the character conversion module.
Example 2–3 uses the conventional I_STR ioctl(2), an indirect way of passing commands and data pointers. Example 2–4 shows the data structure for I_STR.
Instead of I_STR, some systems support transparent ioctls in which calls can be made directly. For example, a module calls I_PUSH. Both modules and drivers can process ioctls without requiring user programs to first encapsulate them with I_STR (that is, the ioctls in the examples would look like ioctl(fd,DELETE,"AEIOU");). This style of call works only for modules and drivers that have been converted to use the new facilities that also accept the I_STR form.
/* change all uppercase vowels to lowercase */ strioctl.ic_cmd = XCASE; strioctl.ic_timout = 0; /* default timeout (15 sec) */ strioctl.ic_dp = "AEIOU"; strioctl.ic_len = strlen(strioctl.ic_dp); if (ioctl(fd, I_STR, &strioctl) < 0) { perror("ioctl I_STR failed"); exit(3); } /* delete all instances of the chars 'x' and 'X' */ strioctl.ic_cmd = DELETE; strioctl.ic_dp = "xX"; strioctl.ic_len = strlen(strioctl.ic_dp); if (ioctl(fd, I_STR, &strioctl) < 0) { perror("ioctl I_STR failed"); exit(4); }
In Example 2–3, the module changes all uppercase vowels to lowercase, and deletes all instances of either uppercase or lowercase "x". ioctl(2) requests are issued indirectly, using I_STR ioctl(2) (see streamio(7I)). 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 described in streamio(7I) and has the format shown in the following example.
struct strioctl { int ic_cmd; /* ioctl request */ int ic_timout; /* ACK/NAK timeout */ int ic_len; /* length of data argument */ char *ic_dp; /* ptr to data argument */ };
Specifies the number of seconds an I_STR request should wait for an acknowledgement before timing out.
Points to the data. In the example, two separate commands are sent to the character-conversion module:
The first command 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 command 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. ic_dp 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, then sends the message downstream. Any module that cannot process the command in ic_cmd passes the message further downstream. The request is processed by the module or driver closest to the stream head that understands the command specified by ic_cmd. ioctl(2) blocks up to ic_timout seconds, waiting for the target module or driver to respond with either a positive or negative acknowledgement message. If an acknowledgement is not received in ic_timout seconds, ioctl(2) fails.
Only one ioctl(2) can be active on a stream at one time, regardless of whether it is issued with I_STR. Further requests will block until the active ioctl(2) is acknowledged and the system call concludes.
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 is set on return to indicate the amount of data returned. The remainder of this example is identical to Example 1–1 in Chapter 1, Overview of STREAMS.
while ((count = read(fd, buf, BUFLEN)) > 0) { if (write(fd, buf, count) != count) { perror("write failed"); break; } } exit(0); }
Notice that the character-conversion processing was realized with no change to the communications driver.
exit(2) dismantles the stream before terminating the process. The character conversion module is removed from the stream automatically when it is closed. Alternatively, remove modules from a stream using I_POP ioctl(2) which is described in streamio(7I). 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.
Several other ioctl(2) requests support STREAMS operations, such as determining if a given module is on a stream, or flushing the data on a stream. streamio(7I) describes these requests.
The previous chapters described the components of a stream from an application level. This chapter explains how those components work together. It shows how the kernel interprets system calls being passed from an application, so that driver and module developers can know what structures are being passed.
Messages are the communication medium between the user application process and the various components of the stream. This chapter describes the path they travel and the changes that occur to them. Chapter 8, STREAMS Kernel-Level Mechanisms covers the underlying mechanics of the kernel.
The put(9E) and srv(9E) interfaces process messages as they pass through the queue. Messages are generally processed by type, resulting in a modified message, one or more new messages, or no message at all. The message usually continues in the same direction it was passing through the queue, but can be sent in either direction. A put(9E) procedure can place messages on its queue as they arrive, for later processing by the srv(9E) procedure. For a more detailed explanation of put(9E) and srv(9E), see Chapter 8, STREAMS Kernel-Level Mechanisms.
Some kernel operations are explained here to show you how to manipulate the driver or module appropriately.
STREAMS messages differ according to their intended purpose and their queueing priority. The contents of certain message types can be transferred between a process and a stream using system calls. Appendix A, Message Types describes message types in detail.
The stream head responds to a message by altering the processing associated with certain system calls. Six stream head characteristics can be modified. Four characteristics correspond to fields contained in queue (packet sizes — minimum and maximum, and watermarks — high and low). Packet sizes are discussed in this chapter. Watermarks are discussed in Flush Handling in Chapter 4, Application Access to the STREAMS Driver and Module Interfaces.
The read options (so_readopt) specify two sets of three modes that can be set by the I_SRDOPT ioctl(2) (see streamio(7I)). Byte-stream mode approximately models pipe data transfer. Message nondiscard mode is similar to a TTY in canonical mode.
The first set of bits, RMODEMASK, deals with data and message boundaries, as shown in Table 3–1.Table 3–1 Data and Message Boundaries
The read(2) call finishes when the byte count is satisfied, the stream head read queue becomes empty, or a zero length message is encountered. A zero length message is put back in the queue. A subsequent read returns 0 bytes.
The read(2) call finishes when the byte count is satisfied or a message boundary is found, whichever comes first. Any data remaining in the message is put back on the stream head read queue.
The read(2) call finishes when the byte count is satisfied or a message boundary is found. Any data remaining in the message is discarded up to the message boundary.
The second set of bits, RPROTMASK, specifies the treatment of protocol messages by the read(2) system call as shown in Table 3–2.Table 3–2 How read(2) Treats Protocol Messages
The read(2) call fails with EBADMSG if an M_PROTO or M_PCPROTO message is at the front of the stream head read queue. This is the default operation protocol.
The read(2) call discards any M_PROTO or M_PCPROTO blocks in a message, delivering the M_DATA blocks to the user.
The read(2) call converts the M_PROTO and M_PCPROTO message blocks to M_DATA blocks, treating the entire message as data.
The write(2) mode is set using the value of the argument arg. Legal bit settings for arg are: SNDZERO—Send a zero-length message downstream when the write of 0 bytes occurs. To avoid sending a zero-length message when a write of 0 bytes occurs, this bit must not be set in arg. On failure, errno can be set to EINVAL—arg is above the legal value.
Any delay in processing messages causes message queues to grow. Normally, queued messages are handled in a first-in, first-out (FIFO) manner. However, certain conditions can require that associated messages (for instance, an error message) reach their stream destination as rapidly as possible. For this reason messages are assigned priorities using a priority band associated with each message. Ordinary messages have a priority of zero. High-priority messages are high priority by nature of their message type. Their priority band is ignored. By convention, they are not affected by flow control. Figure 3–1 shows how messages are ordered in a queue according to priority.
When a message is queued, it is placed after the messages of the same priority already in the queue (in other words, FIFO within their order of queueing). This affects the flow-control parameters associated with the band of the same priority. Message priorities range from 0 (normal) to 255 (highest). This provides up to 256 bands of message flow within a stream. Expedited data can be implemented with one extra band of flow (priority band 1) of data. This is shown in Figure 3–2.
The I_FLUSHBAND, I_CKBAND, I_GETBAND, I_CANPUT, and I_ATMARK ioctl(2)s support multiple bands of data flow. The I_FLUSHBAND ioctl(2) allows a user to flush a particular band of messages. Flush Handling discusses it in more detail.
The I_CKBAND ioctl(2) checks if a message of a given priority exists on the stream head read queue. Its interface is:
ioctl (fd, I_CKBAND, pri);
The call returns 1 if a message of priority pri exists on the stream head read queue and 0 if no message of priority pri exists. If an error occurs, -1 is returned. Note that pri should be of type int.
The I_GETBAND ioctl(2) checks the priority of the first message on the stream head read queue. The interface is:
ioctl (fd, I_GETBAND, prip);
The call results in the integer referenced by prip being set to the priority band of the message on the front of the stream head read queue.
The I_CANPUT ioctl(2) checks if a certain band is writable. Its interface is:
ioctl (fd, I_CANPUT, pri);
The return value is 0 if the priority band pri is flow controlled, 1 if the band is writable, and -1 on error.
A module or driver can mark a message. This supports the ability of the Transmission Control Protocol (TCP) to indicate to the user the last byte of out-of-band data. Once marked, a message sent to the stream head causes the stream head to remember the message. A user can check whether the message on the front of its stream head read queue is marked with the I_ATMARK ioctl(2). If a user is reading data from the stream head, there are multiple messages on the read queue, and one of those messages is marked, the read(2) terminates when it reaches the marked message and returns the data only up to the marked message. Successive reads can return the rest of the data. Chapter 4, Application Access to the STREAMS Driver and Module Interfaces discusses this in more detail.
The I_ATMARK ioctl(2) has the format:
ioctl (fd, I_ATMARK, flag);
where flag can be either ANYMARK or LASTMARK. ANYMARK indicates that the user wants to check whether any message is marked. LASTMARK indicates that the user wants to see whether the message is the one and only one marked in the queue. If the test succeeds, 1 is returned. On failure, 0 is returned. If an error occurs, -1 is returned.
The first routine presented, inter_open, opens the protocol driver device file specified by path and binds the protocol address contained in addr so that it can receive data. On success, the routine returns the file descriptor associated with the open stream; on failure, it returns -1 and sets errno to indicate the appropriate UNIX system error value. Example 3–1 shows the inter_open routine.
inter_open (char *path, oflags, addr) { int fd; struct bind_req bind_req; struct strbuf ctlbuf; union primitives rcvbuf; struct error_ack *error_ack; int flags; if ((fd = open(path, oflags)) < 0) return(-1); /* send bind request msg down stream */ bind_req.PRIM_type = BIND_REQ; bind_req.BIND_addr = addr; ctlbuf.len = sizeof(struct bind_req); ctlbuf.buf = (char *)&bind_req; if (putmsg(fd, &ctlbuf, NULL, 0) < 0) { close(fd); return(-1); } }
After opening the protocol driver, inter_open packages a bind request message to send downstream. putmsg is called to send the request to the service provider. The bind request message contains a control part that holds a bind_req structure, but it has no data part. ctlbuf is a structure of type strbuf, and it is initialized with the primitive type and address. Notice that the maxlen field of ctlbuf is not set before calling putmsg. That is because putmsg ignores this field. The dataptr argument to putmsg is set to NULL to indicate that the message contains no data part. The flags argument is 0, which specifies that the message is not a high-priority message.
After inter_open sends the bind request, it must wait for an acknowledgement from the service provider, as Example 3–2 shows.
/* wait for ack of request */ ctlbuf.maxlen = sizeof(union primitives); ctlbuf.len = 0; ctlbuf.buf = (char *)&rcvbuf; flags = RS_HIPRI; if (getmsg(fd, &ctlbuf, NULL, &flags) < 0) { close(fd); return(-1); } /* did we get enough to determine type? */ if (ctlbuf.len < sizeof(long)) { close(fd); errno = EPROTO; return(-1); } /* switch on type (first long in rcvbuf) */ switch(rcvbuf.type) { default: close(fd); errno = EPROTO; return(-1); case OK_ACK: return(fd); case ERROR_ACK: if (ctlbuf.len < sizeof(struct error_ack)) { close(fd); errno = EPROTO; return(-1); } error_ack = (struct error_ack *)&rcvbuf; close(fd); errno = error_ack->UNIX_error; return(-1); } }
getmsg is called to retrieve the acknowledgement of the bind request. The acknowledgement message consists of a control part that contains either an OK_ACK or an error_ack structure, and no data part.
The acknowledgement primitives are defined as high-priority messages. Messages are queued in a first-in, first-out (FIFO) manner within their priority at the stream head. The STREAMS mechanism allows only one high-priority message per stream at the stream head at one time. Any additional high-priority messages are discarded on reaching the stream head. (There can be only one high-priority message present on the stream head read queue at any time.) High-priority messages are particularly suitable for acknowledging service requests when the acknowledgement should be placed ahead of any other messages at the stream head.
Before calling getmsg, this routine must initialize the strbuf structure for the control part. buf should point to a buffer large enough to hold the expected control part, and maxlen must be set to indicate the maximum number of bytes this buffer can hold.
Because neither acknowledgement primitive contains a data part, the dataptr argument to getmsg is set to NULL. The flagsp argument points to an integer containing the value RS_HIPRI. This flag indicates that getmsg should wait for a STREAMS high-priority message before returning. This catches the acknowledgement primitives that are priority messages. Otherwise, if the flag is zero, the first message is taken. With RS_HIPRI set, even if a normal message is available, getmsg blocks until a high-priority message arrives.
On return from getmsg, check the len field to ensure that the control part of the retrieved message is an appropriate size. The example then checks the primitive type and takes appropriate actions. An OK_ACK indicates a successful bind operation, and inter_open returns the file descriptor of the open stream. An error_ack indicates a bind failure, and errno is set to identify the problem with the request.
The next routine in the service interface library example is inter_close, which closes the stream to the service provider.
inter_close(fd) { close(fd); }
The routine closes the given file descriptor. This causes the protocol driver to free any resources associated with that stream. For example, the driver can unbind the protocol address that had previously been bound to that stream, thereby freeing that address for use by another service user.
The third routine, inter_snd, passes data to the service provider for transmission to the user at the address specified in addr. The data to be transmitted is contained in the buffer pointed to by buf and contains len bytes. On successful completion, this routine returns the number of bytes of data passed to the service provider; on failure, it returns -1.
inter_snd(int fd, char *buf, int len, long *addr) { struct strbuf ctlbuf; struct strbuf databuf; struct unitdata_req unitdata_req; unitdata_req.PRIM_type = UNITDATA_REQ; unitdata_req.DEST_addr = addr; ctlbuf.len = sizeof(struct unitdata_req); ctlbuf.buf = (char *)&unitdata_req; databuf.len = len; databuf.buf = buf; if (putmsg(fd, &ctlbuf, &databuf, 0) < 0) return(-1); return(len); }
In this example, the data request primitive is packaged with both a control part and a data part. The control part contains a unitdata_req structure that identifies the primitive type and the destination address of the data. The data to be transmitted is placed in the data part of the request message.
Unlike the bind request, the data request primitive requires no acknowledgement from the service provider. In the example, this choice was made to minimize the overhead during data transfer. If the putmsg call succeeds, this routine returns the number of bytes passed to the service provider.
The final routine in Example 3–4, inter_rcv, retrieves the next available data. buf points to a buffer where the data should be stored, len indicates the size of that buffer, and addr points to a long integer where the source address of the data is placed. On successful completion, inter_rcv returns the number of bytes of retrieved data; on failure, it returns -1 and an appropriate UNIX system error value.
int inter_rcv (int fd, char *buf, int len, long *addr, int *errorp) { struct strbuf ctlbuf; struct strbuf databuf; struct unitdata_ind unitdata_ind; int retval; int flagsp; ctlbuf.maxlen = sizeof(struct unitdata_ind); ctlbuf.len = 0; ctlbuf.buf = (char *)&unitdata_ind; databuf.maxlen = len; databuf.len = 0; databuf.buf = buf; flagsp = 0; if((retval=getmsg(fd,&ctlbuf,&databuf,&flagsp))<0) { *errorp = EIO; return(-1); } if (retval) { *errorp = EIO; return(-1) } if (unitdata_ind.PRIM_type != UNITDATA_IND) { *errorp = EPROTO; return(-1); } *addr = unitdata_ind.SRC_addr; return(databuf.len); }
getmsg is called to retrieve the data indication primitive, where that primitive contains both a control and data part. The control part consists of a unitdata_ind structure that identifies the primitive type and the source address of the data sender. The data part contains the data itself. In ctlbuf, buf points to a buffer containing the control information, and maxlen indicates the maximum size of the buffer. Similar initialization is done for databuf.
The integer pointed to by flagsp in the getmsg call is set to zero, indicating that the next message should be retrieved from the stream head regardless of its priority. Data arrives in normal priority messages. If there is no message at the stream head, getmsg blocks until a message arrives.
The user's control and data buffers should be large enough to hold any incoming data. If both buffers are large enough, getmsg processes the data indication and returns 0, indicating that a full message was retrieved successfully. However, if neither buffer is large enough, getmsg only returns the part of the message that fits into each user buffer. The remainder of the message is saved for subsequent retrieval (in message non-discard mode), and a positive, nonzero value is returned to the user. A return value of MORECTL indicates that more control information is waiting for retrieval. A return value of MOREDATA indicates that more data is waiting for retrieval. A return value of (MORECTL | MOREDATA) indicates that data from both parts of the message remain. In the example, if the user buffers are not large enough (that is, getmsg returns a positive, nonzero value), the function sets errno to EIO and fails.
The type of the primitive returned by getmsg is checked to make sure it is a data indication (UNITDATA_IND in the example). The source address is then set and the number of bytes of data is returned.
The example presented is a simplified service interface. It shows typical uses of putmsg(2) and getmsg(2). The state transition rules for the interface are not presented and this example does not handle expedited data.
This section describes the synchronous polling mechanism and asynchronous event notification in STREAMS.
User processes can efficiently monitor and control multiple streams with two system calls: poll(2) and the I_SETSIG ioctl(2) command. These calls enable a user process to detect events that occur at the stream head on one or more streams, including receipt of data or messages on the read queue and cessation of flow control on the write queue. Note that poll(2) is usable on any character device file descriptor, not just STREAMS.
To monitor streams with poll(2), a user process issues that system call and specifies the streams and other files to be monitored, the events to check, and the amount of time to wait for an event. poll(2) blocks the process until the time expires or until an event occurs. If an event occurs, it returns the type of event and the descriptor on which the event occurred.
Instead of waiting for an event to occur, a user process can monitor one or more streams while processing other data. To do so, issue the I_SETSIG ioctl(2), specifying a stream and events (as with poll(2)). This ioctl(2) does not block the process and force the user process to wait for the event, but returns immediately and issues a signal when an event occurs. The process calls one of sigaction(2), signal(3c), or sigset(3C) to catch the resulting SIGPOLL signal.
If any selected event occurs on any of the selected streams, STREAMS sends SIGPOLL to all associated requesting processes. The processes have no information on what event occurred on what stream. A signaled process can get more information by calling poll(2).
poll(2) provides a mechanism to identify the streams over which a user can send or receive data. For each stream of interest, users can specify one or more events about which they should be notified. The types of events that can be polled are POLLIN, POLLRDNORM, POLLRDBAND, POLLPRI, POLLOUT, POLLWRNORM, POLLWRBAND, which are detailed in Table 3–3.Table 3–3 Events That Can Be Polled
A message other than high-priority data can be read without blocking. This event is maintained for compatibility with the previous releases of the Solaris operating environment.
A normal (nonpriority) message is at the front of the stream head read queue.
A priority message (band > 0) is at the front of the stream head queue.
A high-priority message is at the front of the stream head read queue.
The normal priority band of the queue is writable (not flow controlled).
The same as POLLOUT.
A priority band greater than 0 of a queue downstream.
Some of the events may not be applicable to all file types. For example, the POLLPRI event usually is not generated when polling a non-STREAMS character device. POLLIN, POLLRDNORM, POLLRDBAND, and POLLPRI are set even if the message is of zero length.
poll(2) checks each file descriptor for the requested events and, on return, indicates which events have occurred for each file descriptor. If no event has occurred on any polled file descriptor, poll(2) blocks until a requested event or timeout occurs. poll(2) takes the following arguments:
An array of file descriptors and events to be polled.
The number of file descriptors to be polled.
The number of milliseconds poll should wait for an event if no events are pending (-1 specifies wait forever).
Example 3–5 shows the use of poll(2). Two separate minor devices of the communications driver are opened, thereby establishing two separate streams to the driver. The pollfd entry is initialized for each device. Each stream is polled for incoming data. If data arrive on either stream, data is read and then written back to the other stream.
#include <sys/stropts.h> #include <fcntl.h> #include <poll.h> #define NPOLL 2 /* number of file descriptors to poll */ int main() { struct pollfd pollfds[NPOLL]; char buf[1024]; int count, i; if ((pollfds[0].fd = open("/dev/ttya", O_RDWR|O_NONBLOCK)) < 0) { perror("open failed for /dev/ttya"); exit(1); } if ((pollfds[1].fd = open("/dev/ttyb", O_RDWR|O_NONBLOCK)) < 0) { perror("open failed for /dev/ttyb"); exit(2); }
The variable pollfds is declared as an array of the pollfd structure, defined in <poll.h>, and has the format:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ }
For each entry in the array, fd specifies the file descriptor to be polled and events is a bitmask that contains the bitwise inclusive OR of events to be polled on that file descriptor. On return, the revents bitmask indicates which of the requested events has occurred.
The example continues to process incoming data, as shown below:
pollfds[0].events = POLLIN; /* set events to poll */ pollfds[1].events = POLLIN; /* for incoming data */ while (1) { /* poll and use -1 timeout (infinite) */ if (poll(pollfds, NPOLL, -1) < 0) { perror("poll failed"); exit(3); } for (i = 0; i < NPOLL; i++) { switch (pollfds[i].revents) { default: /* default error case */ fprintf(stderr,"error event\n"); exit(4); case 0: /* no events */ break; case POLLIN: /*echo incoming data on "other" Stream*/ while ((count = read(pollfds[i].fd, buf, 1024)) > 0) /* * write loses data if flow control * prevents the transmit at this time */ if (write(pollfds[(i+1) % NPOLL].fd buf, count) != count) fprintf(stderr,"writer lost data"); break; } } }
The user specifies the polled events by setting the events field of the pollfd structure to POLLIN. This request tells poll(2) to notify the user of any incoming data on each stream. The bulk of the example is an infinite loop, where each iteration polls both streams for incoming data.
The second argument of poll(2) specifies the number of entries in the pollfds array (2 in this example). The third argument indicates the number of milliseconds poll(2) waits for an event if none has occurred. On a system where millisecond accuracy is not available, timeout is rounded up to the nearest value available on that system. If the value of timeout is 0, poll(2) returns immediately. Here, timeout is set to -1, specifying that poll(2) blocks until a requested event occurs or until the call is interrupted.
If poll(2) succeeds, the program checks each entry in the pollfds array. If revents is set to 0, no event has occurred on that file descriptor. If revents is set to POLLIN, incoming data is available, so all available data is read from the polled minor device and written to the other minor device.
If revents is set to a value other than 0 or POLLIN, an error event must have occurred on that stream because POLLIN was the only requested event. Table 3–4 shows poll error events.Table 3–4 poll Error Events
A fatal error has occurred in a module or driver on the stream associated with the specified file descriptor. Further system calls fail.
A hangup condition exists on the stream associated with the specified file descriptor. This event and POLLOUT are mutually exclusive; a stream is not writable if a hangup has occurred.
The specified file descriptor is not associated with an open stream.
These events cannot be polled for by the user but are reported in revents when they occur. They are only valid in the revents bitmask.
The example attempts to process incoming data as quickly as possible. However, when writing data to a stream, write(2) can block if the stream is exerting flow control. To prevent the process from blocking, the minor devices of the communications driver are opened with the O_NDELAY (or O_NONBLOCK) flag set, see note. write(2) cannot send all the data if flow control is on and O_NDELAY (O_NONBLOCK) is set. This can happen if the communications driver processes characters slower than the user transmits. If the stream becomes full, the number of bytes write(2) sends is less than the requested count. For simplicity, the example ignores the data if the stream becomes full, and a warning is printed to stderr.
To conform with the IEEE operating system interface standard, POSIX, new applications should use the O_NONBLOCK flag. Its behavior is the same as that of O_NDELAY unless otherwise noted.
This program continues until an error occurs on a stream, or until the process is interrupted.
poll(2) enables a user to monitor multiple streams synchronously. poll(2) normally blocks until an event occurs on any of the polled file descriptors. In some applications, however, you want to process incoming data asynchronously. For example, an application can attempt to do some local processing and be interrupted when a pending event occurs. Some time-critical applications must not block, and must have immediate success or failure indication.
The I_SETSIG ioctl(2) (see streamio(7I)) is used to request that a SIGPOLL signal be sent to a user process when a specific event occurs. Table 3–5 lists events for I_SETSIG. These are similar to those described for poll(2).Table 3–5 I_SETSIG ioctl(2) Events
A message other than a high-priority message has arrived on a stream head read queue. This event is maintained for compatibility with the previous releases of the Solaris operating environment.
A normal (nonpriority) message has arrived on the stream head read queue.
A priority message (band > 0) has arrived on the stream head read queue.
A high-priority message has arrived on the stream head read queue.
A write queue for normal data (priority band = 0) is no longer full (not flow controlled). This notifies a user that there is space on the queue for sending or writing normal data downstream.
A priority band greater than 0 of a queue downstream exists and is writable. This notifies a user that there is space on the queue for sending or writing priority data downstream.
A signal message sent from a module or driver has arrived on the stream head read queue.
A hangup message sent from a module or driver has arrived at the stream head.
When used with S_RDBAND, SIGURG is generated instead of SIGPOLL when a priority message reaches the front of the stream head read queue.
S_INPUT, S_RDNORM, S_RDBAND, and S_HIPRI are set even if the message is of zero length. A user process can handle only high-priority messages by setting the arg to S_HIPRI.
STREAMS enables modules and drivers to send a signal to user processes through a special signal message. If the signal specified by the module or driver is not SIGPOLL (see signal(3C)), the signal is sent to the process group associated with the stream. If the signal is SIGPOLL, the signal is only sent to processes that have registered for the signal by using the I_SETSIG ioctl(2).
So that a process can obtain the band and event associated with SIGPOLL more readily, STREAMS supports extended signals. For the given events, a special code is defined in <sys/siginfo.h> that describes the reason SIGPOLL was generated. Table 3–6 describes the data available in the siginfo_t structure passed to the signal handler.Table 3–6 Data in siginfo_t Structure
Band readable
Band writable
Band signaled
stream error
The controlling terminal can receive signals and send signals. If a foreground process group has the stream as a controlling terminal stream, drivers and modules can use M_SIG messages to send signals to processes.
An overview of Job Control is provided here because it interacts with the STREAMS-based terminal subsystem. You can obtain more information on Job Control from the following manual pages: exit(2), getpgid(2), getpgrp(2), getsid(2), kill(2), setpgid(2), setpgrp(2), setsid(2), sigaction(2), signal(3C), sigsend(2), termios(3C), waitid(2), and termio(7I).
Job Control breaks a login session into smaller units called jobs. Each job consists of one or more related and cooperating processes. The foreground job, is given complete access to the controlling terminal. The other background jobs are denied read access to the controlling terminal and given conditional write and ioctl(2) access to it. The user can stop the executing job and resume the stopped job either in the foreground or in the background.
Under Job Control, background jobs do not receive events generated by the terminal and are not informed with a hangup indication when the controlling process exits. Background jobs that linger after the login session has been dissolved are prevented from further access to the controlling terminal, and do not interfere with the creation of new login sessions.
The following list defines terms associated with Job Control:
A process group that is a member of a session that established a connection with a controlling terminal and is not the foreground process group.
A session leader that established a connection to a controlling terminal.
A terminal that is associated with a session. Each session can have at most one controlling terminal associated with it, and a controlling terminal can be associated with at most one session. Certain input sequences from the controlling terminal cause signals to be sent to the process groups in the session associated with the controlling terminal.
Each session that establishes a connection with a controlling terminal distinguishes one process group of the session as a foreground process group. The foreground process group has certain privileges that are denied to background process groups when accessing its controlling terminal.
A process group in which the parent of every member in the group is either a member of the group, or is not a member of the process group's session.
Each process in the system is a member of a process group that is identified by a process group ID. Any process that is not a process group leader can create a new process group and become its leader. Any process that is not a process group leader can join an existing process group that shares the same session as the process. A newly created process joins the process group of its creator.
A process whose process ID is the same as its process group ID.
A time period that begins when a process group is created by its process group leader and ends when the last process that is a member in the group leaves the group.
A positive integer that uniquely identifies each process in the system. A process ID cannot be reused by the system until the process lifetime, process group lifetime, and session lifetime end for any process ID, process group ID, and session ID sharing that value.
A period that begins when the process is forked and ends after the process exits, when its termination has been acknowledged by its parent process.
Each process group is a member of a session that is identified by a session ID.
A positive integer that uniquely identifies each session in the system. It is the same as the process ID of its session leader (POSIX).
A process whose session ID is the same as its process and process group ID.
A period that begins when the session is created by its session leader and ends when the lifetime of the last process group that is a member of the session ends.
The following signals manage Job Control: (see also signal(3C)) :
Sent to a process to stop it. This signal cannot be caught or ignored.
Sent to a process to stop it. It is typically used when a user requests to stop the foreground process.
Sent to a background process to stop it when it attempts to read from the controlling terminal.
Sent to a background process to stop it when a user attempts to write to or modify the controlling terminal.
A session can be allocated a controlling terminal. For every allocated controlling terminal, Job Control elevates one process group in the controlling process's session to the status of foreground process group. The remaining process groups in the controlling process's session are background process groups. A controlling terminal gives a user the ability to control execution of jobs within the session. Controlling terminals are critical in Job Control. A user can cause the foreground job to stop by typing a predefined key on the controlling terminal. A user can inhibit access to the controlling terminal by background jobs. Background jobs that attempt to access a terminal that has been so restricted is sent a signal that typically causes the job to stop. (See Accessing the Controlling Terminal.)
Job Control requires support from a line-discipline module on the controlling terminal's stream. The TCSETA, TCSETAW, and TCSETAF commands of termio(7I) allow a process to set the following line discipline values relevant to Job Control:
A user-defined character that, when typed, causes the line discipline module to request that the stream head send a SIGTSTP signal to the foreground process, which by default stops the members of that group. If the value of SUSP is zero, the SIGTSTP signal is not sent, and the SUSP character is disabled.
If TOSTOP is set, background processes are inhibited from writing to their controlling terminal. A line discipline module must record the SUSP suspend character and notify the stream head when the user has typed it, and record the state of the TOSTOP bit and notify the stream head when the user has changed it.
A stream is allocated as a controlling terminal for a session if it:
Is acting as a terminal.
Is not already allocated as a controlling terminal.
Is opened by a session leader that does not have a controlling terminal.
Controlling terminals are allocated with open(2). The device must inform the stream head that it is acting as a terminal.
When a stream head receives a hangup message from a device or module, it is marked as hung up. A stream that is marked as hung up is allowed to be reopened by its session leader if it is allocated as a controlling terminal, and by any process if it is not allocated as a controlling terminal. This way, the hangup error can be cleared without forcing all file descriptors to be closed first.
If the reopen is successful, the hangup condition is cleared.
When the SIGHUP signal is generated by a hangup message instead of a signal message, the signal is sent to the controlling process instead of the foreground process group. The allocation and deallocation of controlling terminals to a session is the responsibility of that process group.
If a process attempts to access its controlling terminal after it has been deallocated, access is denied. If the process is not holding or ignoring SIGHUP, it is sent a SIGHUP signal. Otherwise, the access fails with an EIO error.
Members of background process groups have limited access to their controlling terminals:
If the background process is ignoring or holding the SIGTTIN signal or is a member of an orphaned process group, an attempt to read from the controlling terminal fails with an EIO error. Otherwise, the process is sent a SIGTTIN signal, which by default stops the process.
If the process is attempting to write to the terminal and if the terminal's TOSTOP flag is clear, the process is allowed access.
If the terminal's TOSTOP flag is set and a background process is attempting to write to the terminal, the write succeeds if the process is ignoring or holding SIGTTOU. Otherwise, the process stops except when it is a member of an orphaned process group, in which case it is denied access to the terminal and it is returned an EIO error.
If a background process is attempting to perform a destructive ioctl(2) (one that modifies terminal parameters), the ioctl(2) call succeeds if the process is ignoring or holding SIGTTOU. Otherwise, the process stops except when the process is a member of the orphaned process group. In that case the access to the terminal is denied and an EIO error is returned.
This chapter describes getting messages into and out of the driver from an application level. It shows the relationship between messages overall and the specific ioctl(2) calls that pertain to application-level operations.
Table 4-1 summarizes the system calls commonly used in controlling and transferring data and messages within a stream.Table 4–1 System Calls Used
System Call
Reads data from a stream
Writes data to a stream
Controls a stream
Receives a message at the stream head
Receives a priority message at the stream head
Sends a message downstream
Sends a priority message downstream
Identifies files on which a user can send or receive messages, or on which certain events have occurred (historically, it was unnecessarily restricted to streams)
Creates a bidirectional channel that provides a communication path between multiple processes
STREAMS is a special type of character device driver that is different from the historical character input/output (I/O) mechanism. In this section, the phrases character I/O mechanism and I/O mechanism refer only to that part of the mechanism that existed before STREAMS.
The character I/O mechanism handles all ioctl(2) calls transparently. That is, the kernel expects all ioctl(2) to be handled by the device driver associated with the character special file on which the call is sent. All ioctl(2) calls are sent to the driver, which is expected to perform all validation and processing other than file descriptor validity checking. The operation of any specific ioctl(2) is dependent on the device driver. If the driver requires data to be transferred in from user space, it will use the kernel ddi_copyin function. It may also use ddi_copyout to transfer any data results to user space.
With STREAMS, there are a number of differences from the character I/O mechanism that impart ioctl(2) processing.
First, there is a set of generic STREAMS ioctl(2) command values recognized and processed by the stream head. This is described in streamio(7I). The operation of the generic STREAMS ioctl(2) is generally independent of the presence of any specific module or driver on the stream.
The second difference is the absence of user context in a module and driver when the information associated with the ioctl(2) is received. This prevents use of ddi_copyin(9F) or ddi_copyout(9F) by the module. This also prevents the module and driver from associating any kernel data with the currently running process. (By the time the module or driver receives the ioctl(2), the process generating it probably will no longer be running.)
A third difference is that for the character I/O mechanism, all ioctl(2) are handled by the single driver associated with the file. In STREAMS, there can be multiple modules on a stream and each one can have its own set of ioctl(2) calls. That is, the ioctl(2) that can be used on a stream can change as modules are pushed and popped.
STREAMS provides the capability for user processes to perform control functions on specific modules and drivers in a stream with ioctl(2) calls. Most streamio(7I) ioctl(2) commands go no further than the stream head. They are fully processed there and no related messages are sent downstream. However, certain commands and all unrecognized commands cause the stream head to create an M_IOCTL </function> message, which includes the ioctl(2) arguments, and send the message downstream to be received and processed by a specific module or driver. The M_IOCTL </function> message is the initial message type that carries ioctl(2) information to modules. Other message types are used to complete the ioctl(2) processing in the stream. In general, each module must uniquely recognize and act on specific M_IOCTL </function> messages.
STREAMS ioctl(2) handling is equivalent to the transparent processing of the character I/O mechanism. STREAMS modules and drivers can process ioctl(2) generated by applications that are implemented for a non-STREAMS environment.
STREAMS blocks a user process that issues an ioctl(2) and causes the stream head to generate an M_IOCTL </function> message. The process remains blocked until one of the following occurs:
A module or a driver responds with an M_IOCACK (ack, positive acknowledgement) message or an M_IOCNAK (nak, negative acknowledgement) message.
No message is received and the request times out.
The ioctl(2) is interrupted by the user process.
An error condition occurs. For the I_STR ioctl(2), the timeout period can be a user-specified interval or a default. For the other ioctl(2) calls, the default value (infinite) is used.
For an I_STR, the STREAMS module or driver that generates a positive acknowledgement message can also return data to the process in the message. An alternate means to return data is provided with transparent ioctl(2). If the stream head does not receive a positive or negative acknowledgement message in the specified time, the ioctl(2) call fails.
A module that receives an unrecognized M_IOCTL message must pass it on unchanged. A driver that receives an unrecognized M_IOCTL must produce a negative acknowledgement.
The two STREAMS ioctl(2) mechanisms, I_STR and transparent, are described next. (Here, I_STR means the streamio(7I) I_STR command and implies the related STREAMS processing unless noted otherwise.) I_STR has a restricted format and restricted addressing for transferring ioctl(2)-related data between user and kernel space. It requires only a single pair of messages to complete ioctl(2) processing. The transparent mechanism is more general and has almost no restrictions on ioctl(2) data format and addressing. The transparent mechanism generally requires that multiple pairs of messages be exchanged between the stream head and module to complete the processing.
This is a rather simplistic view. Nothing prevents a given ioctl(2) from being issued either directly (transparent) or by means of I_STR. Furthermore, ioctl(2) calls issued through I_STR potentially can require further processing of the form typically associated with transparent ioctl(2).
The I_STR ioctl(2) provides a capability for user applications to perform module and driver control functions on STREAMS files. I_STR allows an application to specify the ioctl(2) timeout. It encourages that all user ioctl(2) data (to be received by the destination module) be placed in a single block that is pointed to from the user strioctl structure. The module can also return data to this block.
The transparent STREAMS ioctl(2) mechanism enables application programs to perform module and driver control functions with ioctl(2) calls other than I_STR. It transparently supports applications developed prior to the introduction of STREAMS. It alleviates the need to recode and recompile the user-level software to run over STREAMS files. More importantly, applications do not have to package their ioctl(2) requests into the form demanded by I_STR.
The mechanism extends the data transfer capability for STREAMS ioctl(2) calls beyond those provided in the I_STR form. Modules and drivers can transfer data between their kernel space and user space in any ioctl(2) that has a value of the command argument not defined in streamio(7I). These ioctl(2) are known as transparent ioctl(2) to differentiate them from the I_STR form. Existing applications that use non-STREAMS character devices require transparent processing for ioctl(2) if the device driver is converted to STREAMS. The ioctl(2) data can be in any format mutually understood by the user application and module.
The transparent mechanism also supports STREAMS applications that send ioctl(2) data to a driver or module in a single call, where the data may not be in a form readily embedded in a single user block. For example, the data may be contained in nested structures and different user space buffers.
The I_LIST ioctl(2) supports the strconf(1) and strchg(1) commands that are used to query or change the configuration of a stream. Only the root user or an owner of a STREAMS device can alter the configuration of that stream.
strchg(1) does the following:
Pushes one or more modules on the stream.
Pops the topmost module off the stream.
Pops all the modules off the stream.
Pops all modules up to but not including a specified module.
strconf(1) does the following:
Checks if the specified module is present on the stream.
Prints the topmost module of the stream.
Prints a list of all modules and the topmost driver on the stream. If the stream contains a multiplexing driver, the strchg and strconf commands will not recognize any modules below that driver.
The I_LIST ioctl(2), illustrated in Example 4–1, performs two functions. When the third argument of the ioctl(2) call is NULL,
if ((mods = ioctl(s, I_LIST, 0)) < 0) {
the return value of the call indicates the number of modules, plus the driver, present on the stream. For example, if there are two modules above the driver, 3 is returned. On failure, errno may be set to a value specified in streamio(7I). The second function of the I_LIST ioctl(2) is to copy the module names found on the stream to the user-supplied buffer. The address of the buffer in user space and the size of the buffer are passed to the ioctl(2) through a structure str_mlist that is defined as:
struct str_mlist { char l_name[FMNAMESZ+1]; /* space for holding a module name*/ }; struct str_list { int sl_nmods; /* #of modules for which space is allocated */ struct str_mlist *sl_modlist; /*addr of buf for names*/ };
Here sl_nmods is the number of modules in the sl_modlist array that the user has allocated. Each element in the array must be at least FMNAMESZ+1 bytes long. The array is FMNAMESZ+1 so the extra byte can hold the NULL character at the end of the string. FMNAMESZ is defined by <sys/conf.h>.
The amount of space to allocate for module names is indicated by the number of modules in the STREAM. This is not completely reliable because another module might be pushed onto the stream after the application invokes the I_LIST ioctl(2) with the NULL argument and before it invokes the I_LIST ioctl(2) with the structure argument.
The I_LIST call with arg pointing to the str_list structure returns the number of entries that have been filled into the sl_modlist array (the number represents the number of modules including the driver). If there is not enough space in the sl_modlist array or sl_nmods is less than 1, the I_LIST call fails and errno is set to EINVAL. If arg or the sl_modlist array points outside the allocated address space, EFAULT is returned.
#include <stdio.h> #include <string.h> #include <stropts.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/socket.h> main(int argc, const char **argv) { int s, i; unsigned int mods; struct str_list mod_list;struct str_mlist *mlist; /* Get a socket... */ if ((s = socket(AF_INET, SOCK_STREAM, 0)) <= 0) { perror("socket: "); exit(1); } /* Determine the number of modules in the stream */ if ((mods = ioctl(s, I_LIST, 0)) < 0) { perror("I_LIST ioctl"); } if (mods == 0) { printf("No modules\n"); exit(1); } else { printf("%d modules\n", mods); } /* Allocate memory for all of the module names */ mlist = (struct str_mlist *) calloc(mods, sizeof (struct str_mlist)); if (mlist == 0) { perror("malloc failure"); exit(1); } mod_list.sl_modlist = mlist; mod_list.sl_nmods = mods; /* Do the ioctl and get the module names... */ if (ioctl(s, I_LIST, &mod_list) < 0) { exit(1); } /* Print out the name of the modules... */ for (i = 0; i < mods; i++) { printf("s: %s\n", mod_list.sl_modlist[i].l_name); } /* Free the calloc'd structures... */ free(mlist); return(0); }
streamio(7I) details the ioctl(2) commands shown in Table 4–2.Table 4–2 Other ioctl Commands
ioctl Calls
Prevents the removal of a STREAMS module with an I_POP call. Any process can place an anchor on a stream, but once placed, an anchor can only be removed by a privileged process.
Retrieves the name of the module just below the stream head
Flushes all input or output queues
Flushes a band of messages
Compares the names of all modules present in the stream to a specific name
Enables the user to look at information in the first message on the stream head read queue without taking the message off the queue
Sets the read mode using a series of flag options in the argument
Indicates the read mode in an int
Counts the data bytes in data blocks in the first message on the stream head read queue
Creates a message from a user buffer, adds information about another stream, and sends the message downstream
Sets the write mode using the value of the argument
Returns the current write mode setting
Requests that the stream send a message with file pointer to the stream head at the other end of a STREAMS pipe
Retrieves the file descriptor of the message sent by an I_SENDFD ioctl(2) over a STREAMS pipe
Lets the application see if the current message on the stream head read queue is marked by a module downstream
Checks if the message of a given priority band exists on the stream head read queue
Returns the priority band of the first message on the stream head read queue
Checks if a certain band is writable
Enables the user to set the time the stream head will delay when a stream is closing if data exists on the write queues
Returns the close time delay
Connects two streams
Disconnects two streams
Connects two streams with a persistent link below a multiplexing driver
Disconnects two streams that have been connected with a persistent link
Various system calls let the user create messages and send them downstream and prioritize the messages.Table 4–3 Send and Receive Messages
Creates a message from the caller supplied control and data buffers and sends the message downstream
Does the same as putmsg(2) and enables the caller to specify a priority band for the message
Retrieves M_DATA, M_PROTO, or M_PCPROTO or high priority messages from the stream head, and places the contents into two user buffers
Does the same as getmsg(2) and enables the caller to specify a priority band for the message
The stream head guarantees that the control part of a message generated by putmsg(2) is at least 64 bytes long. This promotes reusability of the buffer. When the buffer is a reasonable size, modules and drivers may reuse the buffer for other headers.
stropts.h contains the specification of strbuf, which describes the control and data buffers.
All modules and drivers are expected to handle the flushing of messages. The user may cause data to be flushed of queued messages from a stream by the submission of an I_FLUSH ioctl(2). Data may be flushed from the read side, write side, or both sides of a stream.
ioctl (fd, I_FLUSH, arg);
Table 4–4 describes the arguments that may be passed to M_FLUSH.Table 4–4 M_FLUSH Arguments and bi_flag values
In addition to being able to flush all the data from a queue, a specific band may be flushed using the I_FLUSHBAND ioctl(2).
ioctl (fd, I_FLUSHBAND, bandp);
The ioctl(2) is passed a pointer to a bandinfo structure. The bi_pri field indicates the band priority to be flushed (values from 0 to 255). The bi_flag field indicates the type of flush to do. The legal values for bi_flag are defined in Table 4–4. bandinfo has the following format:
struct bandinfo { unsigned char bi_pri; int bi_flag; };
See Flushing Priority Band, which describes M_FLUSHBAND processing, for details on how modules and drivers should handle flush band requests.
This chapter describes the tools available to administer STREAMS. It shows how to keep track of names, where to find modules, and how to monitor statistics. Kernel debugging is covered in Chapter 14, Debugging STREAMS-based Applications.
Table 5-1 identifies some common tools available for monitoring, logging, and administering STREAMS.Table 5–1 Tools Available for STREAMS Administration
Configures list of modules to be automatically pushed
Examines system memory images
Note –
crash has reached EOL and is not supported in the Solaris 9 operating environment. For information about how to transition from crash to mdb, see the Solaris Modular Debugger Guide.
Detaches a name from a file descriptor
Prints STREAMS trace messages
Changes or queries a stream configuration
Logs STREAMS errors
Loads a kernel module
Unloads a kernel module
Displays information about loaded kernel modules
The autopush facility (see autopush(1M)) enables administrators to specify a list of modules to be automatically pushed onto the stream whenever a STREAMS device is opened. A prespecified list (/etc/iu.ap) of modules can be pushed onto the stream if the STREAMS device is not already open.
The STREAMS Administrative Driver (SAD) (see sad(7D)) provides an interface to the autopush mechanism. System administrators can open the SAD and set or get autopush information on other drivers. The SAD caches the list of modules to push for each driver. When the driver is opened, if not already open, the stream head checks the SAD's cache to determine if the device is configured to have modules automatically pushed. If an entry is found, the modules are pushed. If the device is open, another open does not cause the list of the prespecified modules to be pushed again.
The autopush SAD_SAP command ap_cmd field specifies the configuration options:
Configure each minor device (that is, a specific major and minor device number).
Configure a range of minor devices within a major device.
Configure all minor devices within a major device.
Undo configuration information for a driver.
In addition, when configuring the module list, an optional anchor can be placed within the list. (See STREAMS Anchors for more information.)
When the configuration list is cleared, a range of minor devices has to be cleared as a range and not in parts.
The SAD is accessed through /dev/sad/admin or /dev/sad/user. After the device is initialized, a program can be run to perform any needed autopush configuration. The program should open the SAD, read a configuration file to find out what modules must be configured for which devices, format the information into strapush structures, and perform the necessary SAD_SAP ioctl(2)s. See sad(7D) for more information.
All autopush operations are performed through an ioctl(2) command to set or get autopush information. Only the root user can set autopush information, but any user can get the autopush information for a device.
In the ioctl(2) call, the parameters are the file descriptor of the SAD, either SAD_SAP (set autopush information) or SAD_GAP (get autopush information), and a pointer to a strapush structure.
/* * maximum number of modules that can be pushed on a * Stream using the autopush feature should be no greater * than nstrpush */ #define MAXAPUSH 8 /* autopush information common to user and kernel */ struct apcommon { uint apc_cmd; /* command - see below */ major_t apc_major; /* major device number */ minor_t apc_minor; /* minor device number */ minor_t apc_lastminor; /* last minor dev # for range */ uint apc_npush; /* number of modules to push */ }; /* ap_cmd - various options of autopush */ #define SAP_CLEAR 0 /* remove configuration list */ #define SAP_ONE 1 /* configure one minor device*/ #define SAP_RANGE 2 /* config range of minor devices */ #define SAP_ALL 3 /* configure all minor devices */ /* format of autopush ioctls */ struct strapush { struct apcommon sap_common; char sap_list[MAXAPUSH] [FMNAMESZ + 1]; /* module list */ }; #define sap_cmd sap_common.apc_cmd #define sap_major sap_common.apc_major #define sap_minor sap_common.apc_minor #define sap_lastminor sap_common.apc_lastminor #define sap_npush sap_common.apc_npush
A device is identified by its major device number, sap_major. A SAD_CMD ioctl(2) is one of the following commands:
Clears the previous settings by removing the entry with the matching sap_major and sap_minor fields
Configures a range of minor devices from sap_minor to sap_lastminor, inclusive
The list of modules is specified as a list of module names in sap_list. The maximum number of modules to push automatically is defined by MAXAPUSH.
A user can query the current configuration status of a given major or minor device by issuing the SAD_GAP ioctl(2) with sap_major and sap_minor values of the device set. On successful return from this system call, the strapush structure will be filled in with the corresponding information for that device. The maximum number of entries the SAD driver can cache is determined by the tunable parameter NAUTOPUSH found in the SAD driver's master file.
The following is an example of an autopush configuration file in /etc/iu.ap:
# major minor lastminor modules wc 0 0 ldterm ttcompat zs 0 1 ldterm ttcompat ptsl 0 15 ldterm ttcompat
The first line is the configuration of a single minor device whose major name is wc and minor numbers start at 0 and end at 0, creating only one minor number. The modules automatically pushed are ldterm and ttcompat. The second line represents the configuration of the zs driver. The minor device numbers are 0 and 1. The last line allows minor device numbers from 0 to 15 for the ptsl driver.
STREAMS error and trace loggers are provided for debugging and for administering STREAMS modules and drivers. This facility consists of log(7D), strace(1M), strclean(1M), strerr(1M), and the strlog(9F) function.
strace(1M) is a utility that displays the messages in a specified STREAMS log. The log to display is identified by STREAMS module ID number, a sub-ID number, and the priority level.
strlog(9F) sends formatted text to log(7D) driver. Required definitions are contained in <sys/strlog.h> and <sys/log.h>. The call specifies the STREAMS module ID number, a sub-ID number, and the priority level. A flag parameter can specify any combination of:
The message is for the error logger
The message is for the tracer
Advisory notification of a fatal error
Modifies the SL_ERROR flag to request that a copy of the message be mailed to the system administrator
Log the message to the console
Warning message
Notice the message
The flags are followed by a printf(3C)-style format string, but %s, %e, %E, %g, and %G conversion specifications are not recognized. Up to NLOGARGS of numeric or character arguments can be specified.
strqget(9F) gets information about a queue or band of a queue. The information is returned in a long. The stream must be frozen by the caller when calling strqget.
strqset(9F) changes information about a queue or band of the queue. The updated information is provided in an int. If the field is read-only, EPERM is returned and the field is left unchanged. See <sys/stream.h> for valid values. The stream must be frozen by the caller when calling strqset(9F).
strerr(1M) is the STREAMS error logger daemon.
This chapter covers communication between processes using STREAMS-based pipes and named pipes. Discussion is limited to communications between applications.
A pipe in the SunOS 5.6 system provides a communication path between multiple processes. Prior to the SunOS 5.0 release, SunOS had standard pipes and named pipes (also called FIFOs). With standard pipes, one end was opened for reading and the other end for writing, so data flow was unidirectional. FIFOs had only one end and typically one process opened the file for reading and another process opened the file for writing. Data written into the FIFO by the writer could then be read by the reader.
To provide greater support and development flexibility for networked applications, pipes and FIFOs are STREAMS-based in the SunOS 5 software. The interface is unchanged but the underlying implementation is changed. When a pipe is created through the pipe(2) interface, two streams are opened and connected. Data flow is serial.
The remainder of this chapter uses the terms pipe and STREAMS-based pipe interchangeably to mean a STREAMS-based pipe.
After both ends of a FIFO have been opened, there is no guarantee that further calls to open O_RDONLY (O_WRONLY) will synchronize with later calls to open O_WRONLY (O_RDONLY) until both ends of the FIFO have been closed by all readers and writers. Any data written into a FIFO will be lost if both ends of the FIFO are closed before the data is read.
A named pipe, also called a FIFO, is a pipe identified by an entry in a file system's name space. FIFOs are created using mknod(2), mkfifo(1M), or the mknod(1M) command. They are removed using unlink(2) or the rm(1) command.
FIFOs look like regular file system nodes, but are distinguished from them by a p in the first column when the ls -l command is run.
/usr/sbin/mknod xxx pls -l xxx prw-r--r-- 1 guest other 0 Aug 26 10:55 xxx echoput> hello.world>xxx &put> [1] 8733 cat xxx hello world [1] + Done rm xxx
FIFOs are unidirectional: that is, one end of the FIFO is used for writing data, the other for reading data. FIFOs allow simple one-way interprocess communication between unrelated processes. Modules may be pushed onto a FIFO. Data written to the FIFO is passed down the write side of the module and back up the read side as shown in Figure 6–1.
FIFOs are opened in the same manner as other file system nodes with open(2). Any data written to the FIFO can be read from the same file descriptor in the first-in, first-out manner (serial, sequentially). Modules can also be pushed on the FIFO. See open(2) for the restrictions that apply when opening a FIFO. If O_NDELAY or O_NONBLOCK is not specified, an open on a FIFO blocks until both a reader and a writer are present.
Named or mounted streams provide a more powerful interface for interprocess communications than does a FIFO. See Named Streams for details.
A STREAMS-based pipe, also referred to as an anonymous pipe, is created using pipe(2), which returns two file descriptors, fd[0] and fd[1], each with its own stream head. The ends of the pipe are constructed so that data written to either end of a pipe may be read from the opposite end.
STREAMS modules can be added to a pipe with I_PUSH ioctl(2). A module can be pushed onto one or both ends of the pipe (see Figure 6–2). However, if a module is pushed onto one end of the pipe, that module cannot be popped from the other end.
Pipes and FIFOs can be accessed through the operating system routines read(2), write(2), ioctl(2), close(2), putmsg(2), putpmsg(2), getmsg(2), getpmsg(2), and poll(2). For FIFOs, open(2) is also used.
read(2) or getmsg(2)) are used to read from a pipe or FIFO. Data can be read from either end of a pipe. On success, the read(2) returns the number of bytes read and buffered. When the end of the data is reached, read(2) returns 0.
When a user process attempts to read from an empty pipe (or FIFO), the following happens:
If one end of the pipe is closed, 0 is returned, indicating the end of the file.
If the write side of the FIFO has closed, read(2) returns 0 to indicate the end of the file.
If some process has the FIFO open for writing, or both ends of the pipe are open, and O_NDELAY is set, read(2) returns 0.
If some process has the FIFO open for writing, or both ends of the pipe are open, and O_NONBLOCK is set, read(2) returns -1 and sets errno to EAGAIN.
If O_NDELAY and O_NONBLOCK are not set, the read call blocks until data is written to the pipe, until one end of the pipe is closed, or the FIFO is no longer open for writing.
When a user process calls write(2), data is sent down the associated stream. If the pipe or FIFO is empty (no modules pushed), the data that is written is placed on the read queue of the other stream for pipes, and on the read queue of the same stream for FIFOs. Since the size of a pipe is the number of unread data bytes, the written data is reflected in the size of the other end of the pipe.
If a user process issues write(2) with 0 as the number of bytes to send a pipe or FIFO, 0 is returned, and, by default, no message is sent down the stream. However, if a user must send a zero-length message downstream, SNDZERO ioctl(2) can be used to change this default behavior. If SNDZERO is set in the stream head, write(2) requests of 0 bytes generate a zero-length message and send the message down the stream. If SNDZERO is not set, no message is generated and 0 is returned to the user.
The SNDZERO bit may be changed by the I_SWROPT ioctl(2). If the arg in the ioctl(2) has SNDZERO set, the bit is turned on. If the arg is set to 0, the SNDZERO bit is turned off.
The I_GWROPT ioctl(2) is used to get the current write settings.
If multiple processes simultaneously write to the same pipe, data from one process can be interleaved with data from another process, if modules are pushed on the pipe or the write is greater than PIPE_BUF. The order of data that is written is not necessarily the order of data that is read. To ensure that writes of less than PIPE_BUF bytes are not interleaved with data written by other processes, any modules pushed on the pipe should have a maximum packet size of at least PIPE_BUF.
PIPE_BUF is an implementation-specific constant that specifies the maximum number of bytes that are atomic when writing to a pipe. When writing to a pipe, write requests of PIPE_BUF or fewer bytes are not interleaved with data from other processes doing writes to the same pipe. However, write requests of more than PIPE_BUF bytes may have data interleaved on arbitrary byte boundaries with writes by other processes whether or not the O_NONBLOCK or O_NDELAY flag is set.
If the module packet size is at least the size of PIPE_BUF, the stream head packages the data in such a way that the first message is at least PIPE_BUF bytes. The remaining data may be packaged into smaller or equal-sized blocks depending on buffer availability. If the first module on the stream cannot support a packet of PIPE_BUF, atomic writes on the pipe cannot be guaranteed.
close(2) closes a pipe or FIFO and dismantles its associated streams. On the last close of one end of a pipe, an M_HANGUP message is sent to the other end of the pipe. Subsequent read(2) or getmsg(2) calls on that stream head return the number of bytes read and zero when there are no more data. Subsequent write(2) or putmsg(2) requests fail with errno set to EPIPE. If the other end of the pipe is mounted, the last close of the pipe forces it to be unmounted.
When the flush request is initiated from an ioctl(2) or from flushq(9F), the FLUSHR or the FLUSHW bits of an M_FLUSH message must be switched. Bits are switched at the point where the M_FLUSH message is passed from a write queue to a read queue. This point is also known as the midpoint of the pipe.
The midpoint of a pipe is not always easily detectable, especially if there are numerous modules pushed on either end of the pipe. In that case, some mechanism needs to intercept all messages passing through the stream. If the message is an M_FLUSH message and it is at the stream midpoint, the flush bits need to be switched.
This bit switching is handled by the pipemod module. pipemod should be pushed onto a pipe or FIFO where flushing of any kind will take place. The pipemod(7M) module can be pushed on either end of the pipe. The only requirement is that it is pushed onto an end that previously did not have modules on it. That is, pipemod(7M) must be the first module pushed onto a pipe so that it is at the midpoint of the pipe itself.
The pipemod(7M) module handles only M_FLUSH messages. All other messages are passed to the next module using the putnext(9F) utility routine. If an M_FLUSH message is passed to pipemod(7M) and the FLUSHR and FLUSHW bits are set, the message is not processed but is passed to the next module using putnext(9F). If only the FLUSHR bit is set, it is turned off and the FLUSHW bit is set. The message is then passed to the next module, using putnext(9F). Similarly, if the FLUSHW bit was the only bit set in the M_FLUSH message, it is turned off and the FLUSHR bit is turned on. The message is then passed to the next module on the stream.
The pipemod(7M) module can be pushed on any stream if it requires the bit switching.
The name of a stream or STREAMS–based pipe often associates the stream with an existing node in the file system name space. This allows unrelated processes to open the pipe and exchange data with the application. The following interfaces support naming a stream or STREAMS–based pipe.
Attaches a stream file descriptor to a node in the file system name space, thus naming the stream.
Detaches a named stream file descriptor from its node in the file system name space, thus unnaming the stream.
Tests whether a file descriptor is associated with a stream.
Named streams are useful for passing file descriptors between unrelated processes on the same machine. A user process can send a file descriptor to another process by invoking the I_SENDFD ioctl(2) on one end of a named stream. This sends a message containing a file pointer to the stream head at the other end of the pipe. Another process can retrieve the message containing the file pointer by an I_RECVFD ioctl(2) call on the other end of the pipe.
With named pipes, client processes may communicate with a server process using the connld module which lets a client process get a unique, non-multiplexed connection to a server. connld(7M) is a STREAMS-based module that has open, close, and put procedures.
When the named stream is opened, the open routine of connld(7M) is called. The open fails if:
The pipe ends cannot be created.
A file pointer and file descriptor cannot be allocated.
The stream head cannot stream the two pipe ends.
The open is not complete and will block until the server process has received the file descriptor using the I_RECVFD ioctl. The setting of the O_NDELAY or O_NONBLOCK flag has no impact on the open routine.
connld(7M) does not process messages. All messages are passed to the next object in the stream. The read, write, and put routines call putnext(9F) to send the message up or down the stream.
The connld(7M) module can be pushed onto the named end of the pipe. If the named end of the pipe is then opened by a client, a new pipe is created. One file descriptor for the new pipe is passed back to a client (named stream) as the file descriptor from open(2) and the other file descriptor is passed to the server using I_RECUFD ioctl(2). The server and the client may then communicate through a new pipe.
Figure 6–3 shows a server process that has created a pipe and pushed the connld module on the other end. The server then invokes the fattach(3C) routine to name the other end /usr/toserv.
When process X (procx) opens /usr/toserv, it gains a unique connection to the server process that was at one end of the original STREAMS-based pipe. When process Y (procy) does the same, it also gains a unique connection to the server. As shown in Figure 6–4, the server process has access to three separate pipes through three file descriptors.