Streaming TK50s, and BSD bugs (long due to code examples)

Chris Torek chris at trantor.umd.edu
Sun Feb 28 06:48:11 AEST 1988


>In article <2346 at umd5.umd.edu> chris at trantor.umd.edu (Chris Torek) writes:
>> [me wondering who uses block special tape devices]

In article <3261 at bloom-beacon.MIT.EDU> nessus at athena.mit.edu (Doug Alan) writes:
>... We have many DEC TK50 streaming tape drives here ....  The TK50
>performs very very very slow and unreliably if it doesn't get to stream.
>The block device is double buffered, while the raw device is not.

(Actually, the block device is n-buffered, where n is nearly the size
of your buffer cache; but the biggest gain is at 2 buffers, so the
difference is largely irrelevant.)

>the raw device ... doesn't stream.  ... the block device ...
>does stream, and is much much happier.

I suspect the TK50 is rather a special case, though.  I know that
at least most SCSI tape cartridge drives are an fact `block'
devices: if you write 1024 bytes to the controller, it creates
two 512-byte blocks on the tape.  Does not the TK50 do the same?

Also [begin 4BSD specific discussion], I use here yet another
variant of my `mass driver' hack, which provides asynchronous I/O
on character special devices.  The code is somewhat tedious to
install, but not terribly difficult, and the installation process
actually makes the existing device drivers simpler.  The async
driver works only on devices that use `generic raw I/O', which
includes all the 9-track tape drivers after you fix a certain bug
that recurs in all except the TU78 driver.  (To see the bug, try

	dd if=/vmunix of=/dev/rmt8 bs=120k

Note that on ts, tu, and tj tapes, this always produces a bogus
hard error.  Not that you could in fact use 63kB tape blocks
reliably, although there are 8mm cartridge tape drives available
on which such blocks *would* be reliable, since they are another
of the 512-byte-blockers.  Except they use the tmscp driver....)

The bug is rather simple.  Here is a fragment of a typical
driver (ts11 in this case):

	tswrite(dev, uio)
		dev_t dev;
		struct uio *uio;
	{
		int errno;

		errno = tsphys(dev, uio);
		if (errno)
			return (errno);
		return (physio(tsstrategy, &rtsbuf[TSUNIT(dev)], dev,
			B_WRITE, minphys, uio));
	}

Whenever tswrite is called (which is once per raw tape write operation),
it calls tsphys:

	tsphys(dev, uio)
		dev_t dev;
		struct uio *uio;
	{
		register int tsunit = TSUNIT(dev);
		register daddr_t a;
		register struct ts_softc *sc;
		register struct uba_device *ui;

		if (tsunit >= NTS || (ui=tsdinfo[tsunit]) == 0 ||
		    ui->ui_alive == 0)
			return (ENXIO);

(tsphys never returns ENXIO, because the above conditions are caught
in tsopen and the device will not open; these tests may be deleted.)

		sc = &ts_softc[tsunit];
		a = bdbtofsb(uio->uio_offset >> 9);

(find the `block number' (512 byte blocks) of the first block of
this transfer, then convert block device blocks to file sytem blocks)

		sc->sc_blkno = a;
		sc->sc_nxrec = a + 1;

(set blkno and nxrec such that the driver allows the transfer without
attempting to seek)

		return (0);

(and say it is OK)

	}

If tsphys did not set blkno and nxrec, the driver would try to seek
on a raw tape device.  Now, suppose we do a transfer of 64kB.
tswrite calls tsphys, which sets up blkno and nxrec; it then calls
physio(tsstrategy,...,minphys).  physio sets up a buffer with
bp->b_blkno = uio->uio_offset >> 9, the same expression that appears
before the bdbtofsb() in setting blkno.  It then calls minphys(),
which truncates bp->b_bcount from 64k to 63k, and calls tsstrategy
(via physstrat), which decides not to seek, starts the transfer,
and returns.  physstrat waits; the transfer finishes; physio notes
that it succeeded but there is more to do; so physio sets bp->b_bcount
to 1k and calls tsstrategy again, and tsstrategy decides to seek.

Oops!

The corrected version of the driver `write' routine looks like this:

	tswrite(dev, uio)
		dev_t dev;
		struct uio *uio;
	{

		return (physio(tsstrategy, &rtsbuf[TSUNIT(dev)], dev,
			B_WRITE, minphys, uio));
	}

tsphys() is deleted entirely.  The code that used to appear in
tsphys() is now in tsstart():

	if (bp == &ctsbuf[tsunit]) {
		/*
		 * Execute control operation with the specified count.
		 */
		um->um_tab.b_active =
		    bp->b_command == TS_REW ? SREW : SCOM;
		sc->sc_ts.t_cmd.c_repcnt = bp->b_repcnt;
		goto dobpcmd;
	}
	/*
	 * For raw I/O, save the current block
	 * number in case we have to retry.
	 */
	if (bp == &rtsbuf[tsunit]) {
		if (um->um_tab.b_errcnt == 0)
			sc->sc_blkno = bdbtofsb(bp->b_blkno);
	} else {

(sc_nxrec is not used here; the test against it is now inside the
`else'.) Now with one small addition beyond the generic raw code,
tsread and tswrite can be eliminated entirely.  If physio will set
a new flag in each buffer header, tests of the form

	if (bp == &rtsbuf[unit])

may be changed to

	if (bp->b_flags & B_RAW)

I have done this and it works quite well.
-- 
In-Real-Life: Chris Torek, Univ of MD Computer Science, +1 301 454 7163
(still on trantor.umd.edu because mimsy is not yet re-news-networked)
Domain: chris at mimsy.umd.edu		Path: ...!uunet!mimsy!chris



More information about the Comp.unix.wizards mailing list