!pr1
ProDOS MLI Tracing...............................Ken Kashmarek
                                                Eldridge, Iowa

I took Bob S-C's work with ProDOS Snooper (October 1985 AAL) one step further:  I added MLI calls to the information that is collected in the trace table.  By combining the MLI call data with the device driver data, we get a better idea of what is happening.

The entries below all come from slot 6 drive 1.  MLI calls are tagged with an "M" after the hex data.  To support both the MLI calls and device driver calls, the hex output provides the data as it exists in memory without taking into account whether a set of bytes is a two byte memory pointer or a single data byte.

For all calls, the return address is still shown as hi-byte first before the colon.  Data for the device driver parameter is still from $42-$47.  For MLI calls, the return address is to the program that called the routine in the BASIC.SYSTEM global page.  All BASIC.SYSTEM calls go to the $BE00 global page and then to the $BF00 ProDOS global page.  MLI data is the MLI call number followed by the first five bytes of the parameter list (some bytes do not apply if the list is shorter).

The volume in question is labeled /TEST and has one file, ABC, in the root directory.

First of all, issue:  CAT,S6

!lm+5
A6E9:C7 BC BC 02 BC BC M GET PREFIX
A85F:C5 60 01 02 00 03 M ON LINE CALL   + Not used when
EC0C:01 50 00 DC 02 00   READ BLOCK 2   + CAT /TEST entered
A825:C4 BC BC C3 0F 00 M GET FILE INFO
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:01 60 00 DC 06 00   READ Bit Map
B1B9:C8 BC BC 00 8A 01 M OPEN FILE
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EE85:01 60 00 8A 02 00   READ BLOCK 2
B175:CA 01 59 02 2B 00 M READ FILE
B201:CE 01 2B 00 00 03 M SET FILE MARK  + Appears for each
B208:CA 01 59 02 27 00 M READ FILE      + file in directory
B0A5:CC 01 00 C3 CF D0 M CLOSE FILE
B0FB:C5 60 BD BC 00 03 M ON LINE CALL
EC0C:01 50 00 DC 02 00   READ BLOCK 2
B10F:C4 BC BC C3 0F 18 M GET FILE INFO
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:01 60 00 DC 06 00   READ Bit Map
!lm-5

For this simple operation, there are ten MLI calls and eight device driver calls (disk I/O operations).  I do not understand the reason for the Get Prefix call at the beginning.  It would appear that the On Line call and the Get File Info call at the end are unnecessary (we will be checking this out as we go).  On Line returns the volume name, but this should already be available through the prefix or pathname of the directory.  Get File Info information should already be available from the previous call, and the bit map was already read in once.  However, this is a simple catalog operation and may be indicative of some of the steps necessary for more complex catalog operations.

Carrying this one step further, I issued CAT /TEST/DIR.  In this case, the first read of the bit map is not performed.  Next, the former apparently duplicate read of block 2 now turns into a read of block 7, the key block for subdirectory DIR (in /TEXT/DIR; the device driver return address is $EE85, the buffer address is $8A00).  Note:  block 2 is the key block of the root directory.

A Get File Info call for a volume name (/TEST) always reads the bit map.  Therefore, this call is repeated when cataloging a volume, but not when cataloging a subdirectory.  As to the On Line call, it is used to get volume name for the Get File Info call for the free space information for the volume, since the initial catalog command may have been for a subdirectory. This explains (only partially) what appeared to be duplicate reads of the same information.

Now, let's try loading an Applesoft file:  LOAD ABC,S6

!lm+5
A85F:C5 60 01 02 00 03 M ON LINE CALL   + Not used for
EC0C:01 60 00 DC 02 00   READ BLOCK 2   + LOAD /TEST/ABC
A825:C4 BC BC E3 FC 01 M GET FILE INFO
EC0C:01 60 00 DC 02 00   READ BLOCK 2
AC00:CC 00 00 C3 CF D0 M CLOSE ALL FILES
B1B9:C8 BC BC 00 8A 01 M OPEN FILE
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EE85:01 60 00 8A 07 00   READ BLOCK 7
AC22:D1 01 01 02 00 03 M GET FILE EOF
AC4B:CA 01 01 08 09 00 M READ FILE
AC50:CC 01 00 C3 CF D0 M CLOSE FILE
!lm-5

