2003-07-28 01:44:38 +00:00
/* Copyright (C) 1994-2003 Revolution Software Ltd
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* of the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*
* $ Header $
*/
//memory manager - "remember, it's not good to leave memory locked for a moment longer than necessary" Tony
// "actually, in a sequential system theoretically you never need to lock any memory!" Chris ;)
//
// This is a very simple implementation but I see little advantage to being any cleverer
// with the coding - i could have put the mem blocks before the defined blocks instead
// of in an array and then used pointers to child/parent blocks. But why bother? I've Kept it simple.
// When it needs updating or customising it will be accessable to anyone who looks at it.
// *doesn't have a purgeable/age consituant yet - if anyone wants this then I'll add it in.
// MemMan v1.1
# include <stdarg.h>
# include <stdio.h>
# include <stdlib.h>
2003-07-28 03:12:49 +00:00
# include "stdafx.h"
2003-07-28 01:44:38 +00:00
# include "driver/driver96.h"
# include "console.h"
# include "debug.h"
# include "memory.h"
# include "resman.h"
uint32 total_blocks ;
uint32 base_mem_block ;
uint32 total_free_memory ;
uint8 * free_memman ; //address of init malloc to be freed later
//#define MEMDEBUG 1
mem mem_list [ MAX_mem_blocks ] ; //list of defined memory handles - each representing a block of memory.
int32 VirtualDefrag ( uint32 size ) ; // Used to determine if the required size can be obtained if the defragger is allowed to run.
int32 suggestedStart = 0 ; // Start position of the Defragger as indicated by its sister VirtualDefrag.
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
void Close_memory_manager ( void ) //Tony2Oct96
{
//unlock our supposedly locked in memory
2003-07-28 07:47:21 +00:00
SVM_VirtualUnlock ( free_memman , total_free_memory ) ;
2003-07-28 01:44:38 +00:00
free ( free_memman ) ;
}
//------------------------------------------------------------------------------------
void Init_memory_manager ( void ) //Tony9April96
{
uint32 j ;
uint8 * memory_base ;
//BOOL res;
2003-07-28 07:52:01 +00:00
SVM_MEMORYSTATUS memo ;
2003-07-28 01:44:38 +00:00
//find out how much actual physical RAM this computer has
2003-07-28 07:47:21 +00:00
SVM_GlobalMemoryStatus ( & memo ) ;
2003-07-28 01:44:38 +00:00
//now decide how much to grab - 8MB computer are super critical
if ( memo . dwTotalPhys < = ( 8000 * 1024 ) ) //if 8MB or less :-O
total_free_memory = 4500 * 1024 ; //4.5MB
else if ( memo . dwTotalPhys < = ( 12000 * 1024 ) ) //if 8MB or less :-O
total_free_memory = 8000 * 1024 ; //8MB
else if ( memo . dwTotalPhys < = ( 16000 * 1024 ) ) //if 16MB or less :-)
total_free_memory = 10000 * 1024 ; //10MB
else //:-)) loads of RAM
total_free_memory = 12000 * 1024 ; //12MB
Zdebug ( " MEM = %d " , memo . dwTotalPhys ) ;
Zdebug ( " Sword 2 grabbed %dk " , total_free_memory / 1024 ) ;
//malloc memory and adjust for long boundaries
memory_base = ( uint8 * ) malloc ( total_free_memory ) ;
if ( ! memory_base ) //could not grab the memory
{
Zdebug ( " couldn't malloc %d in Init_memory_manager " , total_free_memory ) ;
ExitWithReport ( " Init_memory_manager() couldn't malloc %d bytes [line=%d file=%s] " , total_free_memory , __LINE__ , __FILE__ ) ;
}
free_memman = memory_base ; //the original malloc address
//force to long word boundary
memory_base + = 3 ;
memory_base = ( uint8 * ) ( ( uint32 ) memory_base & 0xfffffffc ) ; // ** was (int)memory_base
// total_free_memory-=3; //play safe
//set all but first handle to unused
for ( j = 1 ; j < MAX_mem_blocks ; j + + )
mem_list [ j ] . state = MEM_null ;
total_blocks = 1 ; //total used (free, locked or floating)
mem_list [ 0 ] . ad = memory_base ;
mem_list [ 0 ] . state = MEM_free ;
mem_list [ 0 ] . age = 0 ;
mem_list [ 0 ] . size = total_free_memory ;
mem_list [ 0 ] . parent = - 1 ; //we are base - for now
mem_list [ 0 ] . child = - 1 ; //we are the end as well
mem_list [ 0 ] . uid = UID_memman ; //init id
base_mem_block = 0 ; //for now
//supposedly this will stop the memory swapping out?? Well, as much as we're allowed
// res=VirtualLock(free_memman, total_free_memory);
// if (res!=TRUE)
// Zdebug(" *VirtualLock failed");
}
//------------------------------------------------------------------------------------
mem * Talloc ( uint32 size , uint32 type , uint32 unique_id ) //Tony10Apr96
{
//allocate a block of memory - locked or float
// returns 0 if fails to allocate the memory
// or a pointer to a mem structure
int32 nu_block ;
uint32 spawn = 0 ;
uint32 slack ;
//we must first round the size UP to a dword, so subsequent blocks will start dword alligned
size + = 3 ; //move up
size & = 0xfffffffc ; //and back down to boundary
//find a free block large enough
if ( ( nu_block = Defrag_mem ( size ) ) = = - 1 ) //the defragger returns when its made a big enough block. This is a good time to defrag as we're probably not
{ //doing anything super time-critical at the moment
return ( 0 ) ; //error - couldn't find a big enough space
}
//an exact fit?
if ( mem_list [ nu_block ] . size = = size ) //no new block is required as the fit is perfect
{
mem_list [ nu_block ] . state = type ; //locked or float
mem_list [ nu_block ] . size = size ; //set to the required size
mem_list [ nu_block ] . uid = unique_id ; //an identifier
# ifdef MEMDEBUG
Mem_debug ( ) ;
# endif //MEMDEBUG
return ( & mem_list [ nu_block ] ) ;
}
// nu_block is the free block to split, forming our locked/float block with a new free block in any remaining space
//if our child is free then is can expand downwards to eat up our chopped space
//this is good because it doesn't create an extra bloc so keeping the block count down
//why?
//imagine you Talloc 1000k, then free it. Now keep allocating 10 bytes less and freeing again
//you end up with thousands of new free mini blocks. this way avoids that as the free child keeps growing downwards
if ( ( mem_list [ nu_block ] . child ! = - 1 ) & & ( mem_list [ mem_list [ nu_block ] . child ] . state = = MEM_free ) ) //our child is free
{
slack = mem_list [ nu_block ] . size - size ; //the spare memory is the blocks current size minus the amount we're taking
mem_list [ nu_block ] . state = type ; //locked or float
mem_list [ nu_block ] . size = size ; //set to the required size
mem_list [ nu_block ] . uid = unique_id ; //an identifier
mem_list [ mem_list [ nu_block ] . child ] . ad = mem_list [ nu_block ] . ad + size ; //child starts after us
mem_list [ mem_list [ nu_block ] . child ] . size + = slack ; //childs size increases
return ( & mem_list [ nu_block ] ) ;
}
// otherwise we spawn a new block after us and before our child - our child being a proper block that we cannot change
// we remain a child of our parent
// we spawn a new child and it inherits our current child
//find a NULL slot for a new block
while ( ( mem_list [ spawn ] . state ! = MEM_null ) & & ( spawn ! = MAX_mem_blocks ) )
spawn + + ;
if ( spawn = = MAX_mem_blocks ) //run out of blocks - stop the program. this is a major blow up and we need to alert the developer
{
Mem_debug ( ) ; //Lets get a printout of this
ExitWithReport ( " ERROR: ran out of mem blocks in Talloc() [file=%s line=%u] " , __FILE__ , __LINE__ ) ;
}
mem_list [ spawn ] . state = MEM_free ; //new block is free
mem_list [ spawn ] . uid = UID_memman ; //a memman created bloc
mem_list [ spawn ] . size = mem_list [ nu_block ] . size - size ; //size of the existing parent free block minus the size of the new space Talloc'ed.
//IOW the remaining memory is given to the new free block
mem_list [ spawn ] . ad = mem_list [ nu_block ] . ad + size ; //we start 1 byte after the newly allocated block
mem_list [ spawn ] . parent = nu_block ; //the spawned child gets it parent - the newly allocated block
mem_list [ spawn ] . child = mem_list [ nu_block ] . child ; //the new child inherits the parents old child (we are its new child "Waaaa")
if ( mem_list [ spawn ] . child ! = - 1 ) //is the spawn the end block?
mem_list [ mem_list [ spawn ] . child ] . parent = spawn ; //the child of the new free-spawn needs to know its new parent
mem_list [ nu_block ] . state = type ; //locked or float
mem_list [ nu_block ] . size = size ; //set to the required size
mem_list [ nu_block ] . uid = unique_id ; //an identifier
mem_list [ nu_block ] . child = spawn ; //the new blocks new child is the newly formed free block
total_blocks + + ; //we've brought a new block into the world. Ahhh!
# ifdef MEMDEBUG
Mem_debug ( ) ;
# endif //MEMDEBUG
return ( & mem_list [ nu_block ] ) ;
}
//------------------------------------------------------------------------------------
void Free_mem ( mem * block ) //Tony10Apr96
{
//kill a block of memory - which was presumably floating or locked
//once you've done this the memory may be recycled
block - > state = MEM_free ;
block - > uid = UID_memman ; //belongs to the memory manager again
# ifdef MEMDEBUG
Mem_debug ( ) ;
# endif //MEMDEBUG
}
//------------------------------------------------------------------------------------
void Float_mem ( mem * block ) //Tony10Apr96
{
//set a block to float
//wont be trashed but will move around in memory
block - > state = MEM_float ;
# ifdef MEMDEBUG
Mem_debug ( ) ;
# endif //MEMDEBUG
}
//------------------------------------------------------------------------------------
void Lock_mem ( mem * block ) //Tony11Apr96
{
//set a block to lock
//wont be moved - don't lock memory for any longer than necessary unless you know the locked memory is at the bottom of the heap
block - > state = MEM_locked ; //can't move now - this block is now crying out to be floated or free'd again
# ifdef MEMDEBUG
Mem_debug ( ) ;
# endif //MEMDEBUG
}
//------------------------------------------------------------------------------------
int32 Defrag_mem ( uint32 req_size ) //Tony10Apr96
{
//moves floating blocks down and/or merges free blocks until a large enough space is found
//or there is nothing left to do and a big enough block cannot be found
//we stop when we find/create a large enough block - this is enough defragging.
int32 cur_block ; //block 0 remains the parent block
int32 original_parent , child , end_child ;
uint32 j ;
uint32 * a ;
uint32 * b ;
// cur_block=base_mem_block; //the mother of all parents
cur_block = suggestedStart ;
do
{
if ( mem_list [ cur_block ] . state = = MEM_free ) //is current block a free block?
{
if ( mem_list [ cur_block ] . size > = req_size )
{
return ( cur_block ) ; //this block is big enough - return its id
}
if ( mem_list [ cur_block ] . child = = - 1 ) //the child is the end block - stop if the next block along is the end block
return ( - 1 ) ; //no luck, couldn't find a big enough block
// current free block is too small, but if its child is *also* free then merge the two together
if ( mem_list [ mem_list [ cur_block ] . child ] . state = = MEM_free )
{
// ok, we nuke the child and inherit its child
child = mem_list [ cur_block ] . child ;
mem_list [ cur_block ] . size + = mem_list [ child ] . size ; //our size grows by the size of our child
mem_list [ cur_block ] . child = mem_list [ child ] . child ; //our new child is our old childs, child
if ( mem_list [ child ] . child ! = - 1 ) //not if the chld we're nuking is the end child (it has no child)
mem_list [ mem_list [ child ] . child ] . parent = cur_block ; //the (nuked) old childs childs parent is now us
mem_list [ child ] . state = MEM_null ; //clean up the nuked child, so it can be used again
total_blocks - - ;
}
// current free block is too small, but if its child is a float then we move the floating memory block down and the free up
// but, parent/child relationships must be such that the memory is all continuous between blocks. ie. a childs memory always
// begins 1 byte after its parent finishes. However, the positions in the memory list may become truly random, but, any particular
// block of locked or floating memory must retain its position within the mem_list - the float stays a float because the handle/pointer has been passed back
// what this means is that when the physical memory of the foat moves down (and the free up) the child becomes the parent and the parent the child
// but, remember, the parent had a parent and the child another child - these swap over too as the parent/child swap takes place - phew.
else if ( mem_list [ mem_list [ cur_block ] . child ] . state = = MEM_float )
{
child = mem_list [ cur_block ] . child ; //our child is currently floating
// memcpy(mem_list[cur_block].ad, mem_list[child].ad, mem_list[child].size); //move the higher float down over the free block
a = ( uint32 * ) mem_list [ cur_block ] . ad ;
b = ( uint32 * ) mem_list [ child ] . ad ;
for ( j = 0 ; j < mem_list [ child ] . size / 4 ; j + + )
* ( a + + ) = * ( b + + ) ;
// both *ad's change
mem_list [ child ] . ad = mem_list [ cur_block ] . ad ; //the float is now where the free was
mem_list [ cur_block ] . ad + = mem_list [ child ] . size ; //and the free goes up by the size of the float (which has come down)
// the status of the mem_list blocks must remain the same, so...
original_parent = mem_list [ cur_block ] . parent ; //our child gets this when we become its child and it our parent
mem_list [ cur_block ] . parent = child ; //the free's child becomes its parent
mem_list [ cur_block ] . child = mem_list [ child ] . child ; //the new child inherits its previous childs child
end_child = mem_list [ child ] . child ; //save this - see next line
mem_list [ child ] . child = cur_block ; //the floats parent becomes its child
mem_list [ child ] . parent = original_parent ;
if ( end_child ! = - 1 ) //if the child had a child
mem_list [ end_child ] . parent = cur_block ; //then its parent is now the new child
if ( original_parent = = - 1 ) //the base block was the true base parent
base_mem_block = child ; //then the child that has moved down becomes the base block as it sits at the lowest possible memory location
else
mem_list [ original_parent ] . child = child ; //otherwise the parent of the current free block - that is now the child - gets a new child,
//that child being previously the child of the child of the original parent
}
else //if (mem_list[mem_list[cur_block].child].state==MEM_lock) //the child of current is locked - move to it
cur_block = mem_list [ cur_block ] . child ; //move to next one along - either locked or END
}
else
{
cur_block = mem_list [ cur_block ] . child ; //move to next one along, the current must be floating, locked, or a NULL slot
}
}
while ( cur_block ! = - 1 ) ; //while the block we've just done is not the final block
return ( - 1 ) ; //no luck, couldn't find a big enough block
}
//------------------------------------------------------------------------------------
void Mem_debug ( void ) //Tony11Apr96
{
//gets called with Talloc, Mem_free, Mem_lock & Mem_float if MEMDEBUG has been #defined
//otherwise can be called at any time anywhere else
int j ;
char inf [ ] [ 20 ] =
{
{ " MEM_null " } ,
{ " MEM_free " } ,
{ " MEM_locked " } ,
{ " MEM_float " }
} ;
Zdebug ( " \n base %d total %d " , base_mem_block , total_blocks ) ;
//first in mem list order
for ( j = 0 ; j < MAX_mem_blocks ; j + + )
{
if ( mem_list [ j ] . state = = MEM_null )
Zdebug ( " %d- NULL " , j ) ;
else
Zdebug ( " %d- state %s, ad %d, size %d, p %d, c %d, id %d " , j ,
inf [ mem_list [ j ] . state ] ,
mem_list [ j ] . ad , mem_list [ j ] . size , mem_list [ j ] . parent , mem_list [ j ] . child , mem_list [ j ] . uid ) ;
}
//now in child/parent order
j = base_mem_block ;
do
{
Zdebug ( " %d- state %s, ad %d, size %d, p %d, c %d " , j ,
inf [ mem_list [ j ] . state ] ,
mem_list [ j ] . ad , mem_list [ j ] . size , mem_list [ j ] . parent , mem_list [ j ] . child , mem_list [ j ] . uid ) ;
j = mem_list [ j ] . child ;
}
while ( j ! = - 1 ) ;
}
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
mem * Twalloc ( uint32 size , uint32 type , uint32 unique_id ) //tony12Feb97
{
//the high level Talloc
//can ask the resman to remove old resources to make space - will either do it or halt the system
mem * membloc ;
int j ;
uint32 free = 0 ;
while ( VirtualDefrag ( size ) )
{
if ( ! res_man . Help_the_aged_out ( ) ) //trash the oldest closed resource
{
Zdebug ( " Twalloc ran out of memory! %d %d %d \n " , size , type , unique_id ) ;
ExitWithReport ( " Twalloc ran out of memory! " ) ;
}
}
membloc = Talloc ( size , type , unique_id ) ;
if ( membloc = = 0 )
{
Zdebug ( " Talloc failed to get memory VirtualDefrag said was there " ) ;
ExitWithReport ( " Talloc failed to get memory VirtualDefrag said was there " ) ;
}
j = base_mem_block ;
do
{
if ( mem_list [ j ] . state = = MEM_free )
free + = mem_list [ j ] . size ;
j = mem_list [ j ] . child ;
}
while ( j ! = - 1 ) ;
return ( membloc ) ; //return the pointer to the memory
}
# define MAX_WASTAGE 51200 // Maximum allowed wasted memory.
int32 VirtualDefrag ( uint32 size ) // Chris - 07 April '97
{
//
// Virutually defrags memory...
//
// Used to determine if there is potentially are large enough free block available is the
// real defragger was allowed to run.
//
// The idea being that Twalloc will call this and help_the_aged_out until we indicate that
// it is possible to obtain a large enough free block. This way the defragger need only
// run once to yield the required block size.
//
// The reason for its current slowness is that the defragger is potentially called several
// times, each time shifting upto 20Megs around, to obtain the required free block.
//
int32 cur_block ;
uint32 currentBubbleSize = 0 ;
cur_block = base_mem_block ;
suggestedStart = base_mem_block ;
do
{
if ( mem_list [ cur_block ] . state = = MEM_free )
{
// Add a little intelligence. At the start the oldest resources are at the bottom of the
// tube. However there will be some air at the top. Thus bubbles will be
// created at the bottom and float to the top. If we ignore the top gap
// then a large enough bubble will form lower down the tube. Thus less memory
// will need to be shifted.
if ( mem_list [ cur_block ] . child ! = - 1 )
currentBubbleSize + = mem_list [ cur_block ] . size ;
else if ( mem_list [ cur_block ] . size > MAX_WASTAGE )
currentBubbleSize + = mem_list [ cur_block ] . size ;
if ( currentBubbleSize > = size )
return 0 ;
}
else if ( mem_list [ cur_block ] . state = = MEM_locked )
{
currentBubbleSize = 0 ;
suggestedStart = mem_list [ cur_block ] . child ; // Any free block of the correct size will be above this locked block.
}
cur_block = mem_list [ cur_block ] . child ;
}
while ( cur_block ! = - 1 ) ;
return ( 1 ) ;
}
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------