Go to main content

STREAMS Programming Guide

Exit Print View

Updated: March 2019
 
 

Message Allocation and Freeing

The allocb(9F) utility routine allocates a message and the space to hold the data for the message. allocb(9F) returns a pointer to a message block containing a data buffer of at least the size requested, providing there is enough memory available. The routine returns NULL on failure. allocb(9F) always returns a message of type M_DATA. The type can then be changed if required. b_rptr and b_wptr are set to db_base (see msgb(9S) and datab(9S)), which is the start of the memory location for the message buffer.


Note - STREAMS often provides buffers that are bit aligned, but there is no guarantee that db_base or db_lim reside on bit-aligned boundaries. If bit or page alignment is required on module-supplied buffers use esballoc For more information about esballoc see Extended STREAMS Buffers.

allocb(9F) can return a buffer larger than the size requested. If allocb(9F) indicates that buffers are not available (allocb(9F) fails), the put or service procedure cannot block to wait for a buffer to become available. Instead, bufcall(9F) defers processing in the module or the driver until a buffer becomes available.

If message space allocation is done by the put procedure and allocb(9F) fails, the message is usually discarded. If the allocation fails in the service routine, the message is returned to the queue. bufcall(9F) is called to set a call to the service routine when a message buffer becomes available, and the service routine returns.

freeb(9F) releases the message block descriptor and the corresponding data block, if the reference count (see datab(9S)) is equal to 1. If the reference count exceeds 1, the data block is not released.

freemsg(9F) releases all message blocks in a message. It uses freeb(9F) to free all message blocks and corresponding data blocks.

In Example 17, Use of allocb, allocb(9F) is used by the bappend subroutine that appends a character to a message block.

Example 17  Use of allocb
/*
 * Append a character to a message block.
 * If (*bpp) is null, it will allocate a new block
 * Returns 0 when the message block is full, 1 otherwise
 */
#define MODBLKSZ		128			/* size of message blocks */

static int bappend(mblk_t **bpp, int ch)
{
 	mblk_t *bp;

 	if ((bp = *bpp) != NULL) {
 			if (bp->b_wptr >= bp->b_datap->db_lim)
 				return (0);
 	} else {
 			if ((*bpp = bp = allocb(MODBLKSZ, BPRI_MED)) == NULL)
 				return (1);
 	}
 	*bp->b_wptr++ = ch;
 	return 1;
}

bappend receives a pointer to a message block and a character as arguments. If a message block is supplied (*bpp != NULL), bappend checks if there is room for more data in the block. If not, it fails. If there is no message block, a block of at least MODBLKSZ is allocated through allocb(9F).

If allocb(9F) fails, bappend returns success and discards the character. If the original message block is not full or the allocb(9F) is successful, bappend stores the character in the block.

Example 18, Subroutine modwput shows the processing of all the message blocks in any downstream data (type M_DATA) messages. freemsg(9F) frees messages.

Example 18  Subroutine modwput
/* Write side put procedure */
static int modwput(queue_t *q, mblk_t *mp)
{
 	switch (mp->b_datap->db_type) {
 	default:
 			putnext(q, mp);			/* Don't do these, pass along */
 			break;

	case M_DATA: {
 			mblk_t *bp;
			struct mblk_t *nmp = NULL, *nbp = NULL;

			for (bp = mp; bp != NULL; bp = bp->b_cont) {
 				while (bp->b_rptr < bp->b_wptr) {
 						if (*bp->b_rptr == '\n')
 								if (!bappend(&nbp, '\r'))
 									goto newblk;
 						if (!bappend(&nbp, *bp->b_rptr))
 								goto newblk;

						bp->b_rptr++;
 						continue;

				newblk:
 						if (nmp == NULL)
 								nmp = nbp;
 						else { /* link msg blk to tail of nmp */
 								linkb(nmp, nbp);
 								nbp = NULL;
 						}
 				}
 			}
			if (nmp == NULL)
	 			nmp = nbp;
 			else
	 			linkb(nmp, nbp);
	 		freemsg(mp); /* de-allocate message */
 			if (nmp)
 				putnext(q, nmp);
 			break;
 	 	}
 	}
}

Data messages are scanned and filtered. modwput copies the original message into new blocks, modifying as it copies. nbp points to the current new message block. nmp points to the new message being formed as multiple M_DATA message blocks. The outer for loop goes through each message block of the original message. The inner while loop goes through each byte. bappend is used to add characters to the current or new block. If bappend fails, the current new block is full. If nmp is NULL, nmp is pointed at the new block. If nmp is not NULL, the new block is linked to the end of nmp by use of linkb(9F).

