TweetFollow Us on Twitter

Mar 94 Top 10
Volume Number:10
Issue Number:3
Column Tag:Think Top 10

Non-interrupt
Completion Routines

By Colen Garoutte-Carson, Symantec Technical Support, Symantec Corp.

This is a monthly column written by Symantec’s Technical Support Engineers intended to provide you with information on Symantec products. Each month we cover either a specific application of tools or a “Q&A” list.

Non-interrupt Completion Routines

If you’re using high-level Macintosh toolbox routines to manipulate files, devices, or drivers, chances are that you’re program could be executing much more efficiently.

The most efficient way to do any file or driver access is asynchronously. When you write to a file with the FSWrite routine, your application, as well as any background process, is suspended while FSWrite waits for the disk to seek to the appropriate sector. Upon reaching that sector, the drive controller issues an interrupt, which prompts the CPU to start writing to the file. The time lost while waiting for the disk to seek is time that could be better spent. When you write to a file asychronously, control returns to your program immediately. The only time taken is taken after the disk interrupt has been issued, at which time the operating system does the appropriate work, and, often, waits for yet another interrupt to be issued. The process of performing an asynchronous routine is entirely transparent to your application.

All high-level file and device IO related Macintosh toolbox routines have low-level counterparts. These routines begin with the letters “PB”, probably because they are each passed a structure called a parameter block. Rather than passing arguments necessary to complete an operation, fields of the parameter block are assigned values, and a pointer to that parameter block is passed to the PB routine. Parameter blocks are often used both to pass information to a PB routine, and return information to the calling routine.

The second, and final argument to a PB routine is a Boolean, which is set to false for synchronous, or true for asynchronous execution. All PB routines have only these two arguments.

Different PB routines require different types, and different sizes of parameter blocks. For example, the original set of Macintosh file IO routines, designed to be used on the original flat volume architecture, require a structure called a ParamBlockRec. In C and C++, a ParamBlockRec is actually a union of a number of other parameter blocks types. In Pascal, a cased record is used. Different PB routines require variables from different union members. PBRead and PBWrite require an IOParam parameter block. PBCreate and PBDelete require a FileParam parameter block. You can either use a ParamBlockRec structure for calling all of these routines, or the exact structure a particular routine requires. You can use the same ParamBlockRec for many different consecutive PB routines because it’s allocated to the size of the largest member of the union. But, if you use an IOParam, you will not be able to use it with any PB routines other than those which accept IOParam’s. For more information on the organization of the many types of Parameter Blocks, take a look at Inside Macintosh : Files, Inside Macintosh : Devices, or the Think Reference.

All parameter blocks begin with the same basic structure, a ParamBlockHeader. The first two fields of a ParamBlockHeader, qLink and qType, are also fields of a QElem structure. QElem structures are queue element structures used internally by a driver. QElem structures and parameter blocks are interchangeable. The QElem structures that a driver is using at any given moment are probably parameter blocks, passed to it by a PB routine.

Even if you are not using asynchronous IO, calling the low level PB routines instead of their high level counterparts can save time. High level routines in turn call PB routines. Filling out the parameter blocks and calling the PB routines yourself takes more code space, but saves a little bit of CPU time. Also, sometimes there can be more flexibility built into the PB version of a routine.

There are few functional differences between using PB routines synchronously and using the high level routines, but calling PB routines asynchronously adds some new considerations to your coding. The parameter block structure you pass to a PB routines must remain valid, and not move in memory until the PB routine is completed. This means that you cannot pass a pointer to a parameter block declared locally within your function if your function may be exited before the routine is completed. This also means that you cannot reuse a parameter block until the PB routine you’ve used it in last is completed. Attempting to reuse a parameter block, which is currently being used by another PB routine, would modify information which may still be in use. This could cause unpredictable results.

Once you know an asynchronous routine has completed, you can use the information it returns, if any, and reuse or deallocate the parameter block. Often it’s desirable to chain together a series of asynchronous routines. For example, you could open a file, write to the file, then close the file, all asynchronous. The completion of each of those operations could trigger the next.

There are two ways to find out if an asynchronous routine has completed. The most often used of which is a completion routine. There is a variable within the ParamBlockHeader called ioCompletion. This variable is a ProcPtr. If this variable is non-zero, it’s value will be used as an address and will be jumped to automatically when the asynchronous routine is completed.

At first glance, this appears to be the perfect solution to incorporating asynchronous routines into your program. Unfortunately, the completion routine may be executed at any time, including during interrupt time, or at a point in time when your application is not the current process your computer is handling. This means that you cannot call any routines which move memory. Well over half of the Macintosh Toolbox routines move memory, including most of the fundamental ones. This also means that, at the point your completion routine is called, your routine may not have access to your application’s global variables. There is a mechanism which allows you to gain access to your application’s global variables, but using it means you have to take yet another matter into consideration. You could potentially be modifying a global variable which another part of your code could be in the middle of using. This could be very hazardous unless you’ve written your code to take sudden changes in variables into consideration.

