home *** CD-ROM | disk | FTP | other *** search
- MemMan
- Version 2.1
- Low-Memory manager
- Copyright 1992 Bryan FORD
-
- Warning: DO NOT try to use MemMan without FULLY reading this documentation!
-
-
- Disclaimer
- ~~~~~~~~~~
-
- This program is provided "as is" without warranty of any kind, either
- expressed or implied, including, but not limited to, the implied warranty
- of fitness for a particular purpose. The entire risk as to the results,
- reliability and performance of this program is assumed by you.
-
-
-
- Distribution
- ~~~~~~~~~~~~
-
- MemMan may be freely distributed as long as it is complete (none of the
- files listed below are left out), all of the files are unmodified, and no
- charge is made for such distribution other than a small fee to cover the
- cost of copying. (In effect, no profit may be made through distribution of
- this program). You may modify this code for your own personal use, but you
- may not distribute modified versions. (If you improve MemMan, just send
- your modifications back to me - I'll incorporate them into the master,
- giving appropriate credit to you, and we won't have hundreds of different
- versions of MemMan floating around.)
-
- You may use the object code derived from this source code, or your own
- modified version of this source code, in your own public domain, freeware,
- shareware, or otherwise freely distributable programs, as long as you state
- that you used MemMan and mention the name of its author (Bryan Ford)
- somewhere in your documentation. You may also distribute the
- memman.library with your freely distributable programs. You do not need to
- obtain any kind of license to use it in freely distributable software.
-
- If you want to distribute MemMan, the memman.library, or any modified
- version of MemMan, in either source or object form, with a commercial
- package, you must first obtain a license from me (Bryan Ford). The cost of
- the license will be equal to the retail price of the commercial program in
- which MemMan is used. Additionally, you must send me a copy of the program
- as soon as it is finished and released. The license fee covers all future
- versions of your program; however you must send me copies of any major
- updates you release afterwards, if they still use Bovs. This license cost
- will never increase, but I reserve the right to decrease or eliminate it.
-
- 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
-
-
-
- Acknowledgments
- ~~~~~~~~~~~~~~~
-
- Michael Sinz of Commodore for his valuable suggestions for improving
- this code and making it more compatible with future versions of the OS.
-
- David Le Blanc for the library version and the test program.
-
-
-
- Introduction
- ~~~~~~~~~~~~
-
- 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 first
- 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 the library.) 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 of A68k that can assemble it 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.)
-
-
-
- Functions
- ~~~~~~~~~
-
- This section describes in detail the functions that MemMan makes
- available to the the application. These functions are externally defined
- by MemMan.o as both assembly-language-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 CPU resources while MemMan is not in use. It
- is not dangerous to call MMFinish() more than once, 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 MemMan's
- 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. You may call this function from any interrupt code except
- the Non-Maskable Interrupt (NMI).
-
-
- 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,
- and you may call this function from any interrupt code except NMI.
-
-
-
- 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 list (the same for all applications currently using MemMan) 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 routine
- associated with the MMNode of 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 frees 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 of AllocMem().
-
- 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 to zero before calling MMAddNode()
- or MMRemNode() with it. (Static/global variables 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 for 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 emphasize again 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 as __regargs; in Manx,
- I'm not sure what you do, but I know it's possible. The GetRidFunc will be
- called with the following arguments:
-
- long GetRidFunc(long MemSize, long MemType, void *GetRidData)
- DO 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 return chip memory even if you didn'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.
-
- The GetRidFunc must return nonzero if any memory at all was freed, or
- zero if it couldn't free anything. If a GetRidFunc returns zero,
-
- 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 specific rules you must observe:
-
- 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
- automatically comply 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, although you may
- send a signal or message to a process to do it later.)
-
- The GetRidFunc 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 to see exactly what it is.)
-
- 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 node (the MMNode
- that caused this call to be made) but NEVER on any OTHER node.
-
- The GetRidFunc may call AllocMem() and FreeMem(). The general
- intention, of course, is to call FreeMem() at least once. It is possible
- to swap chip-memory data into fast memory in a GetRidFunc by allocating a
- block of MEMF_FAST memory, copying the chip memory data into the new block,
- and freeing the old block.
-
- To summarize, you shoul 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 AllocMem(),
- 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 of the same priority 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 buffers 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 if
- needed.
-
- 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
- from disk (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 decompressed if 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 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
- ~~~~~~~~~~~~~~
-
- 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 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.
-
- 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 of mistakes 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 ideally be
- able to simply FreeMem() and return, without having to check locks or
- anything. MMAddNode() and MMRemNode() are completely atomic, and may be
- called from any task or interrupt except the NMI. They are also very
- quick, so adding and removing MMNodes won't put a great burden on the
- system.
-
- 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.1 (18-Feb-92)
- GetRidFunc() calling convention changed - it now returns nonzero if
- any memory was freed, zero if it couldn't free anything.
- MMAddNode() and MMRemNode() may now be called from interrupt code,
- or from different tasks or processes.
- A GetRidFunc may now call AllocMem().
- Changed semaphore name to MemMan21.
- Other small reworkings - MemMan should be completely bulletproof now.
-
- 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.
-
-
-
- Contact Address
- ~~~~~~~~~~~~~~~
-
- 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
-
-
-