At the end of the loops, the final new block is linked to nmp. The original message (all message blocks) is returned to the pool by freemsg(9F). If a new message exists, it is sent downstream.

Recovering From No Buffers

bufcall(9F) can be used to recover from an allocb(9F) failure. The call syntax is as follows:

bufcall_id_t bufcall(int size, int pri, void(*func)(), long arg);

Note - qbufcall(9F) and qunbufcall(9F) must be used with perimeters.

bufcall(9F) calls (*func)(arg) when a buffer of size bytes is available. When func is called, it has no user context and must return without blocking. Also, there is no guarantee that when func is called, a buffer will still be available.

On success, bufcall returns a nonzero identifier that can be used as a parameter to unbufcall(9F) to cancel the request later. On failure, 0 is returned and the requested function is never called.


Caution

Caution  - Care must be taken to avoid deadlock when holding resources while waiting for bufcall to call (*func(arg). bufcall should be used sparingly.


Read Device Interrupt Handler

Example 19, Device Interrupt Handler is an example of a read device interrupt handler.

Example 19  Device Interrupt Handler
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
buffcall_id_t id;						/* hold id val for unbufcall */

dev_rintr(dev)
{
 	/* process incoming message ... */
 	/* allocate new buffer for device */
 	dev_re_load(dev);
}

/*
 * Reload device with a new receive buffer
 */
dev_re_load(dev)
{
 	mblk_t *bp;
 	id = 0;						/* begin with no waiting for buffers */
 	if ((bp = allocb(DEVBLKSZ, BPRI_MED)) == NULL) {
 			cmn_err(CE_WARN,"dev:allocbfailure(size%d)\n",
 				 DEVBLKSZ);
 			/*
 			 * Allocation failed. Use bufcall to
 			 * schedule a call to ourselves.
 			 */
 			id = bufcall(DEVBLKSZ,BPRI_MED,dev_re_load,dev);
 			return;
 	}

 	/* pass buffer to device ... */
}

See Multithreaded STREAMS for more information on the uses of unbufcall(9F). These references to unbufcall are protected by MT locks.

Because bufcall(9F) can fail, there is still a chance that the device will hang. A better strategy if bufcall(9F) fails is to discard the current input message and resubmit that buffer to the device. Losing input data is preferable to the device hanging.

Write Service Procedure

Example 20, Write Service Procedure is an example of a write service procedure.

Example 20  Write Service Procedure
static int mod_wsrv(queue_t *q)
{
 	extern int qenable();
 	mblk_t *mp, *bp;
		while (mp = getq(q)) {
			/* check for priority messages and canput ... */

			/* Allocate a header to prepend to the message.
 		 * If the allocb fails, use bufcall to reschedule.
 		 */
 		if ((bp = allocb(HDRSZ, BPRI_MED)) == NULL) {
 			if (!(id=bufcall(HDRSZ,BPRI_MED,qenable, q))) {
  				timeout(qenable, (caddr_t)q,
					drv_usectohz());
 				/*
 				 * Put the msg back and exit, we will be
 				 * re-enabled later
					 */
 				putbq(q, mp);
 				return;
 			}
 			/* process message .... */
 		}
		}
	}

mod_wsrv prefixes each output message with a header.

In this example, mod_wsrv illustrates a potential deadlock case. If allocb(9F) fails, mod_wsrv tends to recover without loss of data and calls bufcall(9F). In this case, the routine passed to bufcall(9F) is qenable(9F). When a buffer is available, the service procedure is automatically re-enabled. Before exiting, the current message is put back in the queue. Example 20, Write Service Procedure deals with bufcall(9F) failure by calling timeout(9F).

timeout(9F) schedules the given function to be run with the given argument in the given number of clock cycles. In this example, if bufcall(9F) fails, the system runs qenable(9F) after two seconds have passed.

Releasing Callback Requests

When allocb(9F) fails and bufcall(9F) is called, a callback is pending until a buffer is actually returned. Because this callback is asynchronous, it must be released before all processing is complete. To release this queued event, use unbufcall(9F).

Pass the id returned by bufcall(9F) to unbufcall(9F). Then close the driver in the normal way. If this sequence of unbufcall(9F) and xxclose is not followed, the callback can occur when the driver is already closed. This is one of the most difficult types of problems to find and debug.


Caution

Caution  - All bufcall(9F) and timeouts must be canceled in the close routine.