home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
581a.lha
/
MemMan_v2.0
/
MemMan.doc.pp
/
MemMan.doc
Wrap
Text File
|
1991-12-05
|
22KB
|
452 lines
MemMan
Version 2.0
Low-Memory manager
Copyright (C) 1991 Bryan Ford
Warning: Don't try to use MemMan without fully reading this documentation!
Introduction
~~~~~~~~~~~~
MemMan is freeware, NOT public domain. See MemMan.asm for details on
distribution and use in your own programs. The MemMan distribution must
contain the following files, complete and unmodified:
MemMan.doc MemMan documentation (this file)
memman.library MemMan runtime library
MemMan.o Linkable version of MemMan
MemMan.fd Entrypoints for memman.library
MemMan.h Header for C programs using MemMan
MemMan.i Same, for assembly language
MemMan.asm Source to MemMan (A68k only)
MemManLib.asm Source to memman.library interface (A68k only)
Macros.i Macros you'll need for the above two files
LMKfile SAS/C makefile for everything
test Program to test memman.library
test.c Source for test program
MemMan is a short assembly language module which allows your
application to be called whenever there is a memory shortage during an
AllocMem() call. This allows your application to free up any memory it can
do without so another program can use the memory. Resident libraries and
devices use a similar system to automatically unload them if the system
runs out of memory.
However, MemMan provides two very important features that the standard
expunging system doesn't. First, MemMan doesn't require an application to
free everything it possibly can all at once. The application can free
a little bit of memory, then MemMan will try the allocation again, and if
the allocation still fails, the application can free more memory, and so
on. Second, to go along with this, the application can tell MemMan how
important a given piece of memory is. This allows the application to free
some types of buffers or caches "at the drop of a hat" while keeping other
more valuable items until the last minute.
To use MemMan, you will either need to link the "MemMan.o" file into
your program, or use the runtime "memman.library". Either way, MemMan
should work with either SAS/C 5.10 or Manx. In program modules that use
MemMan, you will need to include the header file "MemMan.h" or MemMan.i" as
appropriate. If you're using the runtime library from a C program, you
must #define the symbol MM_RUNLIB before including MemMan.h.
At the very beginning of your program you must call the MMInit()
function to initialize MemMan. If you're using the runtime library, you
don't need to do this, but you need to open memman.library and put the
library base in MMBase. At the very end of your program, if you're using
the linked library, you must call MMFinish() to shut it down. (If you're
using the memman.library, you just need to close it.) During your program,
you use the MMAddNode() and MMRemNode() functions to tell MemMan about
things that can be freed during a memory crunch. All of these functions
are described in detail later in the document.
If you would like to make your own modifications to MemMan, you will
probably need the A68k assembler by Charlie Gibbs. It will only work with
a version LATER than version 2.71. This is because MemMan makes use of
some enhancements to A68k which I have made just recently, and as of this
writing, the new version has not been released yet. Also, MemMan.asm
references the include files "memman.i" and "macros.i" in a subdirectory
called "bry". You will either need to create this subdirectory on your
system and put these files into that directory, or change MemMan.asm to
reference them where you want them. ("macros.i" is full of strange and
interesting macros that I commonly use.)
I'd like to thank Michael Sinz of Commodore-Amiga for his valuable
suggestions for MemMan - it is now a whole lot more solid than it was when
I sent it to him originally. (In fact, it's been basically rewritten since
then...) Also, thanks to David Le Blanc (david@walking.pub.uu.oz.au) for
suggestions, the (preliminary) runtime library, and the test program.
I tend to move around a great deal, so mail sent directly to me
sometimes has a hard time catching up. If you want mail to reach me (it
may take a while, but it WILL reach me), send it to this address:
Bryan Ford
8749 Alta Hills Circle
Sandy, UT 84093
I can be reached more quickly (for the time being anyway) on the phone
or through one of the electronic mail addresses below:
(801) 585-4619
bryan.ford@m.cc.utah.edu
baf0863@cc.utah.edu
baf0863@utahcca.bitnet
If you want to get something to me through the mail more quickly, FIRST
call or E-mail me to make sure I'm still here, then send it to this
address:
Bryan Ford
27104 Ballif Hall
University of Utah
Salt Lake City, UT 84112
Enjoy!
Functions
~~~~~~~~~
This section describes in detail the functions that MemMan makes
available to the the application. These functions are defined as both
assembly-style functions without any name prefix as well as prefixed with
'_' to allow calls from SAS/C and Manx C. To call these functions from
assembly language, all you need to do is 'xref' the names and put the
appropriate arguments in the appropriate registers before 'bsr'- or
'jsr'ing to the function. For SAS/C and Manx C, you just need to include
the header file "MemMan.h" which prototypes these functions and tells the
compiler which registers to use for arguments. (To use the runtime
library, you must define MM_RUNLIB before including this file.) For other
compilers or languages, you may have to do a little adaptation.
int Success = MMInit(void)
D0
Initializes MemMan. (This function does not exist in the runtime
library.) You must call this before calling any other MemMan function
except for MMFinish(). You must test the return code - if it is zero, it
means that there wasn't enough memory to initialize. (The first time
MemMan is initialized in the system, some code is made permanently
resident. Other invocations of programs that use MemMan then use this code
without allocating anything else.) You must NEVER call MMInit() more than
once in your program!
void MMFinish(void)
Closes down MemMan. This function does not exist in the runtime
library.) This never actually causes memory to be freed (the resident code
remains in memory until reboot), but it puts the AllocMem() patch "to
sleep" so it uses a minimum of extra CPU time while MemMan is not in use.
It is not dangerous to call MMFinish() more than once, or to call it
without ever calling MMInit(), or to call it after MMInit() has failed.
However, after you call MMFinish(), you may not call ANY other MemMan
functions before your program terminates.
Before calling MMFinish() (or closing MMBase, in the case of the
runtime library), ALL MMNodes you own MUST have been taken off the list!
Remember that the MMList is not private to your application - it is used by
other applications that are running MemMan at the same time, and it remains
in memory even after all applications using MemMan have terminated. (The
same list will be used again the next time some program starts using
MemMan.)
void MMAddNode(struct MMNode *node)
A1
Adds an MMNode to the systemwide MMList. An MMNode represents an item
that can be freed on demand. (See the 'MMNodes' section below for details
on creating these structures.) This function has protection against adding
a node twice, so as long as you don't muck with the ln_Type field in the
Node, you can call MMAddNode() any time you want to make sure a particular
MMNode is on the list. This function is very simple and quick, especially
if the node is already on the list, so there's no problem with calling this
function often.
void MMRemNode(struct MMNode *node)
A1
Removes an MMNode from the MMList. As with MMAddNode(), you don't have
to worry about calling this function with an MMNode that wasn't already on
the list (as long as you call it with a valid MMNode). Also like
MMAddNode(), this function executes very quickly, so you can call it often.
MMNodes
~~~~~~~
To use MemMan, you will need to create structures called MMNodes. This
structure is defined in C as follows:
struct MMNode
{
struct Node Node;
long (*GetRidFunc)(long size,long memtype,void *data);
void *GetRidData;
};
Each MMNode structure is generally associated with one particular piece
of memory (or several pieces closely tied together) that may be freed on
demand when the system needs more memory. MMNodes are added to a single
system-wide (one for all applications currently using MemMan) list by
MMAddNode(), in order of priority.
When a memory crunch occurs and some AllocMem() call is about to fail,
MemMan starts traversing the system MMList. It first calls the MMNode with
the highest priority, then retries the AllocMem() call once, then if it
still fails, it finds the next lower MMNode in priority and tries again,
and so on. If one of the calls succeeds in freeing up enough memory to
allow the AllocMem() to succeed, MemMan will stop traversing the list
immediately, so the lower-priority nodes aren't affected. If MemMan gets
all the way through the MMList and still can't get enough memory to satisfy
the AllocMem() request, it will finally fail and return NULL to the
original caller.
You can create an MMNode as either a static/global variable in your
program, or allocate it dynamically with AllocMem(). If you use
AllocMem(), you MUST either use the MEMF_CLEAR flag, or explicitly clear
the ln_Type field in the Node structure before calling MMAddNode() or
MMRemNode() with it. (Static/global variables always get initialized to
zero before the program starts, so you don't have to worry about it in this
case.)
You should set the ln_Pri field in the Node to an appropriate priority
level. (See the 'Priorities' section for guidelines on selecting a
priority level.) The GetRidFunc pointer should point to the function in
your application which will be called during a memory crunch, in order to
free some memory. (See the 'GetRidFuncs' section below for information on
creating these functions.) The GetRidData pointer can be anything you want,
and is simply passed to the GetRidFunc whenever it is called.
You should (though you don't have to) set the ln_Name field in the Node
to the name of your program or some other descriptive string. This may be
helpful in debugging programs that use MemMan.
Once you have created and initialized an MMNode, you can add it to the
system list at any time with MMAddNode(), and remove it later with
MMRemNode(). To reiterate what I said before, ALL nodes you add MUST be
removed from the system list before your program ends.
GetRidFuncs
~~~~~~~~~~~
The GetRidFunc pointed to by a MMNode is the function that MemMan will
call whenever there is a memory crunch and it needs you to free some
memory. Note that it takes register arguments, NOT standard C-style stack
arguments. In SAS/C, you must define the function with __regargs; in Manx
C, I'm not sure what you do, but I know it's possible. The GetRidFunc will
be called with the following arguments:
void GetRidFunc(long MemSize, long MemType, void *GetRidData)
D0 D1 A0
The first two arguments are simply the arguments from the AllocMem()
call that is causing the memory crunch. In general, you shouldn't need to
look at MemSize, but it's there just in case you find some use for it. You
can look at the MemType parameter to see if giving up your memory will
actually benefit the caller at all. For example, if the caller specifies
MEMF_CHIP and you know for *sure* that the memory you can free is *not*
chip memory, then you don't need to free your memory. (Remember, though,
that AllocMem() may give you chip memory even when you don't specifically
ask for it.)
The GetRidData argument is simply a copy of the GetRidData pointer you
supplied in the MMNode. You can use it as a pointer back to the MMNode, or
to some other related data structure, or whatever.
Since your GetRidFunc may be called by any Task or Process in the
system, at any time AllocMem() can be called, you must be very careful when
writing it. Here are some things to watch out for:
You must not depend on any registers, except for the specified
arguments. For SAS/C, this means you must use the __saveds qualifier when
defining the function.
Be sure to disable stack checking for the GetRidFunc. For SAS/C, you
can use __interrupt, or you can supply -v on the command line to disable
stack checking for the whole program.
Standard Amiga register saving conventions must be used - your function
must save all registers except D0, D1, A0, and A1. Most compilers should
have no problem with this requirement.
The GetRidFunc must NEVER break the Forbid() state by calling Wait() or
any function that might indirectly call it. If another task is permitted
to execute while MemMan is in the middle of the MMList, things could get
very interesting.
Since it can be called from Tasks as well as Processes, it must NEVER
call the dos.library, or any functions that might call the dos.library.
(Sorry, no swapping memory out to disk from a GetRidFunc.)
It must use VERY LITTLE stack space. This means no big Un*x-style auto
arrays! Ideally, the GetRidFunc should use no more (actually a little
less) stack space than the actual AllocMem() function uses, which is very
small. (I haven't checked exactly.)
It must NEVER call AllocMem() or any function that might call
AllocMem(). Sorry, this means that at this point you may not swap chip
memory buffers to fast memory (unless you've already pre-allocated a fast
memory buffer). I *may* add a feature to MemMan later that would allow you
to call the previous AllocMem() vector. However, this would bypass other
programs such as Mungwall that add themselves to the AllocMem() chain,
possibly causing interesting problems. We'll see how things work out.
The GetRidFunc must NEVER call MMAddNode() or anything that could call
this function. This could cause the MMList to be changed while MemMan is
traversing it.
The GetRidFunc MAY call MMRemNode() on the CURRENT MMNode (the MMNode
that caused this call to be made) but NEVER on any OTHER node.
Of course, the GetRidFunc may call FreeMem() as much as it wants.
After all, that's the whole point of this system.
To summarize, you make sure your GetRidFunc is defined correctly if
you're using a high-level language, and keep your GetRidFunc as simple and
direct as possible. If possible, limit your calls to FreeMem() and
MMRemNode() only.
Choosing Priority
~~~~~~~~~~~~~~~~~
MemMan traverses the MMList in order of priority from highest priority
to lowest priority. Therefore, MMNodes with highest priority will be
called first. You should assign your MMNodes with high priorities for
pieces of memory that aren't needed very much or can be recovered easily,
and assign lower priorities for those that you'd really like to keep if at
all possible. Although this may be the opposite of what you would normally
expect (having nodes of the lowest priority being the most "important" to
keep in memory), there is a good reason for this: When several MMNodes
have the same priority, the first one to be added will be called first,
creating a kind of least-recently-used (LRU) caching system. This results
from how Exec's Enqueue() function operates. If the list were to be
traversed from the tail to the head, as might seem more natural, then the
MOST recently added nodes would be called first, which is not generally a
good algorithm for memory management.
It is a good idea to choose priority for your nodes carefully, since
priority not only affects order of calls to your application, but also
affects the order of calls to different applications. Remember that your
MMNodes may be mixed in with the MMNodes of many other applications using
MemMan at the same time.
Priority should be chosen according to the penalty incurred for freeing
that memory (time delays for restoring the data, disk reloads, etc.). Here
are some general guidelines for selecting priority:
100 and up: Use these for pieces of memory that are mostly unnecessary
or that can be recovered with almost no effort or time delay. You might
create large speed-up tables and such with this priority - things that
benefit system performance if enough memory is available, but are not
necessary to correct functioning, and can be easily recovered later.
50 to 100: Use these priority levels for things that may require time
delays or cause other small nuisances in order to restore them later. For
example, precalculated data tables and such would fit into this category -
things that aren't really needed at the moment, but may be a slight pain to
get back later when they *are* needed.
-50 to 50: These priority levels are intended for memory that isn't
needed immediately, but contains cached data that will need to be reloaded
(as opposed to simply recalculated) if needed later. My overlay supervisor
("Bovs") uses priority 0 for all overlay nodes not currently in use.
-100 to -50: This range is for data that can still be recovered, but
only at great expense. For example, large data tables which take a while
to recaulculate, or data that must be reloaded *and* processed or
decompressed if it is needed again.
Below -100: These levels should be used for data that CANNOT be
recovered at all. For example, you could put an application's undo buffers
at this level. It's better to lose some possible undo capability than to
not be able to use the program at all, but the undo buffers should be
retained as long as possible.
Final Warnings
~~~~~~~~~~~~~~
MMNodes should remain private to a particular task or process. In
other words, don't MMAddNode() in one task and MMRemNode() in another, on
the same node. While the MemMan functions know how to deal with a shared
MMList, they do NOT know how to deal with shared MMNodes. The only
exception to this rule is with MMRemNode() called from within a GetRidFunc
(possibly in a different task), as explained elsewhere.
Always MMInit() before using MMAddNode() or MMRemNode(). (You may call
MMFinish() without calling MMInit().)
Never call MMInit() more than once in your program.
Never call AllocMem(), MMAddNode(), or the dos.library from a GetRidFunc.
Never call MMRemNode() on any MMNode OTHER than the one that caused
this particular GetRidFunc call.
Never allow a GetRidFunc to break the Forbid() state by calling
anything that might cause a Wait().
Use as little stack space as possible in your GetRidFunc.
Finally, one more time: ALWAYS remove ALL MMNodes you added BEFORE
calling MMFinish() or closing the library! This can't be stressed enough.
If you fail to do this, you will most likely NOT see any immediate
problems. The problems will only show up the next time you run out of
memory, and even then only if the MemMan patch is currently "awake" (when
an application is using MemMan). You should test your program VERY
thoroughly for bugs in this area, as outlined in the 'Hints' section below.
Be VERY careful to follow these rules. Test your program thoroughly,
bang it to its limits, and subject it to every known program terror.
MemMan and the operating system are not very forgiving when it comes to
low-level programming like this.
Hints
~~~~~
Don't keep MMNodes on the system list unless they actually represent
free-able data. When you want to lock something in memory, use MMRemNode()
to prevent it from being expunged. Then when you unlock it, use
MMAddNode() to put it back on the list. A GetRidFunc should be able to
simply FreeMem() and return, without having to check locks or anything.
When debugging your program, use the 'Avail FLUSH' command in 2.0 (if
you have 2.0; if you don't, now would be a good time to get it) to test
your program's handling of memory crunches. Play around with your program
a little, get some caches loaded, etc., then use 'Avail FLUSH' to force
MemMan into action. Make sure the system doesn't crash, and make sure
memory is being freed as expected. Run your program several times in a
row, in each case "exercising" MemMan, to make sure your program is not
leaving "stale" MMNodes on the global list. Finally, run several copies of
your program simultaneously to make sure they don't interfere with each
other. If you happen to have another program that also uses MemMan (such
as MultiPlayer or another program that uses Bovs), load up a copy or two of
that alongside as well. Another good program to test with is FragIt by
Justin V. McCormick. Really bash it out.
History
~~~~~~~
2.0 (4-Dec-91)
Added runtime library and test program (originally by David Le Blanc).
Changed format of MMNode to use a standard Node structure.
Changed semaphore name to MemMan2 to avoid version conflicts.