Important: The information in this document is obsolete and should not be used for new development.
Using the Virtual Memory Manager
The routines described in this section allow drivers and applications with critical timing needs to intervene in the otherwise automatic workings of the Virtual Memory Manager's paging mechanism.
If necessary, your software can request that a range of memory be held in physical memory. Holding means that the specified memory range cannot be paged out to disk, although it might be moved within physical RAM. As a result, no page faults can result from reading or writing memory addresses of pages that are held in memory.
- Note
- The vast majority of applications do not need to use these
routines. They are used primarily by drivers, debuggers, and other interrupt-servicing code.Similarly, a page or range of pages can be locked in physical memory. Locking means that the specified memory cannot be paged out to disk and that the memory cannot change its real (physical) RAM location. You can also request that a range of pages be locked in a contiguous range of physical memory, although contiguity is not guaranteed. The need to lock pages in a contiguous area of memory arises primarily when external hardware transfers data directly into physical RAM. In this case, locking might be useful for keeping a contiguous range of memory stationary during operations of an external CPU (on a NuBus card, for example) that cannot support a DMA action.
Most applications do not need to hold or lock pages in physical RAM. The Virtual Memory Manager usually works quickly enough that your application is not affected by any delay that might result from paging. Device drivers or sound and animation applications with critical timing requirements usually need only to hold memory, not lock it. Here are some general rules regarding when to hold or lock memory:
The last directive is especially important. Your application is responsible for undoing the effects of locking or holding ranges of memory. In particular, the Virtual Memory Manager does not automatically unlock pages that have been locked. If you do not undo these effects in a timely fashion, you are likely to degrade performance. In the worst case, you could cause the system to run out of physical memory.
- Avoid executing tasks that could cause page faults at interrupt time. The less work done at interrupt time, the better for all applications running.
- You cannot hold or lock memory (or call any Memory Manager routines that move or purge memory) at interrupt time.
- Don't lock or hold everything in RAM. Sometimes you do need to hold or lock pages in RAM, but if you are in doubt, then probably you need to do neither.
- Your application must explicitly release or unlock whatever it held or locked. If for some reason an area of RAM is held and locked, or held twice, then it must be released and unlocked, or released twice.
Obtaining Information About Virtual Memory
You should always determine whether virtual memory is available before attempting to use any Virtual Memory Manager routines. To do this, pass theGestalt
function thegestaltVMAttr
selector. TheGestalt
function's response indicates the version of virtual memory, if any, installed. If bit 0 of the response is set to 1, then the system software version 7.0 implementation of virtual memory is installed.
You can also use the
- Note
- Sometimes you don't need to check whether virtual memory is actually available before calling some Virtual Memory Manager routines. For example, you might need to call the
GetPhysical
function even if virtual memory is not enabled. Instead of callingGestalt
to see whether virtual memory is available, you should simply test whether the appropriate trap is available. In the case of theGetPhysical
function, you should check that the_MemoryDispatchA0Result
trap is available.Gestalt
function to obtain information about the memory configuration of the system, in particular, information about the amount of physical memory installed in a computer, the amount of logical memory available in a computer, the version of virtual memory installed (if any), and the size of a logical page. By obtaining this information fromGestalt
, you can help insulate your applications or drivers from possible future changes in the details of the virtual memory implementation.Holding and Releasing Memory
You can use theHoldMemory
function to make a portion of the address space resident in physical memory and ineligible for paging. This function is intended primarily for use by drivers that access user data buffers at interrupt level, whether transferring data to or from them. CallingHoldMemory
on the appropriate memory ranges thus prevents them from causing page faults at interrupt level and effectively prevents them from generating fatal double page faults. The contents of the specified range of virtual addresses can move in physical memory, but they are guaranteed always to be in physical memory when accessed.
The following sample code instructs the Virtual Memory Manager to hold in RAM an 8192-byte range of memory starting at address $32500:
- Note
- If you use the device-level
_Read
and_Write
functions when doing data transfers, the Virtual Memory Manager automatically ensures that the data buffers and parameter blocks are held before the transfer
of data.
myAddress := $32500; myLength := 8192; myErr := HoldMemory(myAddress, myLength);Note that whole pages of the virtual address space are held, regardless of the starting address and length parameters you supply. If the starting address parameter supplied to theHoldMemory
function is not on a page boundary, then it is rounded down to the nearest page boundary. Similarly, if the specified range does not end on a page boundary, the length parameter is rounded up so that one or more whole pages are held. This rounding might result in the holding of several pages of physical memory, even if the specified range is less than a page in length.To release memory held as a result of a call to
HoldMemory
, you must use theUnholdMemory
function, which simply reverses the effects of theHoldMemory
function. For example, the page or pages held in memory in the previous example can be released as follows:
myErr := UnholdMemory(myAddress, myLength);Like holding, releasing applies to whole pages of the virtual address space. Similar rounding of the address and length parameters is performed, as required, to make the range begin and end on page boundaries.
- Note
- In current versions of system software, the system heap is always held in memory and is never paged out.
Locking and Unlocking Memory
You can use theLockMemory
function to make a portion of the address space immovable in physical memory and ineligible for paging. The Operating System may move the contents of the specified range of logical addresses to a more convenient location in physical memory during the locking operation, but on completion, the contents of the specified range of logical addresses are resident and do not move in physical memory.Locking a range of memory is a more drastic measure than just holding it. Locking not only forces the range to be held resident in RAM but also prevents its logical address from moving with respect to its physical address. The
LockMemory
function is used by drivers and other code when hardware other than the Macintosh CPU is transferring data to or from user buffers, such as any NuBus master peripheral card or DMA hardware. This function prevents both paging and physical relocation of a specified memory area and allows the physical addresses of a memory area to be exported to the non-CPU hardware. Typically, you would use this service for the duration of a single
I/O request. However, you could use this service to lock data structures that are permanently shared between a driver (or other code) and a NuBus master.
The main reason to disable movement of pages in physical memory is to allow translation of virtual memory addresses to physical addresses. This translation is needed by bus masters, which must write to memory in the physical address space. To avoid stale data, the memory locked in RAM is marked as noncacheable in the MMU
- Note
- Don't confuse locking address ranges in RAM (using
LockMemory
) with locking a handle (usingHLock
). A locked handle can still be
paged out.
page tables.You can lock a range of memory in a contiguous range of physical memory by calling the
LockMemoryContiguous
function. This function can be used by driver and NuBus master or driver and DMA hardware combinations when a non-CPU device accessing memory cannot handle physically discontiguous data transfers. You can also use this service when the transfer of physically discontiguous data would degrade performance. However, the call toLockMemoryContiguous
may be expensive, because sometimes entire pages must be copied to make a range contiguous.
To unlock a range of previously locked pages, use the
- Note
- It might not be possible to make a range physically contiguous if any of the pages in the range are already locked. Because a call to
LockMemoryContiguous
is not guaranteed to return the desired results, you must include in your code an alternate method for locking the necessary ranges of memory. In general, you should avoid callingLockMemoryContiguous
if at all possible. If you must call it, do so as early as possible--preferably at system startup time--to increase the likelihood of finding enough contiguous memory.UnlockMemory
function. This function reverses the effects ofLockMemory
orLockMemoryContiguous
. Unlocked pages are marked as cacheable.Locking, contiguous locking, and unlocking operations are applied to ranges of the logical address space. If necessary to force the ranges onto page boundaries, the Virtual Memory Manager performs rounding of addresses and sizes, as described in "Holding and Releasing Memory" on page 3-14.
Mapping Logical to Physical Addresses
To obtain information about page mapping between logical and physical addresses, use theGetPhysical
function, which translates logical addresses into their corresponding physical addresses. It provides drivers and other software with the actual physical memory addresses of a specified logical address range. Non-CPU devices need this information to access memory mapped by the CPU.The
GetPhysical
function allows you to obtain the physical addresses that correspond to any logically addressable range of main memory. To specify the logical address
range to be translated, you use a memory-block record, defined by theMemoryBlock
data type.
TYPE MemoryBlock = RECORD address: Ptr; {start of block} count: LongInt; {size of block} END;A memory-block record identifies a single contiguous block of memory by specifying the first byte in the block and the length of the block.
A single logical address range sometimes corresponds to more than one range of physical addresses. As a result,
- Note
- Don't confuse the blocks of memory defined by the
MemoryBlock
data type with memory blocks as manipulated by the Memory Manager. The portion of the logical address space to be translated byGetPhysical
can overlap several Memory Manager memory blocks or be just a part of one. Typically, however, that range coincides with the contents of a single Memory Manager block.GetPhysical
needs to pass back to your application an array of memory-block records. You pass a logical address range toGetPhysical
, and it returns an array of physical address ranges. This operation requires the use of a logical-to-physical translation table, defined by theLogicalToPhysicalTable
data type.
TYPE LogicalToPhysicalTable = RECORD logical: MemoryBlock; {a logical block} physical: ARRAY[0..defaultPhysicalEntryCount-1] OF MemoryBlock; {equivalent physical blocks} END;To callGetPhysical
, you need to pass a translation table whoselogical
field specifies the logical address range you want to translate. You also need to specify how many contiguous physical address ranges you want returned. In this way, you can adjust the number of elements in the array to suit your own needs. By default, a translation table contains enough space for eight physical memory blocks.
CONST defaultPhysicalEntryCount = 8;If the variablemyTable
is of type LogicalToPhysicalTable andmyCount
is of typeLongInt
, you can callGetPhysical
as follows:
myCount := (SizeOf(myTable) DIV SizeOf(MemoryBlock)) - 1; myErr := GetPhysical(myTable, myCount);The algorithm used here to calculate the number of physical entries returned (myCount
) allows you to change the size (and hence the type) of themyTable
variable to include more or fewer memory blocks. The default size of the translation table is sufficient for most purposes. Before you do the translation, you can determine how many physical blocks you need to accommodate the entire logical address space specified in the table'slogical
parameter. To determine this, you pass a variable whose initial value is 0:
myCount := 0; {get number of blocks needed for given range} myErr := GetPhysical(myTable, myCount);If the value of its second parameter is 0,GetPhysical
returns in that parameter the total number of physical blocks that would be required to translate the entire logical address range. In this case, both thelogical
andphysical
fields of the translation table are unchanged.If the value of its second parameter is not 0,
GetPhysical
returns in thephysical
field of the translation table an array specifying the physical blocks that correspond to the logical address specified in thelogical
field. TheGetPhysical
function returns in its second parameter the number of entries in that array (which may be fewer than were asked for). If the translation table was not large enough to contain all the physical blocks corresponding to the logical block,GetPhysical
updates the fields of thelogical
memory block to reflect the remaining number of bytes in the logical range left to translate (count
field) and the next address in the logical address range to translate (start
field).
Recall that you sometimes need to call
- Note
- You must lock (using
LockMemory
) the address range passed toGetPhysical
to guarantee that the translation data returned are accurate (that is, that the logical pages do not move around in physical memory and that paging activity has not invalidated the translation data). An error is returned if you callGetPhysical
on an address range that is not locked.GetPhysical
even if virtual memory is not available. (See "The Physical Address Space" on page 3-9 for details.) In general, ifGetPhysical
is available in the operating environment, then you should call it
any time your software exports addresses to a NuBus expansion card that can read or write physical RAM directly. Listing 3-1 defines a general algorithm for implementing driver calls to a generic NuBus master card. To maximize compatibility with virtual memory, make sure that your hardware and device drivers support this method of issuing driver calls.Listing 3-1 Translating logical to physical addresses
PROGRAM GetPhysicalUsage; USES Types, Traps, Memory, Utilities; CONST kTestPtrSize = $100000; VAR myPtr: Ptr; myPtrSize: LongInt; hasGetPhysical: Boolean; {does this machine have GetPhysical?} lockOK: Boolean; {was the block successfully locked?} myErr: OSErr; myTable: LogicalToPhysicalTable; myCount: LongInt; index: Integer; PROCEDURE SendDMACmd (addr: Ptr; count: LongInt); BEGIN {This is where you would probably make a driver call } { to initiate DMA from a NuBus master or similar hardware.} END; BEGIN myPtrSize := kTestPtrSize; myPtr := NewPtr(myPtrSize); IF myPtr <> NIL THEN BEGIN hasGetPhysical := TrapAvailable(_MemoryDispatch); IF hasGetPhysical THEN BEGIN myErr := LockMemory(myPtr, myPtrSize); lockOK := (myErr = noErr); IF lockOK THEN BEGIN myTable.logical.address := myPtr; myTable.logical.count := myPtrSize; myErr := noErr; WHILE (myErr = noErr) & (myTable.logical.count <> 0) DO BEGIN myCount := SizeOf(myTable) DIV SizeOf(MemoryBlock) - 1; myErr := GetPhysical(myTable, myCount); IF myErr = noErr THEN FOR index := 0 TO (myCount - 1) DO WITH myTable DO SendDMACmd(physical[index].address, physical[index].count) ELSE BEGIN {Handle GetPhysical error indicated by myErr.} {Loop will terminate unless myErr is reset to noErr.} END; END; {WHILE} {Always unlock a range you locked; ignore any error here.} myErr := UnlockMemory(myPtr, myPtrSize); END ELSE {not lockOK} BEGIN {handle LockMemory error indicated by myErr} END; END ELSE {GetPhysical not available} SendDMACmd(myPtr, myPtrSize); END; {IF myPtr} END.If theGetPhysical
function is not available, the program defined in Listing 3-1 simply calls your routine to send a DMA command to the NuBus hardware. In that case, no address translation is necessary. If, however,GetPhysical
is available, you need to lock the logical address range whose physical addresses you want to get. If you successfully lock the range, you can callGetPhysical
as illustrated earlier. Be sure to unlock the range you previously locked before exiting the program.
- WARNING
- Some Macintosh computers contain the
_MemoryDispatch
trap in ROM, even though they do not contain an MMU coprocessor. In this case, the system software patches the_MemoryDispatch
trap to make it appear unimplemented. However, software that executes before system patches are installed cannot use this as a test of whether to callGetPhysical
or not. If your code is executed before the installation of system patches, you should use theGestalt
function to test directly for the existence of an MMU coprocessor.
Deferring User Interrupt Handling
During the time that the Macintosh is handling a page fault, it is critical that no other page faults occur. Because the system performs no other work while it is handling a page fault, only code that runs as a result of an interrupt can generate a second page fault. For this reason, you must call theHoldMemory
function on buffers or code that are to be referenced by any interrupt service routine. You must call this function at noninterrupt level because theMemoryDispatch
calls may cause movement of logical memory or physical memory and possible I/O.The use of procedure pointers (variables of type
ProcPtr
) in specifying I/O completion routines, socket listeners, and so forth makes it impossible for drivers to know the exact location and size of all code or buffers that might be referenced when these routines are invoked. However, these routines must still be called only at a safe time, when paging is not currently in progress. Because the locations of all needed pages cannot be known, an alternate strategy is used to prevent a fatal double page fault.The
DeferUserFn
function is provided to allow interrupt service routines to defer, until a safe time, code that might cause page faults. This function determines whether the call can be made immediately and, if it is safe, makes the call. If a page fault is in progress, the address of the service routine and its parameter are saved, and the routine is deferred until page faults are again permitted.