The second way to find out if an asynchronous routine has been completed is to poll the ioResult field of it’s parameter block. The ioResult field, like ioCompletion, is a variable within the ParamBlockHeader structure. While the asynchronous routine is executing, this variable has a positive value. When the asynchronous routine is completed, this variable holds an error code, which is a either a negative value, or zero (noErr).

Polling the ioResult field can be a lot more difficult than using a completion routine. In order to poll an ioResult field, you must have access to the parameter block. And, in order to really let the rest of your program continue to go about it’s business, you’d have to poll ioResult fields in your programs main loop, or at some other point in your code that gets executed repeatedly. In order to do this, you’d need access to all of your parameter blocks globally. And, if you did have access to all of them globally, you’d need a mechanism to find out which ones are being used, and which ones are idle. This is not very intuitive.

The best use for ioResult polling is for doing something in particular while you wait for your asynchronous routine to complete, not to make asynchronous routines transparent to the rest of your program. With ioResult polling, you could execute a routine asychronous, and use the little bit of time immediately after it’s started executing to update windows, etc, while you wait for it to complete. As far as the rest of your program is concerned, this might as well be considered sychronous execution.

For education purposes, we’re going to try to mix these two methods to develop a new means of handling asynchronous completion routines. A mechanism which we shall outline will allow you to provide a routine to be executed when the asynchronous routine is completed, yet will execute the routine during normal process time. This is done by polling the ioResult field until the asynchronous routine is complete, and then executing a specified completion routine.

This mechanism offers the best of both worlds, and the drawbacks of neither. You are not limited to non-memory moving routines, and you are able to specify an action be taken at the completion of an asynchronous routine. Because the completion routine is not executed at interrupt time, you’re free to deallocate the parameter block with DisposPtr, which would otherwise be off limits.

Our non-interrupt asynchronous completion routine mechanism will maintain a linked list of installed parameter blocks, periodically search this linked list for completed routines, and remove them from the list and execute their completion routines when completed.

First, let’s declare our linked list structure :


/* 1 */
typedef void (* CompletionProc)(ParmBlkPtr, long);

struct CompletionHandlerEntry {
 ParmBlkPtr pb;
 CompletionProc  doneProc;
 long   refCon;
 struct CompletionHandlerEntry** nextEntry;
};
typedef struct CompletionHandlerEntry CompletionHandlerEntry;

In Pascal, this would be :

/* 2 */
type
   CompletionHandlerEntryHand = ^CompletionHandlerEntryPtr;
   CompletionHandlerEntryPtr = ^CompletionHandlerEntry;
   CompletionHandlerEntry = record
      pb: ParmBlkPtr;
      doneProc: ProcPtr;
      refCon: longint;
      nextEntry: CompletionHandlerEntryHand;
   end;

Notice that in the C version of this structure we’ve declared a function pointer type. There is no equivalent to this in Pascal. We’ll discuss how the function is executed in Pascal later. Also, notice that in addition to storing the parameter block and completion routine, we are also storing a refCon variable in this structure. refCon is a commonly used name for a variable which the programmer can use for his or her own purposes. Because it’s a 4-byte value, you can allocate a pointer or a handle and safely store it in a refCon. This means that you can piggy-back any information you would like onto each parameter block in this completion routine mechanism.

Our program will have to maintain a linked list of these structures in order to implement this mechanism. The first entry in the list will have to be kept track of as a global variable, like so :

CompletionHandlerEntry **Asynchs;

In Pascal:

var
   Asynchs: CompletionHandlerEntryHand;

When the list is empty, this variable will be NULL (NIL, or zero). When there are entries in the list, this global variable will point the first entry. The list can be walked through by following the nextEntry pointer to the next entry in the list. The last entry in the list will have a nextEntry of NULL. At the start of your program you should make sure to initialize Asynchs to NULL.

Next we need to write two routines which act on these structures. One to allow you to add a parameter block to the list, and another to scan the list for completed routines. First, the routine to add the parameter block to the list :


/* 3 */
void InstallCompletion(ParmBlkPtr pb, CompletionProc theProc, 
 long refCon)
{
 CompletionHandlerEntry ** NewEntry;

 NewEntry = (CompletionHandlerEntry **)
 NewHandle(sizeof(CompletionHandlerEntry));
 if (NewEntry == NULL)
 {
 while (pb->ioParam.ioResult > 0)
 ;
 (*theProc)(pb, theProc);
 }
 else
 {
 (**NewEntry).pb = pb;
 (**NewEntry).doneProc = theProc;
 (**NewEntry).refCon = refCon;
 (**NewEntry).nextEntry = Asynchs;
 Asynchs = NewEntry;
 }
}

