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

Hazel 5.3 - Create rules for organizing...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a familiar... Read more
Duet 3.15.0.0 - Use your iPad as an exte...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a iOS companion app. Release notes were... Read more
DiskCatalogMaker 9.0.3 - Catalog your di...
DiskCatalogMaker is a simple disk management tool which catalogs disks. Simple, light-weight, and fast Finder-like intuitive look and feel Super-fast search algorithm Can compress catalog data for... Read more
Maintenance 3.1.2 - System maintenance u...
Maintenance is a system maintenance and cleaning utility. It allows you to run miscellaneous tasks of system maintenance: Check the the structure of the disk Repair permissions Run periodic scripts... Read more
Final Cut Pro 10.7 - Professional video...
Redesigned from the ground up, Final Cut Pro combines revolutionary video editing with a powerful media organization and incredible performance to let you create at the speed of thought.... Read more
Pro Video Formats 2.3 - Updates for prof...
The Pro Video Formats package provides support for the following codecs that are used in professional video workflows: Apple ProRes RAW and ProRes RAW HQ Apple Intermediate Codec Avid DNxHD® / Avid... Read more
Apple Safari 17.1.2 - Apple's Web b...
Apple Safari is Apple's web browser that comes bundled with the most recent macOS. Safari is faster and more energy efficient than other browsers, so sites are more responsive and your notebook... Read more
LaunchBar 6.18.5 - Powerful file/URL/ema...
LaunchBar is an award-winning productivity utility that offers an amazingly intuitive and efficient way to search and access any kind of information stored on your computer or on the Web. It provides... Read more
Affinity Designer 2.3.0 - Vector graphic...
Affinity Designer is an incredibly accurate vector illustrator that feels fast and at home in the hands of creative professionals. It intuitively combines rock solid and crisp vector art with... Read more
Affinity Photo 2.3.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more

Latest Forum Discussions

See All

New ‘Sonic Dream Team’ Performance Analy...
Sonic Dream Team (), the brand new Apple Arcade exclusive 3D Sonic action-platformer releases tomorrow. Read my in-depth interview with SEGA HARDLight here covering the game, future plans, potential 120fps, playable characters, the narrative, and... | Read more »
New ‘Zenless Zone Zero’ Trailer Showcase...
I missed this late last week, but HoYoverse released another playable character trailer for the upcoming urban fantasy action RPG Zenless Zone Zero. We’ve had a few trailers so far for playable characters, but the newest one focuses on Nicole... | Read more »
Get your heart pounding quicker as Autom...
When it comes to mobile battle royales, it wouldn’t be disrespectful to say the first ones to pop to mind would be PUBG Mobile, but there is one that perhaps doesn’t get the worldwide recognition it should and that's Garena’s Free Fire. Now,... | Read more »
‘Disney Dreamlight Valley Arcade Edition...
Disney Dreamlight Valley Arcade Edition () launches beginning Tuesday worldwide on Apple Arcade bringing a new version of the game without any passes or microtransactions. While that itself is a huge bonus for Apple Arcade, the fact that Disney... | Read more »
Adventure Game ‘Urban Legend Hunters 2:...
Over the weekend, publisher Playism announced a localization of the Toii Games-developed adventure game Urban Legend Hunters 2: Double for iOS, Android, and Steam. Urban Legend Hunters 2: Double is available on mobile already without English and... | Read more »
‘Stardew Valley’ Creator Has Made a Ton...
Stardew Valley ($4.99) game creator Eric Barone (ConcernedApe) has been posting about the upcoming major Stardew Valley 1.6 update on Twitter with reveals for new features, content, and more. Over the weekend, Eric Tweeted that a ton of progress... | Read more »
Pour One Out for Black Friday – The Touc...
After taking Thanksgiving week off we’re back with another action-packed episode of The TouchArcade Show! Well, maybe not quite action-packed, but certainly discussion-packed! The topics might sound familiar to you: The new Steam Deck OLED, the... | Read more »
TouchArcade Game of the Week: ‘Hitman: B...
Nowadays, with where I’m at in my life with a family and plenty of responsibilities outside of gaming, I kind of appreciate the smaller-scale mobile games a bit more since more of my “serious" gaming is now done on a Steam Deck or Nintendo Switch.... | Read more »
SwitchArcade Round-Up: ‘Batman: Arkham T...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 1st, 2023. We’ve got a lot of big games hitting today, new DLC For Samba de Amigo, and this is probably going to be the last day this year with so many heavy hitters. I... | Read more »
Steam Deck Weekly: Tales of Arise Beyond...
Last week, there was a ton of Steam Deck coverage over here focused on the Steam Deck OLED. | Read more »

Price Scanner via MacPrices.net

Apple has Certified Refurbished iPhone 12 Pro...
Apple has unlocked Certified Refurbished iPhone 12 Pro models in stock starting at $589 and ranging up to $350 off original MSRP. Apple includes a standard one-year warranty and new outer shell with... Read more
Holiday Sale: Take $50 off every 10th-generat...
Amazon has Apple’s 10th-generation iPads on sale for $50 off MSRP, starting at $399, as part of their Holiday Sale. Their discount applies to all models and all colors. With the discount, Amazon’s... Read more
The latest Mac mini Holiday sales, get one to...
Apple retailers are offering Apple’s M2 Mac minis for $100 off MSRP as part of their Holiday sales. Prices start at only $499. Here are the lowest prices available: (1): Amazon has Apple’s M2-powered... Read more
Save $300 on a 24-inch iMac with these Certif...
With the recent introduction of new M3-powered 24″ iMacs, Apple dropped prices on clearance M1 iMacs in their Certified Refurbished store. Models are available starting at $1049 and range up to $300... Read more
Apple M1-powered iPad Airs are back on Holida...
Amazon has 10.9″ M1 WiFi iPad Airs back on Holiday sale for $100 off Apple’s MSRP, with prices starting at $499. Each includes free shipping. Their prices are the lowest available among the Apple... Read more
Sunday Sale: Apple 14-inch M3 MacBook Pro on...
B&H Photo has new 14″ M3 MacBook Pros, in Space Gray, on Holiday sale for $150 off MSRP, only $1449. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8-Core M3 MacBook Pro (8GB... Read more
Blue 10th-generation Apple iPad on Holiday sa...
Amazon has Apple’s 10th-generation WiFi iPad (in Blue) on Holiday sale for $349 including free shipping. Their discount applies to WiFi models only and includes a $50 instant discount + $50 clippable... Read more
All Apple Pencils are on Holiday sale for $79...
Amazon has all Apple Pencils on Holiday sale this weekend for $79, ranging up to 39% off MSRP for some models. Shipping is free: – Apple Pencil 1: $79 $20 off MSRP (20%) – Apple Pencil 2: $79 $50 off... Read more
Deal Alert! Apple Smart Folio Keyboard for iP...
Apple iPad Smart Keyboard Folio prices are on Holiday sale for only $79 at Amazon, or 50% off MSRP: – iPad Smart Folio Keyboard for iPad (7th-9th gen)/iPad Air (3rd gen): $79 $79 (50%) off MSRP This... Read more
Apple Watch Series 9 models are now on Holida...
Walmart has Apple Watch Series 9 models now on Holiday sale for $70 off MSRP on their online store. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... 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
Senior Product Manager - *Apple* - DISH Net...
…Responsibilities** We are seeking an ambitious, data-driven thinker to assist the Apple Product Development team as our Wireless Product division continues to grow Read more
Senior Product Manager - *Apple* - DISH Net...
…Responsibilities** We are seeking an ambitious, data-driven thinker to assist the Apple Product Development team as our Wireless Product division continues to grow Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.