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

Monosnap 3.5.9 - Versatile screenshot ut...
Monosnap lets you capture screenshots, share files, and record video and .gifs. Features Capture Capture full screen, just part of the screen, or a selected window Make your crop area pixel... Read more
Tweetbot 3 for Twitter 3.3.1 - Popular T...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Tunnelblick 3.8.0 - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Bookends 13.2.5 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Quicken 2019 5.11.2 - Complete personal...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
Bookends 13.2.5 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Quicken 2019 5.11.2 - Complete personal...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
Dashlane 6.1927.0 - Password manager and...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more
Capo 3.7.4 - Slow down and learn to play...
Capo lets you slow down your favorite songs so you can hear the notes and learn how they are played. With Capo, you can quickly tab out your songs atop a highly-detailed OpenCL-powered spectrogram... Read more
BetterTouchTool 3.153 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more

Latest Forum Discussions

See All

Void Tyrant guide - Tips and tricks for...
Void Tyrant continues to get a lot of play in these parts. Probably because the game is just so deep and varied. The next stop on our guide series for Void Tyrant is class-specific guides. First up is the Knight, as it’s the first class anyone has... | Read more »
Summon beasts and battle evil in epic re...
Imagine a tale of conlict between factions of good and evil, where rogueish heroes summon beasts to aid them in them in warfare and courageously battle dragons over fields of scorched earth and brimstone - that's exactly the essence of epic fantasy... | Read more »
Upcoming visual novel Arranged shines a...
If you’re in the market for a new type of visual novel designed to inform and make you think deeply about its subject matter, then Arranged by Kabuk Games could be exactly what you’re looking for. It’s a wholly unique take on marital traditions in... | Read more »
TEPPEN guide - The three best decks in T...
TEPPEN’s unique take on the collectible card game genre is exciting. It’s just over a week old, but that isn’t stopping lots of folks from speculating about the long-term viability of the game, as well as changes and additions that will happen over... | Read more »
Intergalactic puzzler Silly Memory serve...
Recently released matching puzzler Silly Memory is helping its fans with their intergalactic journeys this month with some very special offers on in-app purchases. In case you missed it, Silly Memory is the debut title of French based indie... | Read more »
TEPPEN guide - Tips and tricks for new p...
TEPPEN is a wild game that nobody asked for, but I’m sure glad it exists. Who would’ve thought that a CCG featuring Capcom characters could be so cool and weird? In case you’re not completely sure what TEPPEN is, make sure to check out our review... | Read more »
Dr. Mario World guide - Other games that...
We now live in a post-Dr. Mario World world, and I gotta say, things don’t feel too different. Nintendo continues to squirt out bad games on phones, causing all but the most stalwart fans of mobile games to question why they even bother... | Read more »
Strategy RPG Brown Dust introduces its b...
Epic turn-based RPG Brown Dust is set to turn 500 days old next week, and to celebrate, Neowiz has just unveiled its biggest and most exciting update yet, offering a host of new rewards, increased gacha rates, and a brand new feature that will... | Read more »
Dr. Mario World is yet another disappoin...
As soon as I booted up Dr. Mario World, I knew I wasn’t going to have fun with it. Nintendo’s record on phones thus far has been pretty spotty, with things trending downward as of late. [Read more] | Read more »
Retro Space Shooter P.3 is now available...
Shoot-em-ups tend to be a dime a dozen on the App Store, but every so often you come across one gem that aims to shake up the genre in a unique way. Developer Devjgame’s P.3 is the latest game seeking to do so this, working as a love letter to the... | Read more »

Price Scanner via MacPrices.net

B&H drops prices another $50 on clearance...
B&H Photo has dropped prices on clearance 2018 13″ MacBook Airs by a further $50 with models now available for $250 off Apple’s original MSRP. Overnight shipping, or expedited shipping, is free... Read more
Find the best sales & lowest prices on Ap...
Our Apple award-winning price trackers are the best place to look for the best sales and lowest prices on Apple gear. Scan our price trackers for the latest information on sales, bundles, and... Read more
Apple has clearance 2018 13″ MacBook Airs now...
Apple has Certified Refurbished 2018 13″ MacBook Airs available starting at only $849. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is shipped free. The... Read more
Save $400 on the 8-Core iMac Pro today at Ama...
Amazon has the base 8-core iMac Pro on sale today for $4599 including free shipping. Their price is $400 off Apple’s MSRP, and it’s the currently lowest price available for an iMac Pro. For the... Read more
Flash sale! New 11″ 1TB WiFi iPad Pros for th...
Amazon has the 11″ 1TB WiFi iPad Pro on sale today for only $1199.99 including free shipping. Their price is $350 off Apple’s MSRP for this model, and it’s the lowest price ever for a 1TB 11″ iPad... Read more
Weekend Deal: 2018 13″ MacBook Airs starting...
B&H Photo has clearance 2018 13″ MacBook Airs available starting at only $999 with all models now available for $200 off Apple’s original MSRP. Overnight shipping, or expedited shipping, is free... Read more
Apple has clearance 10.5″ iPad Pros available...
Apple has Certified Refurbished 2017 10.5″ iPad Pros available starting at $469. An Apple one-year warranty is included with each iPad, outer shells are new, and shipping is free: – 64GB 10″ iPad Pro... Read more
Apple restocks refurbished iPad mini 4 models...
Apple has restocked Certified Refurbished 32GB iPad mini 4 WiFi models for $229 shipped. That’s $70 off original MSRP for the iPad mini 4. Space Gray, Silver, and Gold colors are available. Read more
Apple, Yet Again, Is Missing An Ultraportable...
EDITORIAL: 07.19.19 Prior to the decision made by Apple earlier this month to retire the thin and light MacBook model with a 12-inch retina display, the Cupertino, California-based company offered,... Read more
Verizon is offering a 50% discount on iPhone...
Verizon is offering 50% discounts on Apple iPhone 8 and iPhone 8 Plus models though July 24th, plus save 50% on activation fees. New line required. The fine print: “New device payment & new... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**711346BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 001095-Chesterfield-Store **Job Description:** Read more
Best Buy *Apple* Computing Master - Best Bu...
**704899BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000135-Pleasant Hill-Store **Job Description:** **What does Read more
Best Buy *Apple* Computing Master - Best Bu...
**707083BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000045-Rockford-Store **Job Description:** **What does a Read more
Geek Squad *Apple* Master Consultation Agen...
**702908BR** **Job Title:** Geek Squad Apple Master Consultation Agent **Job Category:** Services/Installation/Repair **Location Number:** 000360-Williston-Store Read more
Best Buy *Apple* Computing Master - Best Bu...
**711023BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000012-St Cloud-Store **Job Description:** **What does a Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.