Demystifying The GS/OS Cache MATT DEATHERAGE GS/OS has given Apple IIgs users an important capability: caching. To the newly initiated, the mysteries of the GS/OS cache may still seem profound. This article clarifies the basics of the caching algorithm and offers useful pointers for working safely and efficiently. In the past couple of years, the Apple IIgs system software has grown by leaps and bounds. With the introduction of thousands of new features and capabilities, one very important one often goes unnoticed. GS/OS is the first Apple II operating system of any kind to provide a comprehensive caching implementation. Although the cache is an important part of GS/OS, its purpose and nature are sometimes misunderstood. Some people create huge caches in the hope that peripherals will start behaving like RAM disks. Others set the cache size to zero because they think it's wasting time and memory. Some developers mistakenly believe their program has absolute control over caching, which is only true if they're writing device drivers. This article reaches into the murky depths of your IIgs's memory and allows you to examine the cache in the light you would normally use, say, to read develop by. CACHE FUNDAMENTALS Let's start at the beginning, with the basics. What's a cache? How is it managed? How big is it? How does the GS/OS cache differ from that of the Macintosh? Read on. WHAT'S A CACHE? A cache is a part of memory in which the operating system can keep a spare copy of information read from and written to a device, in an attempt to decrease disk access time. Many people use RAM disks to decrease disk access time. Because the operating system doesn't actually have to physically manipulate any media to access information on a RAM disk, the information is saved and retrieved at a very nice clip, exceeded only by the speed at which the operating system can read information already in main memory. In a caching algorithm, that is exactly what happens. When information is read from a device, the operating system reads from the physical media and returns the information to the caller who asked for it. Additionally, the operating system keeps a spare copy of the information in the cache. The next time a caller requests information, the operating system first looks in the cache to see if it already has it from a previous media access. If it's there, the operating system simply moves the data into the caller's buffer, completing the call without having to actually read anything from disk. Information to be written can also be cached. The operating system writes the information to the disk and also writes it to the cache. This method, called a write-through cache, ensures that the information in the cache is never more recent than the information on the disk. GS/OS uses a write- through cache except in one special situation--during a write-deferral session. THE CACHE MANAGER AND WHAT IT CACHES In GS/OS, several distinct managers handle different parts of the operating system or environment. Many of these managers (the Loader and the Device Manager, for example) are familiar to application and driver authors. Another, more obscure manager takes care of the cache; plainly enough, it's called the Cache Manager. The Cache Manager handles the storage of the cache and all requests involving it. The GS/OS Cache Manager only caches one thing: blocks. Blocks are what both generated and loaded drivers read from block devices. They can be traditional ProDOS-sized 512-byte blocks from traditional ProDOS devices; they can be 512-byte blocks from nontraditional ProDOS devices, such as network volumes or nondisk devices; or they can be odd-sized blocks from odd peripherals. If you had a GS/OS loaded driver to read 981-byte blocks from a 20-gigahertz, 35-terabyte RAM disk hooked to your Apple IIgs through a slot-based card, GS/OS could cache those blocks. (Whether or not any file system translators in the system could use the device is another story.) The Cache Manager does not cache anything having to do with printers, modems, or other character devices. THE SIZE OF THE CACHE The size of the cache is determined by a battery RAM (BRAM) parameter. When the Cache Manager is initialized as part of the GS/OS boot process, it retrieves this value from the battery backed-up RAM and adjusts so that the given value is taken as the maximum size to which the cache can grow. The BRAM parameter is currently number $81 and represents the cache size in 32K increments (a value of 2 would indicate 64K). You should know that the location and interpretation of the cache size parameter are not guaranteed at this point, and relying on the location or interpretation of the parameter might get you into trouble. When they are guaranteed, Apple II Developer Technical Support will release a Technical Note detailing this point, or the GS/OS Reference will be revised to document it. The size of the cache is not an exact number but an exact maximum, and it is that only when nonzero. If the user has set the cache size to nonzero, that value (which will always be an increment of 32K) is the maximum cache size. If the user has set the cache size to zero, GS/OS allocates a 16K cache for system purposes--so that system components such as file system translators (FSTs) and drivers (both generated and loaded) can take advantage of the speed increase the cache provides. There is no way to completely turn off caching under GS/OS. When the cache is initialized, it is empty and has zero size. As blocks are added to it, it grows as necessary to accommodate the increased use until the maximum size is reached. If no one ever asks for blocks to be cached, the cache remains empty and occupies no memory. WHAT HAPPENS WHEN THE CACHE IS FULL Having a maximum cache size implies that there can be a problem -- what happens when nothing else can be written to the cache because there is no room in cache memory for more information? How does GS/OS behave when there's no room in the Cache Inn? The operating system, through the cache algorithm, has a few options for handling the possibility that the cache will be full. The most obvious option is for the algorithm to shrug its bit-encoded shoulders and say, "Well, the cache is full, so nothing else will be written to it." Information that comes knocking at the door thereafter is written to disk only and read from disk each time it's needed. Most caching algorithms, including the one used by GS/OS, are a little bolder. They attempt to identify which blocks in the cache have actually been used and to keep those blocks in the cache, simultaneously removing from the cache the blocks that have not been used. The rationale for this is that if a block hasn't been read from the cache in a long time, it's probable that no one's going to want to read it again for a while, and the cache can be better used by a new block. For example, when GS/OS is booted, the file START.GS.OS is read from the disk. Suppose, for the sake of argument, that this file's information was placed in the cache. This file isn't likely to be read again by the operating system or by any application unless GS/OS has to be reloaded, which under version 5.0 and later never happens. This file could, therefore, be sitting in the cache, taking up valuable cache space that subsequently can't be used by directory blocks, bitmap blocks, or even program files that will be needed repeatedly, such as the Finder or APW commands. The strategy used by most caching algorithms tries to keep space in the cache free for use by directory blocks, bitmap blocks, and program files that will be needed repeatedly. One popular caching algorithm keeps all the blocks in a chain with the most-recently used block at the beginning of the chain and the block not used for the longest time at the end. When there's no more room in the cache, the blocks at the end of the chain are removed, making room for new blocks. This algorithm, straightforwardly called a least-recently used (LRU) caching algorithm, is what GS/OS uses. Some blocks, however, can't be kicked out of the cache by the LRU algorithm. These are the blocks placed in the cache by a write-deferral session. When such a session is in progress, some or all of the information written to files is write-deferred, which means GS/OS keeps the information around in the cache instead of taking the time to write it all to physical media. This is accomplished in different ways for different file systems; each file system translator behaves in the way it can achieve the best performance during a session. For example, the ProDOS FST writes actual data to the disk but not system-level information such as directories or bitmap blocks. If any write-deferred blocks are in the cache, the Cache Manager will not purge them to make room for new blocks until they have been written to disk. If the cache fills up with nonpurgeable blocks and another nonpurgeable block must be added, a feature known as AutoFlush, which was new to System Software 5.0, takes over, stops the session, and flushes all write-deferred blocks to disk so that new ones can be added. This has the effect of breaking a session into lots of mini-sessions, each exactly long enough to get the best possible use from the cache. Blocks in the cache can also be deleted from it by another method besides the LRU algorithm. If a driver detects a disk-switched condition, which normally means that an on-line volume has been taken off-line, it makes the SET_DISKSW.System Service call, which enables the file system translators to remove all blocks belonging to the switched disk from the cache so that no blocks are in the cache for volumes that aren't currently on-line. This ensures that no one accidentally reads from the device and gets an old block from the cache, or that no blocks are in the cache for volumes that aren't currently on-line. DIFFERENCES FROM THE MACINTOSH The GS/OS Cache Manager is different from the caching implementation on the Macintosh in two important ways. The first difference is in the way memory is allocated. On the Macintosh, if the user sets the disk cache size to 128K, a 128K block of memory stays allocated for the disk cache unless someone resizes the cache. Under GS/OS, memory for cached blocks is allocated as needed up to the maximum set by the user. The other difference between GS/OS and Macintosh is in the size of the blocks that can be cached. The GS/OS cache can handle a block of any size; if a device deals in 2048-byte blocks--as some CD-ROM discs do--GS/OS will cache a 2048-byte block. The Macintosh Cache Manager, on the other hand, can only cache 512-byte blocks. HOW APPLICATIONS REQUEST TO USE THE CACHE Applications--or desk accessories, inits, or anyone who makes GS/OS system calls--request to use the cache through cache-related parameters to the system calls. Specifically, applications can ask that data read from files be cached by using the cachePriority field of class one (and only class one)Read and Write calls. CACHING CAN BE REQUESTED BY CLASS ONE CALLS An application requests caching by setting the cachePriority field in the GS/OS parameter block of Read and Write calls. (These are the only two GS/OS system calls with this field in the parameter block. The system calls DRead and DWrite do not have a cachePriority field, as the Device Manager always disables caching of blocks read or written through these calls.) A value of $0000 for the word-length parameter in the cachePriority field is the norm (and the default if this parameter is omitted) and indicates that blocks involved in this call should not be cached. A value of $0001 identifies the blocks involved as candidates for caching. Only a value of $0001 in this field will cause files read at the application level to be considered for caching. The fact that a caching request has been made by an application doesn't mean that it will be fulfilled. Applications do not call the Cache Manager; other system components do. Those components (file system translators and drivers) may deny the request when it doesn't make sense or is dangerous for the file's blocks to be cached. CACHING CANNOT BE REQUESTED BY CLASS ZERO CALLS Applications written using class zero calls (including older ProDOS 16 applications) cannot request that their files be placed in the cache. To make such a request, the application must be changed to use class one calls. At first this seems a little harsh and arbitrary, but it's quite the opposite. GS/OS could treat class zero file calls one of two ways--it could automatically cache everything, or automatically cache nothing. If it cached everything, reading any file larger than the maximum cache size would flush all the cached blocks, no matter how frequently they were used. This would force them all to be reread from disk the next time they were needed. Such a method is normally a grand waste of time, since most files on the Apple IIgs are typically read once. Most programs still follow the old Apple II method of "read the file, modify it, and write it back," and writing such files to the cache only serves to slow things down while the flushed blocks are reread from disk. Better methods for file manipulation exist these days, but the system was designed for maximum performance using the methods of the time. An application knows best which files it will be reading from disk often enough to benefit from caching. Such decisions are often reached after long performance studies of cached reads vs. purgeable handles in memory. For more pointers on application-level caching, see GS/OS Technical Note #3, Pointers on Caching. WRITE-DEFERRAL SESSIONS AND THE CACHE Applications can also speed up disk-intensive operations through the use of write-deferral sessions. An application begins such a session by giving the GS/OS system call BeginSession. This places all subsequent writes in a mode where some or all of the blocks written to disk are placed only in the cache and not on the media. Deferred blocks are then written to the media when the EndSession call is made. If theEndSession call is not made or if it's made before the files being written to are closed, some of the blocks for the files written may be on the disk while others are in the cache only. This damages disks in most file systems as fast as fingerprints on the media. Be sure to always issue EndSession calls on every exit path after a BeginSession call to prevent blocks written from being only in the cache and not on the disk. And be sure to close all open files before calling EndSession, since the operating system can get caught with write-deferred blocks in open files if the session is ended and the disk is ejected before the files are closed. HOW CACHE REQUESTS ARE FULFILLED GS/OS attempts to make sure that although an application can cache blocks if it so chooses, by default the most intelligent use possible of the caching capabilities will be made. It does this by filtering out possibly bogus requests for caching at several levels. When an application makes a request to use the cache, the request is filtered through drivers and/or file system translators. These agents of the Cache Manager evaluate requests to make sure that the most intelligent possible use of the caching capability is made. If an application's request to use the cache is found to be valid, one of these system components calls a System Service routine to add blocks to the cache. THE SYSTEM SERVICE CALLS The ultimate caching authority is at the System Service call level. System Service calls are used by drivers and file system translators to access the routines that act on cache requests. These calls are available only to drivers and file system translators; they are not available to applications. System Service calls are accessed through vectors in the $01/FC00 page and are described in the GS/OS Reference , volume 2, chapter 12. The following four System Service calls--to add blocks, find blocks in the cache, move blocks in and out of the cache, and remove all purgeable blocks in the cache belonging to a switched disk--are the only calls that can be made by drivers. Other cache-related System Service calls enable the system to delete blocks and volumes from the cache, but these are not documented in the GS/OS Reference and can only be used by file system translators and other Apple-supplied system components. CACHE_ADD_BLK ($01/FC08) is the System Service call for a routine that adds blocks to the cache. This call takes several parameters on GS/OS direct page (which is available to drivers and file system translators but not to applications), including information to identify the block by volume, device number, size and block number, and whether or not a write-deferral session is in progress. The Cache Manager is called, and it adds the block to the cache with no filtering. If necessary, the entire purgeable cache (the cache size minus all nonpurgeable write-deferred blocks) will be purged to add the block. The call will return with an error if a block could not be added to the cache, most likely because the entire purgeable cache was smaller than the block to be cached. CACHE_FIND_BLK ($01/FC04) is the System Service call for a routine that finds blocks in the cache. It will search the cache for a specified block, returning a pointer to it if it is found. The cache can be searched by device number, so a device driver can find all blocks it has cached, or by volume ID, so that a file system translator can find all blocks it has cached (when a write-deferral session is in progress). MOVE_INFO is the System Service call for a memory-moving routine. This routine is called by drivers and file system translators to move data in and out of the cache. SET_DISKSW is the System Service call for a routine that kicks out all purgeable blocks in the cache belonging to a switched disk. If SET_DISKSW is called while a write-deferral session is in progress involving closed files on that device, GS/OS puts up a dialog box warning that the disk was prematurely ejected and that the disk's structure may be damaged. There are two main parts of GS/OS that can use these System Service calls -- drivers and file system translators. THE ROLE PLAYED BY DRIVERS Drivers filter cache requests passed on from the application and file system translator levels. With every Driver_Read and Driver_Write command a driver gets from an application, it is passed instructions to take one of three possible caching actions: ¥If the cachePriority word (on GS/OS direct page) is $0000, the block being read or written should not be cached. ¥If cachePriority is nonzero with the high bit clear ($0001--$7FFF), the block should be cached as a normal, purgeable block. ¥If cachePriority has the high bit set ($8000-$FFFF), the block should be cached as a deferred nonpurgeable block. This means a write-deferral session is in progress. In this case, which is only valid for Driver_Write calls (there are no read-deferral sessions in GS/OS), the driver should write the requested block only to the cache and not to the physical media. The end of the session will result in the driver being called again to write all the cached blocks to the physical media. There are instances, such as identifying volumes, in which a file system translator might wish to force a read from physical media rather than from the cache (if the block is in it). In these cases, the FST ID number on GS/OS direct page has the high bit set, telling drivers not to read the block from the cache. A driver is not obligated to cache blocks when requested to, but instead can decide to disable caching completely or selectively. In some cases, the driver should refuse to cache any blocks. For example, this would be appropriate if the driver is for a device that cannot identify a disk-switched condition with any degree of reliability, as with a 5.25" disk. Since the driver can't call SET_DISKSW until it notices the disk has been switched--which could be well after the fact--a block in the cache for such a device might not be deleted when it should be, and thus the driver should refuse to cache any blocks in the first place. Or for example, if a driver can actually read from the media faster than a block can be returned from the cache, it should refuse to cache blocks. THE ROLE PLAYED BY FILE SYSTEM TRANSLATORS File system translators can initiate cache requests themselves, and can filter cache requests passed on from the application level. When a file is opened and read or written, not all of the requests to a driver for information from the physical media are requests for data blocks from the file in question. Many of the read and write requests are for directories, file-system-specific data structures--such as key blocks and index blocks in ProDOS--and bitmap blocks. File system translators need these data structures repeatedly during file operations, and may ask that the blocks involved be cached. The ProDOS FST does this, caching all blocks it reads and writes that aren't passed on to the application. If the caching can be done, the file system translator gets much faster response time the next time it needs those blocks. On ProDOS disks, the caching of the volume directory and volume bitmap give tremendous speed increases since every file opening causes a read of the volume directory, and every write operation causes a read and write of the volume bitmap. The file system translator may also cause caching of a different variety. When a write-deferral session is enabled, the file system translator changes the cachePriority field it passes to the drivers so that blocks are marked as write-deferred. This places them in the cache in a nonpurgeable state but not on the disk. The ProDOS FST asks that all of its ProDOS-specific directory, index, and bitmap blocks be placed in the cache write-deferred, but that all pure data blocks go straight out to disk without caching. This enables most devices to write data to contiguous areas of the disk, so that the head doesn't need to move back and forth writing directories and index blocks and updating bitmaps. All of that is done in one burst at the end of the session. Other file system translators may use the cache during sessions in ways that make sense for that particular file system. For example, the AppleShare FST handles caching in a completely different way, not involving the GS/OS cache at all, since the media could change on the server from another workstation. Most file system translators, however, use the GS/OS cache in ways similar to the way the ProDOS FST uses it. A file system translator can deny an application's caching request if it interferes with optimal system performance. For example, caching a ProDOS file being copied to a ProDOS disk during a session would slow things down, since cached file blocks would be continually removed from a full cache to make room for more write-deferred system blocks from the ProDOS FST. Write-deferral sessions are usually used to copy large numbers of files or create them from memory. In either case, the files in question aren't likely to be read again, so the ProDOS FST eliminates possible overhead by denying requests to cache files during write-deferral sessions. RESETTING THE CACHE FROM THE APPLICATION Only one application-level call acts only on the cache--the ResetCache call (class one only, call number $2026). This call forces the cache to be reinitialized, purging all blocks that are in it and resizing if necessary. Do not issue this call while a write-deferral session is in progress; you can use SessionStatus to see if a write-deferral session is currently active. If you're writing a utility program and suspect that an file system translator has cached something you don't really want around, calling ResetCache will ensure the cache is flushed. The cache is also resized from the battery RAM parameter. As discussed earlier, this parameter is currently $81 and represents the cache size in 32K increments, but this is not guaranteed. ResetCache is called by the RAM CDev to change the size of the cache. To make the call, simply issue it with a parameter block pointer to a word of $0000. There are no parameters. WHERE TO GO FROM HERE This article has described the basics of the GS/OS cache, and has given you an idea of how an application requests caching and how such a request is fulfilled (or not fulfilled, if the request turns out not to be in the best interests of the system). If you want to experiment with the effects of GS/OS calls that request caching, play around with these calls in the Exerciser that comes on the GSBug disk. That's why it's there. It's come a long way from the ProDOS 16 Exerciser. It makes all the calls (inline or stack-based, any class, with the exception of ResetCache), lists all devices, and catalogs directories to 255 levels. It also lets you choose any number of parameters for any class one call, except ResetCache, and allows you to modify memory through a built-in editor--you can visit the Monitor and return if you so choose. (Incidentally, the new calls for System Software 5.0 are coming in a revision soon.) As always, help to Apple Partners on all matters, including GS/OS, is available on AppleLink and MCIMail from Apple II Developer Technical Support at the address AIIDTS. (Macintosh developers can contact Macintosh Developer Technical Support at AppleLink address MACDTS or MCIMail address MACTECH.) If you're not an Apple Partner, you can often get help from knowledgeable programmers on third-party online services, usually in the "Developers" or"Development" forum. MATT DEATHERAGE says, "You always want what you can't get," and the thing he wants most is sleep. This desire probably started in his college days while studying industrial engineering at the University of Oklahoma. In his far too many waking hours he's an Apple II DTS en gineer, a self-proclaimed nonhardware person. "Eat lots of toast" is his motto and the ideal to which he aspires (thanks to his friend Robert Thurman). When he's not gobbling toast or snoozing, he can be found writing music (on the Apple IIGS, naturally) or playing the piano, the clarinet, or the bass clarinet. He isn't married, as far as he can tell.