home *** CD-ROM | disk | FTP | other *** search
- 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.
-
-
-