The loaded program is less than 512 bytes in length, so the key block read is the only data I/O operation.  As with the catalog operation, the Get File Info call is used to verify the file type.  Close All Files is used in case the previous program left any open.  Note the Get File EOF call which is used to get the length for the Read File call (which performs the entire load operation).  This example is relatively simple.  Let's check what happens when we create an Applesoft file that is just over 512 bytes in length (changing our seedling file into a sapling file, which requires an index block and two data blocks).

We'll lengthen the program, and then type:  SAVE /TEST/ABC.3

!lm+5
A825:C4 BC BC C3 0F 18 M GET FILE INFO
EC0C:01 60 00 DC 02 00   READ BLOCK 2
ACDC:C0 BC BC C3 FC 01 M CREATE FILE
EC0C:01 60 00 DC 02 00   READ BLOCK 2
F477:00 60 00 DC 00 00   STATUS S6,D1
EC0C:01 60 00 DA 06 00   READ BIT MAP
EC0C:02 60 00 DC 07 00   WRITE BLOCK 7
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:02 60 00 DC 02 00   WRITE BLOCK 2
EC0C:02 60 00 DA 06 00   WRITE BIT MAP
B1B9:C8 BC BC 00 8A 01 M OPEN FILE CALL
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EE85:01 60 00 8A 07 00   READ BLOCK 7
AD0A:CB 01 01 08 5B 02 M WRITE FILE CALL
F477:00 60 01 08 00 00   STATUS S6,D1
EE85:02 60 00 8A 07 00   WRITE BLOCK 7
EC0C:01 60 00 DA 06 00   READ BIT MAP
EC0C:02 60 00 DA 06 00   WRITE BIT MAP
EE85:02 60 00 8C 08 00   WRITE BLOCK 8
EC0C:01 60 00 DA 06 00   READ BIT MAP
AD11:D0 01 5B 02 00 03 M SET FILE E0F CALL
AD16:CC 01 00 C3 CF D0 M CLOSE FILE CALL
EE85:02 60 00 8A 09 00   WRITE BLOCK 9
EC0C:02 60 00 DA 06 00   WRITE BIT MAP
EE85:02 60 00 8C 08 00   WRITE BLOCK 8
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:02 60 00 DC 02 00   WRITE BLOCK 2
!lm-5

This sequence has the same number of MLI calls for a seedling or a sapling file.  The big difference is allocating the index block (block number 8) and additional data blocks.  This also generates additional calls to read and write the bit map.

If the file already exists, and the SAVE command does not change the length, then the Create File call is not executed, there are no accesses to the bit map (block 6), and the index block does not change.  If the file length changes sufficiently to add or delete  blocks, then the bit map is updated and the index block is rewritten (this is forced by the Set File EOF call which adjusts the file length).

Interesting note:  whenever a file is opened, the first data block is always read in, even if the file will subsequently be written to.  Likewise, when a new file is allocated, the first data block is allocated and written, even if no data is placed in the block.

In the above sequence, what appears to be a duplicate read of block 2 (return address $EC0C) is actually a read to separate blocks if the SAVE command was to a subdirectory.  It turns out to be duplicate reads to the subdirectory block, write to the subdirectory, then read and write the root directory.  Sigh.

LOAD /TEST/ABC.3 is similar to the previous load operation, except that we must also read the index block before reading the data blocks, and there are two data blocks rather than one.

Finally, let's try deleting this file:  DELETE /TEST/ABC.3

!lm+5
A825:C4 BC BC E3 04 00 M GET FILE INFO CALL
EC0C:01 60 00 DC 02 00   READ BLOCK 2
9AD7:C1 BC BC 02 BC BC M DESTROY FILE CALL
EC0C:01 60 00 DC 02 00   READ BLOCK 2
F477:00 60 00 DC 00 00   STATUS S6,D1
EC0C:01 60 00 DC 08 00   READ BLOCK 8 (index block)
EC0C:01 60 00 DA 06 00   READ BIT MAP
EC0C:02 60 00 DC 08 00   WRITE INDEX BLOCK (zeroed)
EC0C:01 60 00 DC 07 00   READ BLOCK 7
EC0C:02 60 00 DA 06 00   WRITE BIT MAP
EC0C:01 60 00 DC 02 00   READ BLOCK 2
EC0C:02 60 00 DC 02 00   WRITE BLOCK 2
!lm-5