In Pascal :


/* 4 */
procedure InstallCompletion (pb: univ ParmBlkPtr;
   theProc: ProcPtr; refCon: longint);

var
   NewEntry: CompletionHandlerEntryHand;

begin
   Handle(NewEntry) := 
      NewHandle(sizeof(CompletionHandlerEntry));
   if NewEntry = nil then
      begin
         while pb^.ioResult > 0 do
            ;
         CallCompletion(pb, theProc);
      end
   else
      begin
         NewEntry^^.pb := pb;
         NewEntry^^.refCon := refCon;
         NewEntry^^.doneProc := theProc;
         NewEntry^^.NextOne := Asynchs;
         Asynchs := NewEntry;
      end;
end;


This routine will first allocate a new completion handler entry. If it fails, it will wait for the asynchronous routine to complete and then call the completion routine. This means the asynchronous routine will be performed pseudo-synchronously when memory is very low. If the allocation is successful, the completion handler entry is added to the beginning of our linked list.

There is room to expand upon this routine. It could be modified to use a default completion routine if a completion routine of NULL is passed to it. The default completion routine would probably only deallocate the parameter block. This routine could also be modified to save certain variables in the linked list structure to be restored when the completion routine is called. This could be very useful if you change your current resource file often. You could save the current resource fork when the asynchronous routine is installed, and restore it temporarily when the completion routine is called.

Next, we need a routine to scan this linked list for completed asynchronous routines, and execute their completion routines:


/* 4 */
void DoCompletions()
{
 CompletionHandlerEntry **curEntry = Asynchs;
 CompletionHandlerEntry **parentEntry = NULL;

 while (curEntry)
 {
 if ((**curEntry).pb->ioParam.ioResult <= 0)
 {
 if (parentEntry)
 (**parentEntry).nextEntry = (**curEntry).nextEntry;
 else
 Asynchs = (**curEntry).nextEntry;
 (*(**curEntry).doneProc) 
 ((**curEntry).pb, (**curEntry).refCon);
 DisposHandle((Handle)curEntry);
 curEntry = NULL;
 if (parentEntry)
 curEntry = (**parentEntry).nextEntry;
 else
 curEntry = Asynchs;
 }
 else
 {
 parentEntry = curEntry;
 curEntry = (**curEntry).nextEntry;
 }
 }
}

In Pascal :


/* 6 */
procedure CallDoneProc (pb: ParmBlkPtr; refCon: longint; 
   jmpAddr: ProcPtr);

inline
   $205F, $4E90;

procedure DoCompletions;

var
   curEntry: CompletionHandlerEntryHand;
   parentEntry: CompletionHandlerEntryHand;

begin
   curEntry = Asynchs;
   parentEntry = nil;
   while (curEntry <> nil) do
      begin
         if (curEntry^^.pb^.ioResult <= 0) then
            begin
               if (parentEntry <> nil) then
                  parentEntry^^.nextEntry :=
                     curEntry^^.nextEntry
               else
                  Asynchs := curEntry^^.nextEntry;
               CallDoneProc(curEntry^^.pb, curEntry^^.refCon, 
                  curEntry^^.doneProc);
               DisposHandle(Handle(curEntry));
               curEntry := nil;
               if (parentEntry <> nil) then
                  curEntry := parentEntry^^.nextEntry
               else
                  curEntry := Asynchs;
            end
         else
            begin
               parentEntry := curEntry;
               curEntry := curEntry^^.nextEntry;
            end;
      end;
end;

Notice the inline Pascal procedure. What it does is strip the last parameter off the stack, and jump to the address in memory that it represents with the rest of the parameters intact. Although this works, and is a commonly used way to execute ProcPtr’s in Pascal, the method we can use in C uses registers much more efficiently.

DoCompletions should be added to your event loop, or to some other point in your code which is executed repeatedly.

One important thing to note about this mechanism is that you cannot call InstallCompletion from within a completion routine. This is because of how the DoCompletion routine is organized. At the point your completion routine is being called, the linked list is being acted upon. Installing a new completion handler entry could cause the linked list to be incorrectly maintained. This can be fixed. Consider it an exercise to better familiarize yourself with the routines used in the mechanism. It would either be a matter of 1) keeping a list of completed asychronous routines, removing them from the linked list, then executing their completion routines, or 2) making DoCompletions execute in such a way that modifications to the linked list during the call to the completion routine are compensated for.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »

Price Scanner via MacPrices.net

Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.