// Ruler // 1 2 3 4 5 6 7 8 //345678901234567890123456789012345678901234567890123456789012345678901234567890 /********************************************************************/ /* */ /* The standard layout. */ /* */ /* The standard layout for 'cpp' files in this code is as */ /* follows: */ /* */ /* 1. Include files. */ /* 2. Constants local to the class. */ /* 3. Data structures local to the class. */ /* 4. Data initializations. */ /* 5. Static functions. */ /* 6. Class functions. */ /* */ /* The constructor is typically the first function, class */ /* member functions appear in alphabetical order with the */ /* destructor appearing at the end of the file. Any section */ /* or function this is not required is simply omitted. */ /* */ /********************************************************************/ #include "HeapPCH.hpp" #include "Cache.hpp" #include "Find.hpp" #include "Heap.hpp" #include "NewPage.hpp" #include "Page.hpp" /********************************************************************/ /* */ /* Constants local to the class. */ /* */ /* The constants supplied here allow the allocation bit vector */ /* to be rapidly searched for free storage. */ /* */ /********************************************************************/ CONST BIT32 AllocatedMask = 0x2; CONST BIT32 FullSearchMask = 0xaaaaaaaa; CONST BIT32 FullWordShift = (MaxBitsPerWord - OverheadBits); CONST BIT32 SubDividedMask = 0x1; CONST BIT32 WordSearchMask = (AllocatedMask << FullWordShift); /********************************************************************/ /* */ /* Class constructor. */ /* */ /* */ /* All page descriptions are actually created and deleted by */ /* a separate class called 'NEW_PAGE'. Nonetheless, as a */ /* step the appropriate constructors and destructors are */ /* invoked to support the standard C++ programming methodology. */ /* */ /********************************************************************/ PAGE::PAGE ( VOID *NewAddress, CACHE *NewCache, SBIT32 NewPageSize, CACHE *NewParentPage, SBIT32 NewVersion ) { REGISTER SBIT32 Count; REGISTER SBIT16 NumberOfElements = (NewCache -> GetNumberOfElements()); REGISTER SBIT16 SizeOfElements = (NewCache -> GetSizeOfElements()); // // Create a page description. // Address = (CHAR*) NewAddress; PageSize = NewPageSize; Version = NewVersion; Allocated = 0; Available = NumberOfElements; FirstFree = 0; // // Set up the pointers to related classes. // Cache = NewCache; ParentPage = NewParentPage; // // Zero the bit vector. // for ( Count=0;Count < SizeOfElements;Count ++ ) { Vector[ Count ] = 0; } } /********************************************************************/ /* */ /* Compute the actual size. */ /* */ /* Almost all allocation sizes are derived from the associated */ /* caches. However, there are a few special pages that contain */ /* a single allocation of some weird size. */ /* */ /********************************************************************/ SBIT32 PAGE::ActualSize( VOID ) { return ( (ParentPage == ((CACHE*) GlobalRoot)) ? PageSize : (Cache -> GetAllocationSize()) ); } /********************************************************************/ /* */ /* Delete an allocation. */ /* */ /* We need to delete the memory allocation described by the */ /* parameters. However, as we are of an untrusting nature */ /* we carefully check the request to ensure it is valid. */ /* */ /********************************************************************/ BOOLEAN PAGE::Delete( SEARCH_PAGE *Details ) { // // We know that no one would deallocate memory // they had not previously allocated (yea - right). // So here we check for this case. // if ( (*Details -> VectorWord) & Details -> AllocationMask ) { // // Nasty: it is possible for the user to give us an // address that points to the middle of the element // to be freed instead of the beginning. This is no // problem for us but we have to ponder whether the // caller knew what they were doing. If this is the // case we fail the request. // if ( Details -> Found ) { // // We have found that the element is allocated // (as one might expect) so lets deallocate it // and update the various counters. // (*Details -> VectorWord) &= ~(Details -> AllocationMask | Details -> SubDivisionMask); // // We may need to push back the pointer to the // first free element. This will ensure that // we can quickly locate the freed element for // later so we can reuse it. // if ( FirstFree > Details -> VectorOffset ) { FirstFree = ((SBIT16) Details -> VectorOffset); } // // If the page was full and now has an empty // slot then add it to the bucket list so that // the free space can be found. // if ( Full() ) { Cache -> InsertInBucketList( this ); } // // Update the allocation information. // Allocated --; Available ++; // // If the page is now empty then delete // the page to conserve space. // if ( Empty() ) { // // We immediately delete empty pages // except at the top level where it is // under user control. // if ( ! Cache -> TopCache() ) { Cache -> DeletePage( this ); } else { REGISTER SBIT32 MaxFreePages = (Cache -> GetHeap() -> GetMaxFreePages()); ((BUCKET*) Cache) -> ReleaseSpace( MaxFreePages ); } } return True; } } return False; } /********************************************************************/ /* */ /* Delete all simple allocations. */ /* */ /* Although this routine may seem insignificant its effects are */ /* dramatic. When called this function deletes all the none */ /* sub-allocated elements and updates the control values. */ /* */ /********************************************************************/ VOID PAGE::DeleteAll( VOID ) { REGISTER BOOLEAN PageFull = Full(); // // Simply reset the allocation counts. // Allocated = 0; Available = (Cache -> GetNumberOfElements()); FirstFree = 0; // // We know that if this cache does not have any // child allocations that it is safe to simply // zero the bit vector. If not we have to do it // the long way. // if ( Cache -> GetNumberOfChildren() > 0 ) { REGISTER SBIT32 Count; REGISTER SBIT16 SizeOfElements = (Cache -> GetSizeOfElements()); // // We examine each word of the bit vector // and delete all the elements that are // not sub-divided into smaller sizes. // for ( Count=0;Count < SizeOfElements;Count ++ ) { REGISTER BIT32 *Word = & Vector[ Count ]; REGISTER BIT32 AllAllocations = ((*Word) & FullSearchMask); REGISTER BIT32 AllSubDivided = ((*Word) & (AllAllocations >> 1)); REGISTER BIT32 FinalMask = (AllSubDivided | (AllSubDivided << 1)); // // Delete all normal allocations. // (*Word) &= FinalMask; // // If the final mask is not zero then // we still have some allocations active. // We need to count these and update the // control information. // if ( FinalMask != 0 ) { REGISTER SBIT32 Total = 0; // // Count the allocations. // for ( /* void */;FinalMask != 0;FinalMask >>= OverheadBits ) { Total += (FinalMask & 1); } // // Update the control information. // Allocated = ((SBIT16) (Allocated + Total)); Available = ((SBIT16) (Available - Total)); } } } else { REGISTER SBIT32 Count; REGISTER SBIT16 SizeOfElements = (Cache -> GetSizeOfElements()); // // Zero the bit vector. // for ( Count=0;Count < SizeOfElements;Count ++ ) { Vector[ Count ] = 0; } } // // If the page was full and now has empty // slots then add it to the bucket list so // that the free space can be found. // if ( (PageFull) && (! Full()) ) { Cache -> InsertInBucketList( this ); } } /********************************************************************/ /* */ /* Find an allocation page. */ /* */ /* When we receive a request to delete an allocation we don't */ /* have a clue about where to find it. All we have is a hash */ /* table (see 'FIND') of allocated pages. So we mask off the */ /* low order bits of the address and try to find the top level */ /* external allocation. If this works we see if the area we */ /* are looking at has been sub-divided and if so we try the */ /* same trick again until we get the the origibal allocation */ /* page. */ /* */ /********************************************************************/ PAGE *PAGE::FindPage ( VOID *Memory, SEARCH_PAGE *Details, FIND *Find, BOOLEAN Recursive ) { // // We navigate through the pages trying to find // the allocation page associated with the address. // If we find a page that has no children then // we can assume we have arrived and exit early // unless the caller has requested all the realated // details. // if ( (Cache -> GetNumberOfChildren() > 0) || (Details != NULL) ) { AUTO BOOLEAN Found; REGISTER SBIT32 Displacement = ((SBIT32) (((CHAR*) Memory) - Address)); REGISTER SBIT32 ArrayOffset = (Cache -> ComputeOffset( Displacement,& Found )); REGISTER SBIT32 VectorOffset = (ArrayOffset / OverheadBitsPerWord); REGISTER SBIT32 WordOffset = (ArrayOffset - (VectorOffset * OverheadBitsPerWord)); REGISTER SBIT32 WordShift = (((OverheadBitsPerWord-1) - WordOffset) * OverheadBits); REGISTER BIT32 AllocationMask = (AllocatedMask << WordShift); REGISTER BIT32 SubDivisionMask = (SubDividedMask << WordShift); REGISTER BIT32 *VectorWord = & Vector[ VectorOffset ]; // // We will recursively search and find the target // address if requested otherwise we will just // return the details of the next level in the tree. // if ( (Recursive) && ((*VectorWord) & AllocationMask) && ((*VectorWord) & SubDivisionMask) ) { REGISTER PAGE *Page = (Cache -> FindChildPage( Memory,Find )); // // We have found the element and checked it. // So lets pass this request on to the // child page. However, there is a slight // chance of a race condition here. It // might be that the original page was // deleted and a new page is currently // being created. If this is the case // then we will not find the page in the // hash table so we just exit and fail the // call. // if ( Page != ((PAGE*) NULL) ) { return (Page -> FindPage( Memory,Details,Find,Recursive )); } else { return NULL; } } // // We see if the caller is interested in the // details relating to this address at the // current level in the tree. // if ( Details != NULL ) { // // We have computed the details relating // to this address at the current level // in the tree so load them into the // caller supplied structure. // Details -> Address = Memory; Details -> Cache = Cache; Details -> Found = Found; Details -> Page = this; Details -> AllocationMask = AllocationMask; Details -> SubDivisionMask = SubDivisionMask; Details -> VectorWord = VectorWord; Details -> ArrayOffset = ArrayOffset; Details -> VectorOffset = VectorOffset; Details -> WordShift = WordShift; } } return this; } /********************************************************************/ /* */ /* Multiple memory allocations. */ /* */ /* Allocate available memeory elements from a page. This is */ /* done by scanning the bit vector looking for unallocated */ /* slots. */ /* */ /********************************************************************/ BOOLEAN PAGE::MultipleNew( SBIT32 *Actual,VOID *Array[],SBIT32 Requested ) { // // We begin by making sure that there is at least // one element to allocate and that we need to // allocated at least one element. // if ( (! Full()) && ((*Actual) < Requested) ) { REGISTER SBIT16 SizeOfElements = (Cache -> GetSizeOfElements()); // // Search the bit vector from low addresses to // high addresses looking for a free slots. // We keep a pointer to the first word with // a free element in 'FirstFree'. Sometimes // the current word may be fully allocated so // we might need to scan. However, there can // never be any free memory before this point // in the bit vector. // for ( /* void */;FirstFree < SizeOfElements;FirstFree ++ ) { REGISTER SBIT32 ArrayOffset = (FirstFree * OverheadBitsPerWord); REGISTER BIT32 AvailableMask = WordSearchMask; REGISTER BIT32 *VectorWord = & Vector[ FirstFree ]; REGISTER SBIT32 WordOffset = 0; // // We scan the bit vector word at a time // looking for any free allocation slots. // while ( ((*VectorWord) & FullSearchMask) != FullSearchMask ) { REGISTER BIT32 Value = (*VectorWord); // // We know there is an at least one empty // slot availabale in the current word but // don't know which one We search for the // slot with the lowest address and stop // when we find it. // for ( /* void */; (AvailableMask & Value) != 0; AvailableMask >>= OverheadBits, WordOffset ++ ); // // We should never fail to find a free // allocation slot so if we do then the // heap must be corrupt. // if ( WordOffset < OverheadBitsPerWord ) { REGISTER SBIT32 VectorOffset = (ArrayOffset + WordOffset); // // We need to ensure that the element // we have chosen if not outside the // valid range for this page. // if ( VectorOffset < (Cache -> GetNumberOfElements()) ) { // // Update the allocation information. // Allocated ++; Available --; // // Turn on the bits indicating that this // element is in use. // (*VectorWord) |= AvailableMask; // // If the page is full we remove it // from the bucket list so we will no // longer look at it when we are // trying to find free space. // if ( Full() ) { Cache -> DeleteFromBucketList( this ); } // // Add the element to the allocation array // so it can be returned to the caller. // Array[ (Requested - ((*Actual) ++) - 1) ] = ( Cache -> ComputeAddress ( Address, VectorOffset ) ); // // When we have got what we need we exit. // if ( ((*Actual) >= Requested) ) { return True; } } else { break; } } else { Failure( "Bit vector is corrupt in MultipleNew" ); } } } } return ((*Actual) >= Requested); } /********************************************************************/ /* */ /* A single memory allocation. */ /* */ /* Allocate an available memeory element from the page. This */ /* is done by scanning the bit vector looking for unallocated */ /* slots. */ /* */ /********************************************************************/ VOID *PAGE::New( BOOLEAN SubDivided ) { // // We begin by making sure that there is at least // one element to allocate. // if ( ! Full() ) { REGISTER SBIT16 SizeOfElements = (Cache -> GetSizeOfElements()); // // Search the bit vector from low addresses to // high addresses looking for a free slot. // We keep a pointer to the first word with // a free element in 'FirstFree'. Sometimes // the current word may be fully allocated so // we might need to scan. However, there can // never be any free memory before this point // in the bit vector. // for ( /* void */;FirstFree < SizeOfElements;FirstFree ++ ) { REGISTER BIT32 *VectorWord = & Vector[ FirstFree ]; // // We scan the bit vector word at a time // looking for any free allocation slots. // if ( ((*VectorWord) & FullSearchMask) != FullSearchMask ) { REGISTER BIT32 AvailableMask = WordSearchMask; REGISTER BIT32 Value = (*VectorWord); REGISTER SBIT32 WordOffset = 0; // // We know there is an at least one empty // slot availabale in the current word but // don't know which one We search for the // slot with the lowest address and stop // when we find it. // for ( /* void */; (AvailableMask & Value) != 0; AvailableMask >>= OverheadBits, WordOffset ++ ); // // We should never fail to find a free // allocation slot so if we do then the // heap must be corrupt. // if ( WordOffset < OverheadBitsPerWord ) { REGISTER SBIT32 VectorOffset = ((FirstFree * OverheadBitsPerWord) + WordOffset); // // We need to ensure that the element // we have chosen if not outside the // valid range for this page. // if ( VectorOffset < (Cache -> GetNumberOfElements()) ) { // // Update the allocation information. // Allocated ++; Available --; // // Turn on the bit indicating that this // element is in use. If the allocation // is to be sub-divided then trun on this // bit as well. // (*VectorWord) |= ( AvailableMask | (SubDivided ? (AvailableMask >> 1) : 0) ); // // If the page is full we remove it // from the bucket list so we will no // longer look at it when we are // trying to find free space. // if ( Full() ) { Cache -> DeleteFromBucketList( this ); } // // Return the address of the allocated // memory to the caller. // return ( Cache -> ComputeAddress ( Address, VectorOffset ) ); } } else { Failure( "Bit vector is corrupt in New" ); } } } #ifdef DEBUGGING if ( ! Full() ) { Failure( "Available count corrupt in New" ); } #endif } return ((VOID*) AllocationFailure); } /********************************************************************/ /* */ /* Walk the heap. */ /* */ /* We have been asked to walk the heap. It is hard to know */ /* why anybody might want to do this given the rest of the */ /* functionality available. Nonetheless, we just do what is */ /* required to keep everyone happy. */ /* */ /********************************************************************/ BOOLEAN PAGE::Walk( SEARCH_PAGE *Details,FIND *Find ) { REGISTER BOOLEAN FreshPage = False; // // We have been handed the details of an allocation. // We need to walk along this allocation and find // the next non-subdivided allocation. do { // // We need to setup the heap walk if the address // is null so we skip the heap walk code. // if ( Details -> Address != NULL ) { REGISTER SBIT32 Count; REGISTER SBIT32 End = Details -> Cache -> GetNumberOfElements(); REGISTER SBIT32 Start = Details -> ArrayOffset; REGISTER PAGE *Page = Details -> Page; // // Walk the current page looking for a suitable // memory allocation to report to the user. When // we reach the end of the page we need to get // another page to walk. // for ( Count = ((FreshPage) ? 0 : 1); (Start + Count) < End; Count ++ ) { // // Compute the new address. // Details -> Address = ( Page -> Cache -> ComputeAddress ( Page -> Address, (Start + Count) ) ); // // Compute the new allocation details. // Page -> FindPage ( Details -> Address, Details, Find, False ); // // We skip all sub-divided allocations as they // will get reported elsewhere. // if (! ((*Details -> VectorWord) & Details -> SubDivisionMask) ) { return True; } } } // // Update the flag to show that we have // had to go and get a new page. // FreshPage = True; } while ( Details -> Cache -> Walk( Details,Find ) ); return False; } /********************************************************************/ /* */ /* Class destructor. */ /* */ /* Destory the current page structure. */ /* */ /********************************************************************/ PAGE::~PAGE( VOID ) { #ifdef DEBUGGING // // Destroy the page structure. // Address = NULL; PageSize = 0; ParentPage = NULL; Allocated = 0; Available = 0; FirstFree = 0; #endif // // We update the version number whenever a page is created // or destroyed. We use the version number to ensure that // a page has not been deleteed and/or recreated between // releasing one lock and claiming a another other lock. // Version ++; }