STREAMS Programming Guide

Chapter 3 STREAMS Application-Level Mechanisms

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.

Message Handling

Since 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, Messages - Kernel Level covers the underlying mechanics of the kernel.

Modifying Messages

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 service procedure. For a more detailed explanation of put and service, see Chapter 8, Messages - Kernel Level.

Some kernel operations are explained here to show you how to manipulate the driver or module appropriately.

Message Types

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.

Control of Stream-Head Processing

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, STREAMS Driver and Module Interfaces.

Read Options

The value of read options (so_readopt) specifies 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.

Table 3-1 Read Option Modes

RMODEMASK

 

 

RNORM

RMSGN

RMSGN

RPROTMASK

 

 

RPROTNORM

RPROTDIS

RPROTDATA

The first set of bits, RMODEMASK, deals with data and message boundaries, as shown in Table 3-1.

byte-stream (RNORM)

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.

message non-discard (RMSGN)

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.

message discard (RMSGD)

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:

normal protocol (RPROTNORM)

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.

protocol discard (RPROTDIS)

The read(2) call discards any M_PROTO or M_PCPROTO blocks in a message, delivering the M_DATA blocks to the user.

protocol data (RPROTDAT)

The read(2) call converts the M_PROTO and M_PCPROTO message blocks to M_DATA blocks, treating the entire message as data.

Write Options

send zero (I_SWROPT)

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.

Message Queueing and Priorities

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.

Figure 3-1 Message Ordering in a Queue

Graphic

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.

Figure 3-2 Message Ordering With One Priority Band

Graphic

Controlling Flow and Priorities

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 Transmission Control Protocol's (TCP) ability 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 if the message on the front of its Stream-head read queue is marked or not 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. Get the rest of the data with successive reads. Chapter 4, 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 if any message is marked. LASTMARK indicates that the user wants to see if 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.

Accessing the Service Provider

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.


Example 3-1 inter_open Routine

inter_open (char *path, oflags, addr)
{
 	intt 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 acknowledgment from the service provider, as Example 3-2 shows:


Example 3-2 Service Provider

/* 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 acknowledgment of the bind request. The acknowledgment message consists of a control part that contains either an OK_ACK or an error_ack structure, and no data part.

The acknowledgment 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; high-priority messages are placed at the front of the Stream-head queue, followed by priority band messages and ordinary messages. 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 acknowledgment 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 acknowledgment 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. It is set because you want to catch the acknowledgment 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.

Closing the Service Provider

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.

Sending Data to the Service Provider

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.


Example 3-3 inter_snd

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

Receiving Data

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.


Example 3-4 Receiving Data

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

Input and Output Polling

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

Synchronous Input and Output

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, detailed in Table 3-2.

Table 3-2 Events That Can Be Polled

Event 

Description 

POLLIN

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.  

POLLRDNORM

A normal (nonpriority) message is at the front of the Stream-head read queue.  

POLLRDBAND

A priority message (band > 0) is at the front of the Stream-head queue.  

POLLPRI

A high-priority message is at the front of the Stream-head read queue.  

POLLOUT

The normal priority band of the queue is writable (not flow controlled).  

POLLWRNORM

The same as POLLOUT.

POLLWRBAND

A priority band greater than 0 of a queue downstream.  

Some of the events may not be applicable to all file types. For example, it is not expected that the POLLPRI event is 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:

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.


Example 3-5 Polling

#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-3 shows poll error events.

Table 3-3 poll Error Events

Error 

Description 

POLLERR

A fatal error has occurred in a module or driver on the Stream associated with the specified file descriptor. Further system calls fail.  

POLLHUP

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.

POLLNVAL

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; see note) flag set. 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.


Note -

To conform with the IEEE operating system interface standard, POSIX, new applications should use the O_NONBLOCK flag, whose 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.

Asynchronous Input and Output

poll(2) lets a user monitor multiple Streams synchronously. poll(2) normally blocks until an event occurs on any of the polled file descriptors. In some applications, however, it is desirable 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-4 lists events for I_SETSIG. These are similar to those described for poll(2).

Table 3-4 I_SETSIG ioctl(2) Events

Event 

Description 

S_INPUT

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 Solaris operating environment.  

S_RDNORM

A normal (nonpriority) message has arrived on the Stream-head read queue.  

S_RDBAND

A priority message (band > 0) has arrived on the Stream-head read queue.  

S_HIPRI

A high-priority message has arrived on the Stream-head read queue.  

S_OUTPUT

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.  

S_WRNORM

The same as S_OUTPUT.

S_WRBAND

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.  

S_MSG

A signal message sent from a module or driver has arrived on the Stream-head read queue.  

S_ERROR

An error message reaches the Stream-head.  

S_HANGUP

A hangup message sent from a module or driver has arrived at the Stream-head.  

S_BANDURG

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.

Signals

STREAMS allows modules and drivers to cause a signal to be sent to user processes through a special signal message. If the signal specified by the module or driver is not SIGPOLL (see signal(5)), 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).

Extended Signals

To let a process 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-5 describes the data available in the siginfo_t structure passed to the signal handler.

Table 3-5 Data in siginfo_t Structure

Event 

si_signo

si_code

si_band

si_errno

S_INPUT

 

SIGPOLL

POLL_IN

Band readable 

Unused 

S_OUTPUT

SIGPOLL

POLL_OUT

Band writable 

Unused 

S_MSG

SIGPOLL

POLL_MSG

Band signaled 

Unused 

S_ERROR

SIGPOLL

POLL_ERR

Unused 

Stream error 

S_HANGUP

SIGPOLL

POLL_HUP

Unused 

Unused 

S_HIPRI

SIGPOLL

POLL_PRI

Unused 

Unused 

Stream as a Controlling Terminal

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.

Job Control

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(5), sigsend(2), termios(3), waitid(2), waitpid(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. One job, the foreground job, is given complete access to the controlling terminal. The other jobs, 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:

Background Process Group

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.

Controlling Process

A session leader that established a connection to a controlling terminal.

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.

Foreground Process Group

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.

Orphaned Process Group

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.

Process Group

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.

Process Group Leader

A process whose process ID is the same as its process group ID.

Process Group Lifetime

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.

Process ID

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.

Process Lifetime

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.

Session

Each process group is a member of a session that is identified by a session ID.

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

Session Leader

A process whose session ID is the same as its process and process group ID.

Session Lifetime

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(5))

SIGCONT

Sent to a stopped process to continue it.

SIGSTOP

Sent to a process to stop it. This signal cannot be caught or ignored.

SIGTSTP

Sent to a process to stop it. It is typically used when a user requests to stop the foreground process.

SIGTTIN

Sent to a background process to stop it when it attempts to read from the controlling terminal.

SIGTTOU

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:

SUSP character

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.

TOSTOP flag

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.

Allocation and Deallocation

A Stream is allocated as a controlling terminal for a session if:

Hungup Streams

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.

Hangup Signals

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, since the allocation and deallocation of controlling terminals to a session is the responsibility of that process group.

Accessing the Controlling Terminal

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: