STREAMS Programming Guide

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 actually still be available.

On success, bufcall(9F) 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(9F) to call (*func)(arg). bufcall(9F) should be used sparingly.


Two examples are provided. Example 8-3 is a device-receive-interrupt handler and Example 8-4 is a write service procedure:


Example 8-3 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 Chapter 12, MultiThreaded STREAMS for more information on the uses of unbufcall(9F). These references are protected by MT locks.

Since bufcall(9F) can fail, there is still a chance that the device hangs. 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 than hanging.

Example 8-4, mod_wsrv prefixes each output message with a header.


Example 8-4 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 .... */
 		}
		}
	}

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 8-4 deals with bufcall(9F) failure by calling timeout(9F). 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.