Part I Application Programming Interface
2. STREAMS Application-Level Components
3. STREAMS Application-Level Mechanisms
4. Application Access to the STREAMS Driver and Module Interfaces
7. STREAMS Framework - Kernel Level
8. STREAMS Kernel-Level Mechanisms
11. Configuring STREAMS Drivers and Modules
14. Debugging STREAMS-based Applications
B. Kernel Utility Interface Summary
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
|
Various system calls let the user create messages and send them downstream and prioritize the messages.
Table 4-3 Send and Receive Messages
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.