Important: The information in this document is Not Recommended and should not be used for new development.
Current information on this Reference Library topic can be found here:
|
Although Virtual Memory (VM) has been available on the Mac OS since the release of System 7, its use has become more widespread in recent years. This is because of a combination of factors, including
Because VM is so widely used, it's critically important to make sure that your software is compatible with it. This Technote, which is divided into two parts, provides you with
All Mac OS programmers should read this Note. For application developers, this Note supplants and extends the information presented in Technote ME 09, "Coping With VM and Memory Mappings". Device driver writers should continue to refer to Technote ME 09.
As far as VM is concerned, the important distinction is between application code and device drivers. Updated: [Apr 02 1998]VM Compatibility ChecklistIn general, programs should be able to ignore VM and operate successfully. Specific problems may occur under the following conditions:
If your program does none of the operations listed, it should already be VM-compatible. If you want to improve your code's performance under VM, you should read the section Ways to Improve Performance in this Note. On the other hand, if your program does any of these operations, you'll want to read the next section, Programming Implications, which explains how to make your program VM-compatible. That section is also a good place to start if your program is incompatible with VM and all you want to do is fix it as quickly as possible. For an overview of VM theory, terminology, and implementation under System 7, you'll want to go to the How VM Works section. Programming ImplicationsIf your program does any of the things listed above, you should read the next few sections for information on how to execute safely under VM. Direct SCSI Manager or ATA Manager CallsSome applications, such as scanning programs or hard disk formatters, need to call the SCSI or ATA Managers directly. These applications must take special care when VM is enabled. For example, if such an application was to grab exclusive access to the SCSI bus and then take a page fault, VM would be unable to swap in the new page, making the page fault fatal. The solution is simple: if your program calls the SCSI or ATA Manager directly, you must make sure that any code executed or data accessed while paging is unsafe is held in memory. If you're writing an application, meeting that requirement can be tricky because it's hard to hold just part of an application's code in memory. For this reason, your application might want to implement this functionality as a bundled device driver or other code resource. This separation makes it easy for the application to ensure that the code running while paging is unsafe is held. Hardware Interrupt Handlers and Physical AddressingSome applications need to drive hardware devices
directly; for example, a data capture program that talks to
a dedicated PCI card. Such applications typically install
hardware interrupt handlers (either using
The basic rules for device driver writers are:
For further explanation, see the above references. As an aside, in future versions of the Mac OS it is likely that these functions will be privileged and inaccessible from applications. If your application does this sort of thing, it is most probably a good idea to prepare for the future by factoring your code into an application and a bundled device driver. AppleTalk Socket ListenersAppleTalk socket listeners are also not VM-safe. An AppleTalk socket listener on a slow computer using LocalTalk networking has very difficult real-time goals. Deferring this while waiting for a page fault would cause serious packet loss. So, for performance reasons, VM does not defer socket listeners until paging is safe. Socket listeners must be written so that they don't cause a page fault. The way to make your socket listener VM-safe is to ensure
that all code that can be called by the socket listener, and
the data it accesses, is either held in memory or deferred
using Calling Non-Interrupt-Safe RoutinesIf your code calls routines that are not interrupt-safe while handling a non-deferred hardware interrupt (or, for that matter, any time paging is unsafe), you may encounter a problem running on Mac OS 7.6 and future releases of the Mac OS that use System 7-style VM. In older Mac OS implementations, large parts of system software were in the system heap simply so that they could be shared between applications. These parts caused the system heap to grow in size. Because the system heap is always held, these parts were resident, even though they didn't need to be. This reduced the number of physical pages of RAM that VM had available to use as a cache for its various logical address ranges. This reduction made the system slower. Under versions of the Mac OS that use System 7-style VM, these system parts are being moved out of the system heap and into file-mapped CFM containers. This makes them available for paging, and increases the number of physical pages available to VM. In general, this helps system performance. Such parts of the system are only made pageable if none of their routines can be called at interrupt time. Otherwise, a non-deferred hardware interrupt might call these routines and cause a fatal page fault. However, some programs call non-interrupt-safe routines when paging isn't safe. This works under earlier versions of Mac OS because the code for these routines was in the system heap, and hence resident. Such software has problems under newer versions of Mac OS when VM is turned on because these routines are no longer held. If your software was previously compatible with VM and broke under Mac OS 7.6, you should check to be sure that you are not calling non-interrupt-safe routines when paging is unsafe. Switching Stacks at Interrupt TimeIf your code switches stacks at interrupt time (usually this is done to guarantee a minimum amount of available stack space), it must ensure that the stack is held. This is true even if your code runs at times when paging is normally safe, such as deferred task time. For a detailed explanation of why this is necessary, see User vs Supervisor Mode. Ways to Improve PerformanceThis section describes a number of things that you can do to make your application work better under VM. Grouping Commonly Used Code and DataPerhaps the best thing you can do to make your application work better under VM is to analyze its working set. The working set of a program is the set of memory pages that the program accesses most often. The smaller you make your working set, the better your program will run under VM. If the working set of all the active processes exceeds the amount of physical memory available for paging, the system begins to take an excessive number of page faults. This state is known as thrashing. Under System 7 there are no good tools for analyzing your application's working set automatically. However, you can do some things to empirically adjust your working set. One good thing is to 'segment' your CFM-based applications sensibly. Most development environments provide a mechanism for CFM-based applications to sort routines in their PEF container according to a "group" that is set using compiler directives. This is analogous to the classic runtime segmentation model. You can use these directives to group rarely used functions together and away from the commonly used functions. This helps keep rarely used code paged out, which reduces your working set. Sensible Memory ManagementAnother good place to look when analyzing your working set is your memory management system. Some memory management systems are VM-friendly, and some are not. For example, if your memory management system looks at every block in the heap when a block is freed, it has pathologically bad VM performance. On PowerPC machines, the system Memory Manager (also known as the Modern Memory Manager) has been optimized to be as VM-friendly as possible. It's important that you be compatible with the Modern Memory Manager, for this and other reasons. On 680x0 machines, the Memory Manager has some behaviors that cause excessive page faults under VM. Unfortunately, there's not a lot you can do about the system Memory Manager on these machines other than avoid using it. If you're using your own memory management scheme (for C++ objects, for example), you should look at its implementation to determine whether it's VM-friendly. A VM-friendly memory manager attempts to reduce the number of times it looks at bytes located in different pages, and thus minimizes the program's working set. You may be able to switch memory managers and get VM performance benefits. Another way to avoid thrashing is to minimize your use of Process Manager temporary memory. While it may appear that this memory is free for application use, under VM this memory has often been paged out, and therefore is not 'real'. So if you look at temporary memory and see that there's a huge amount of free space, do not allocate and use it all. This will likely cause the system to thrash. Only allocate as much temporary memory as you actually need. Paging Control APIMac OS 8.1 introduced a new API for the Virtual Memory Manager, the VM paging control API. Your application can use this API to advise VM on how it is using memory. This allows VM to optimize its paging behavior to improve application, and overall system, performance. You can read more about the VM paging control API in the Mac OS 8.1 Technote. Judicious Use of
|
Bus Error accessing 00123456 at PC 4080BB8C 4080BB8C MOVE.W $0010(A0),D0 |
MacsBug reports that the instruction that caused the bus error is at address $4080BB8C, but this report is not always correct. The dynamic recompiling (DR) emulator found on recent PowerPC computers can cause an imprecise PC address to be reported by MacsBug. However, one piece of information that you can rely on is the address that was being accessed and caused the page fault. In the above example, this is address $00123456. You can find out information about this address using the following MacsBug command:
dm $00123456 wh $00123456 |
If this command indicates that the address is in valid memory (i.e., either the primary address range or one of the file mapped address ranges), the access should have succeeded. The only reason for this bus error is a fatal page fault.
If the wh
command reports that the address is "not in
RAM or ROM", chances are that this bus error is just a
normal bus error, ie one caused by dereferencing a bogus
pointer.
Another common form of fatal page fault is reported as:
Bus Error at 4080BB8C while writing long work (data = 0000009B) to 00123456 4080BB8C LINK A6,#$FFCC |
The LINK
instruction is touching the stack, which may
have been paged out. You can check this by looking at the
value of SP
, which has most probably just crossed a page
(4KB) boundary. You can confirm that this is a fatal page
fault by looking to see whether paging is safe and by
checking the target address 00123456 using wh
and dm
.
A third form of fatal page fault is reported as:
Bus Error accessing 00123456 at PC 00123456 Unable to access that address |
In this case, the actual instruction fetch has caused a
bus error. You can confirm that this is a fatal page fault
by looking to see whether paging is safe and by checking the
target address using wh
.
Note
These last two examples may also be complicated by the DR emulator. Remember that you can trust the "accessing" address reported but not the PC.
Not all fatal page faults are double page faults. For example, if you take a page fault while the device driver that's controlled the backing store is busy, the page fault is fatal, even though it isn't a double page fault. See the "Preventing Fatal Page Faults" section for a description of the various reasons why a page fault might be fatal.
Determining whether a fatal page fault is a double page fault is reasonably tricky. One good indicator is whether you're in user or supervisor mode. You can tell this by looking at the S bit in the SR display in MacsBug. If this is a capital "S", you are in supervisor mode; if this is a lower case "s" you are in user mode.
You can't get double page faults from user mode. If you
get a fatal page fault and you're in user mode, you know
there was some other cause. One good place to start
debugging this is to use MacsBug's drvr
command to see if
any of the paging device drivers are busy. Remember that you
will suffer a
resource constraint fatal page fault if you take a page
fault while the paging device driver is busy.
Unfortunately, taking a fatal page fault in supervisor
mode still isn't a guarantee that it was a double page fault.
The only certain way to determine whether it was a double
page fault is to dump memory starting at the
ISP and
look for a
bus
error exception stack frame on the interrupt stack. Bus
error exception stack frames are relatively easy to
recognize because they contain a special word that denotes
the frame type. For a bus errror on a 68040, this value is
$7008
. For a bus error on the 68020, 68030, and the emulated
680x0 processor of a PowerPC-based computer, this value is
$B008
.
Note
Under rare circumstances is is possible for the
68020 and 68030 processors to generate a frame type
of $A008
in response to a bus error.
You can also use other fields in the exception frame to confirm that you have found the correct frame. All of the exception stack frames we're interested in share a common format, as shown below:
SP + $00 - Status Register SP + $02 - High Word of PC SP + $04 - Low Word of PC SP + $06 - Frame Format, Vector Offset (eg $B008) ... and so on |
Once you have found the frame type word on the stack, you
can look back six bytes to find the value of the Status
Register (SR) immediately prior to the bus error. Common
values for SR are $0Ixx
or $2ixx
, where I
is the interrupt
level (0 to 7) and xx
is "don't care". You can look at this
saved SR, see whether its value is sensible, and use that to
confirm whether you have found the bus error exception
frame.
Once you find the bus error exception stack frame, you can use the information in M68000 Family Programmer's Reference Manual to examine the frame and find more clues about the cause.
Important
On PowerPC-based Mac OS computers, the value for the PC stored in the bus error exception frame is always incorrect. The interaction between the PowerPC processor and the Virtual Memory Manager causes this PC address to always point to an address within VM itself.
Once you know you've taken a double page fault, you can find out information about the first page fault by looking at the bus error exception stack frame. The format of this frame is described in the M68000 Family Programmer's Reference Manual. For example, a bus error exception stack frame on a 68040 looks like:
SP + $00 - Status Register SP + $02 - High Word of PC SP + $04 - Low Word of PC SP + $06 - Frame Format, Vector Offset (contains $7008) SP + $08 - High Word of Effective Address SP + $0A - Low Word of Effective Address ... and so on |
Once you find the start of the frame, you can dump the
long at offset $02
from the start of the frame to determine,
subject to the restrictions described in the previous
section, the address of the instruction that took the
original bus error.
You can also dump the long at offset $08
from the start
of the frame to determine the address that the code was
trying to access when it bus errored.
Finally, you can dump the word at offset $00
from the
start of the frame to determine the value of the SR when the
bus error occurred. This can be useful to determine the type
of code that was running at the time. For example, interrupt
level 4 is used by the Macintosh Serial Communications
Controller (SCC) and it's likely that a fatal page fault
that happens when the processor is at interrupt level 4 is
somehow related to serial code.
When VM is enabled, it maps CFM containers into file mapping space. These file-mapped address ranges are read-only. Any attempt to write to your own code will cause you to drop into MacsBug with the message:
PowerPC read-only memory exception at 02314BB8 main+00018 |
For example, the following snippet of PowerPC code runs just fine when VM is disabled, but dies under VM:
static void Wibble(void) { } void main(void) { char x; x = **((char **) Wibble); **((char **) Wibble) = x; } |
Note
The extra dereference in the above snippet is required because procedure pointers on a CFM architecture are actually pointers to transition vectors.
System 7.5.5 introduced two new system errors related to virtual memory. Both errors are completely fatal for the system, but if you encounter one while debugging, it is useful to know their cause.
dsVMDeferredFuncTableFull
(112)
This error is generated when the deferred user function table is full. A common way of getting this error is to defer an operation that has already been deferred. Another possibility is to install a Time Manager task which has already been installed.
If you get this error, you can use the "UsersFns"
'dcmd'
(part of MacsBug 6.5.4a1 and later)
to dump out the list of deferred user functions. If you
see one entry repeated many times, you should start
looking for problems in how you use the system service
corresponding to that entry.
Specifically, if you find that there the list of
deferred user functions is full of Time Manager user
functions, check that you are calling
RmvTime
for each time you call
InsTime
(or InsXTime
). While
imbalanced calls to the Time Manager work when VM is
disabled, they are not correct and they will cause this
system error when VM is enabled.
dsVMBadBackingStore
(113)
This error is generated when VM gets an error while reading or writing a backing store. Typically, this indicates a genuine hardware problem. It could be a useful debugging aid if you are developing a paging device driver.
See Technote 1069 - "System 7.5.5" for more details on all of the changes that occurred in the System 7.5.5 release of VM.
'dcmd'
sMacsBug
contains two 'dcmd'
s that can be useful when
debugging VM problems. The first, VMDump
, dumps the
current state of VM on a page-by-page basis. The second,
UserFns
, displays a the current list of deferred user
functions.
For information about these 'dcmd'
s, type
the following commands in MacsBug:
help vmdump help userfns |
Virtual memory is not rocket science. While its implementation on the Mac OS is complicated by the constraints of the original design, it's not incomprehensible. Understanding how VM works will help you know why the rules are important, recognize when you need to apply them, and debug problems when they arise.
Inside Macintosh: Memory provides a description of the VM API calls.
Technote ME 09 - "Coping With VM and Memory Mappings" Although the information in ME 09 is supplanted by this Technote for application code, ME 09 still contains useful information for traditional device driver (DRVR) writers.
Designing PCI Cards and Drivers for Power Macintosh Computers describes the VM rules that apply to native drivers ('ndrv's)
M68000 Family Programmer's Reference Manual gives detailed information about the 680x0 exception architecture, including the exception vector table and the bus error exception frame.
Inside Macintosh: PowerPC System Software gives a description of the differences between an emulated 680x0 processor and a real one, including the bus error exception frame format of the emulated variety.
Technote 1063 - "Inside Macintosh: Processes: Time Manager Addenda" describes how to mark your Time Manager task "VM immune".
Technote 1069 - "System 7.5.5" describes how to mark your Time Manager task "VM immune".
Technote NW 13 - "AppleTalk: The Rest of the Story" describes how to mark your device "VM immune".
Technote ME 06 - "_StripAddress: The Untold Story"
Modern Operating Systems, by Andrew S. Tanenbaum, Prentice-Hall, 1992, ISBN 0-13-588187-0 gives a good introduction to virtual memory in general.
1.0 |
03/97 |
initial version |
1.1 |
3/98 |
updated to reference the Mac OS 8.1 VM paging control APIs, and to describe the requirement that private interrupt-time stacks must be held resident. |
Acrobat version of this Note (84K) |