Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Memory /
Chapter 3 - Virtual Memory Manager


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.

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.
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.

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.

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 the Gestalt function the gestaltVMAttr selector. The Gestalt 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.

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 calling Gestalt to see whether virtual memory is available, you should simply test whether the appropriate trap is available. In the case of the GetPhysical function, you should check that the _MemoryDispatchA0Result trap is available.
You can also use the 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 from Gestalt, 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 the HoldMemory 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. Calling HoldMemory 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.

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.
The following sample code instructs the Virtual Memory Manager to hold in RAM an 8192-byte range of memory starting at address $32500:

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 the HoldMemory 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 the UnholdMemory function, which simply reverses the effects of the HoldMemory 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 the LockMemory 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.

Note
Don't confuse locking address ranges in RAM (using LockMemory) with locking a handle (using HLock). A locked handle can still be
paged out.
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
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 to LockMemoryContiguous may be expensive, because sometimes entire pages must be copied to make a range contiguous.

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 calling LockMemoryContiguous 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.
To unlock a range of previously locked pages, use the UnlockMemory function. This function reverses the effects of LockMemory or LockMemoryContiguous. 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 the GetPhysical 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 the MemoryBlock 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.

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 by GetPhysical 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.
A single logical address range sometimes corresponds to more than one range of physical addresses. As a result, GetPhysical needs to pass back to your application an array of memory-block records. You pass a logical address range to GetPhysical, and it returns an array of physical address ranges. This operation requires the use of a logical-to-physical translation table, defined by the LogicalToPhysicalTable
data type.

TYPE LogicalToPhysicalTable =
RECORD
   logical:    MemoryBlock;         {a logical block}
   physical:   ARRAY[0..defaultPhysicalEntryCount-1] OF
                      MemoryBlock;  {equivalent physical blocks}
END;
To call GetPhysical, you need to pass a translation table whose logical 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 variable myTable is of type LogicalToPhysicalTable and myCount is of type LongInt, you can call GetPhysical 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 the myTable 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's logical 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 the logical and physical fields of the translation table are unchanged.

If the value of its second parameter is not 0, GetPhysical returns in the physical field of the translation table an array specifying the physical blocks that correspond to the logical address specified in the logical field. The GetPhysical 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 the logical 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).

Note
You must lock (using LockMemory) the address range passed to GetPhysical 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 call GetPhysical on an address range that is not locked.
Recall that you sometimes need to call GetPhysical even if virtual memory is not available. (See "The Physical Address Space" on page 3-9 for details.) In general, if GetPhysical 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 the GetPhysical 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 call GetPhysical 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 call GetPhysical or not. If your code is executed before the installation of system patches, you should use the Gestalt 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 the HoldMemory function on buffers or code that are to be referenced by any interrupt service routine. You must call this function at noninterrupt level because the MemoryDispatch 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.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996