Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
  Previous   Contents   Next 
   
 
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

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.

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

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, Application Access to the STREAMS Driver and Module Interfaces.

Read Options

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

RMODEMASK

 

 

RNORM

RMSGN

RMSGD

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 as shown in Table 3-2.

Table 3-2 How read(2) Treats Protocol Messages

RPROTMASK

 

 

RPROTNORM

RPROTDIS

RPROTDATA

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

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

Controlling Data 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 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.

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


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

 
 
 
  Previous   Contents   Next