Again, use Get File Info for file type and status call to see if the disk can be written to.  The bit map is read and written to reflect the freed blocks.  Block 8, the former file index block, is trashed.  I don't know why block 7 is read in.  Trashing the index block makes it very hard to reconstruct a DELETEd file.

At this point, we get a feel for what is happening between the MLI calls and the device driver calls.  Consider how extensive these simple examples become on a hard disk if working down three or four directory levels and at the second, third, or fourth block in each directroy, and the hard disk has five blocks for the bit map (and we need the fifth block because the disk is almost full).  Ouch!

I performed one more test case, far too long to list here.  It involved adding a record to a new sparse random access file.  The new record caused the file to grow to a tree file.  The program used was:

       10 D$ - CHR$(4)
       20 PRINT D$"OPEN /TEST/NAMES,L140"
       30 PRINT D$"WRITE/TEST/NAMES,R936"
       40 PRINT "XXX ... XXX": REM 120 X's
       50 PRINT D$"CLOSE/TEST/NAMES"

This sequence produced eight MLI calls and 29 device driver calls to perform I/O (there were three status calls).  The file ended up with six blocks (master index block, two index blocks, and three data blocks) which generated 12 accesses to read and write the bit map.

A 32 megabyte hard disk, the maximum size supported by ProDOS, requires 16 blocks for the free space bit map.  Obviously, such a disk would suffer quite a performance impact when allocating new files, or adding space to existing files, if the hard disk were more than half full.
!np
Ohio Systems Kache Card....................Bob Sander-Cederlof

After reading Ken's article, I came to the conclusion that the Kache Card or something like it is a MUST for users of large hard disks.

The Kache Card has 256K RAM and a controlling Z-80 on it.  As far as the Apple is concerned, it is just a hard disk controller.  It replaces the controller card which came with your Sider.  But it is smarter.

The Kache card remembers the most recently read or most frequently read data blocks.  Over 2000 of them.  You can see that the entire bit map and at least all the directory blocks associated with the currently used pathnames would stay in RAM on the card.  When ProDOS issues a READ command, the DMA interface on the Kache Card simply transfers the block, without doing anything to the hard disk.

When you write to the hard disk, the Kache Card sends it to the hard disk as well as updating its RAM-based copy.  You can write to the Kache Card faster than the Kache Card writes to the disk, and your program keeps chugging along while the Kache Card spins out the data to the drive.

The Kache Card is expensive ($695), at least relative to the price of a Sider.  A 10-meg Sider is currently $595, and a 20-meg Sider is currently $895.  Nevertheless, if you are using 20 megabytes or more you really need a caching system of some kind.

Of course, you could implement caching inside the operating system.  ProDOS could be modified (perish the thought) to use about 16K RAM from the //e's auxiliary memory to cache the bit map, root directory, and other frequently used blocks, for each on-line hard disk.  (It does not seem profitable to try to cache blocks from floppies, because you can too easily mess things up by removing one floppy and inserting another.)

Like I said, you COULD do it this way.  However, it would be very difficult to make it work with the variety of peripherals available to Apple owners.  It seems much more reasonable to include caching on the controller card, or even inside the hard disk box itself.  I think 256K is probably overkill, 64K per hard drive should be plenty.

My first brush with the Kache Card was not pleasant.  I ended up returning the card with a list of complaints.  They called me about a month later with the news that they had taken my compaints seriously, and rectified the problems I had pointed out.  Or at least most of them.

If you are interested in the Kache Card, contact Ohio Kache Systems Corporation, 75 Tahlequah Trail, Springboro, Ohio 45066.  Or call them at (513)746-9160.  Tell them where you read this.
