STREAMS Programming Guide

Chapter 4 Application Access to the STREAMS Driver and Module Interfaces

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.

System Calls Used

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 

Description 

read(2)

Reads data from a stream  

write(2)

Writes data to a stream  

ioctl(2)

Controls a stream  

getmsg(2)

Receives a message at the stream head  

getpmsg(2)

Receives a priority message at the stream head  

putmsg(2)

Sends a message downstream  

putpmsg(2)

Sends a priority message downstream  

poll(2)

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) 

pipe(2)

Creates a bidirectional channel that provides a communication path between multiple processes 

 

Module and Driver ioctl Calls

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.

General ioctl Processing

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:

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).

I_STR ioctl Processing

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.

Transparent ioctl Processing

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.

I_LIST ioctl

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:

strconf(1) does the following:

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.


Example 4–1 I_LIST ioctl

#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);
}

Other ioctl Commands

streamio(7I) details the ioctl(2) commands shown in Table 4–2.

Table 4–2 Other ioctl Commands

ioctl Calls

Description 

I_ANCHOR

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.

I_LOOK

Retrieves the name of the module just below the stream head 

I_FLUSH

Flushes all input or output queues 

I_FLUSHBAND

Flushes a band of messages 

I_FIND

Compares the names of all modules present in the stream to a specific name 

I_PEEK

Enables the user to look at information in the first message on the stream head read queue without taking the message off the queue  

I_SRDOPT

Sets the read mode using a series of flag options in the argument 

I_GRDOPT

Indicates the read mode in an int

I_NREAD

Counts the data bytes in data blocks in the first message on the stream head read queue 

I_FDINSERT

Creates a message from a user buffer, adds information about another stream, and sends the message downstream 

I_SWROPT

Sets the write mode using the value of the argument 

I_GWROPT

Returns the current write mode setting 

I_SENDFD

Requests that the stream send a message with file pointer to the stream head at the other end of a STREAMS pipe 

I_RECVFD

Retrieves the file descriptor of the message sent by an I_SENDFD ioctl(2) over a STREAMS pipe

I_ATMARK

Lets the application see if the current message on the stream head read queue is marked by a module downstream 

I_CKBAND

Checks if the message of a given priority band exists on the stream head read queue 

I_GETBAND

Returns the priority band of the first message on the stream head read queue 

I_CANPUT

Checks if a certain band is writable 

I_SETCLTIME

Enables the user to set the time the stream head will delay when a stream is closing if data exists on the write queues 

I_GETCLTIME

Returns the close time delay 

I_LINK

Connects two streams 

I_UNLINK

Disconnects two streams 

I_PLINK

Connects two streams with a persistent link below a multiplexing driver 

I_PUNLINK

Disconnects two streams that have been connected with a persistent link 

Message Direction

Various system calls let the user create messages and send them downstream and prioritize the messages.

Table 4–3 Send and Receive Messages

putmsg(2)

Creates a message from the caller supplied control and data buffers and sends the message downstream

putpmsg(2)

Does the same as putmsg(2) and enables the caller to specify a priority band for the message

getmsg(2)

Retrieves M_DATA, M_PROTO, or M_PCPROTO or high priority messages from the stream head, and places the contents into two user buffers

getpmsg(2)

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.

Flush Handling

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

Flag 

Description

FLUSHR

Flushes read side of stream

FLUSHW

Flushes write queue

FLUSHRW

Flushes both read and write queues

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.