windows-nt/Source/XPSP1/NT/base/mvdm/suballoc/suballoc.c

932 lines
23 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
suballoc.c
Abstract:
This module contains code for managing a paritially commited address
space. It handles allocation of chunks of memory smaller than the
commit granularity. It commits and decommits memory as needed using
the supplied function for committing and decommitting memory. The
structures used for tracking the address space are allocated outside
of the specified addresss space.
Author:
Dave Hastings (daveh) creation-date 21-Jan-1994
Notes:
Since this package does not actually access memory in the address space
it is managing, it will work as well with real linear addresses or
"Intel Addresses" such as would be encountered with the Insignia Emulator
on risc.
Revision History:
--*/
#include "suballcp.h"
PVOID
SAInitialize(
ULONG BaseAddress,
ULONG Size,
PSACOMMITROUTINE CommitRoutine,
PSACOMMITROUTINE DecommitRoutine,
PSAMEMORYMOVEROUTINE MemoryMoveRoutine
)
/*++
Routine Description:
This function performs initialization of the sub allocation package
for the specified addresss range. It allocates the data structures
necessary to track the allocations
Arguments:
BaseAddress -- Supplies the base address of the address space to
sub allocate.
Size -- Supplies the size in bytes of the address space to sub allocate.
CommitRoutine -- Supplies a pointer to the routine used to commit regions
of the address space.
Return Value:
If the function was successful it returns a pointer to the sub-allocation
data structures. Otherwise it returns NULL.
--*/
{
PSUBALLOCATIONDATA SubAlloc;
ULONG SASize;
ASSERT_STEPTHROUGH;
//
// Asserts to insure that everything is as we expect it to be
//
ASSERT(((COMMIT_GRANULARITY % SUBALLOC_GRANULARITY) == 0));
//
// Allocate the tracking structure
//
// SUBALLOCATIONDATA is declared with 1 uchar for the bitmap.
// this is the reason for subtracting one from the total size
// calculation.
//
SASize = sizeof(SUBALLOCATIONDATA)
+ (Size / SUBALLOC_GRANULARITY) / sizeof(UCHAR) - 1;
SubAlloc = malloc(SASize);
if (SubAlloc == NULL) {
return NULL;
}
//
// Initialize the structure
//
RtlZeroMemory(SubAlloc, SASize);
INIT_SUBALLOC_SIGNATURE(SubAlloc);
SubAlloc->BaseAddress = BaseAddress;
SubAlloc->Size = Size / SUBALLOC_GRANULARITY;
SubAlloc->CommitRoutine = CommitRoutine;
SubAlloc->DecommitRoutine = DecommitRoutine;
SubAlloc->MoveMemRoutine = MemoryMoveRoutine;
return SubAlloc;
}
BOOL
SAQueryFree(
PVOID SubAllocation,
PULONG FreeBytes,
PULONG LargestFreeBlock
)
/*++
Routine Description:
This routine returns the number of free bytes in the
sub allocated address space.
Arguments:
SubAllocation -- Supplies the pointer returned by SAInitialize
FreeBytes -- Returns the number of free bytes
Return Value:
TRUE -- if successful, and FreeBytes contains the number of free bytes.
FALSE otherwise
--*/
{
ULONG i, FreeCount;
PSUBALLOCATIONDATA SubAlloc;
ULONG TempLargest, LargestBlock;
ASSERT_STEPTHROUGH;
//
// Get a typed pointer
//
SubAlloc = SubAllocation;
//
// Make sure that we have what we think we do
//
ASSERT_SUBALLOC(SubAlloc);
//
// Count the free chunks and find largest block
//
FreeCount = 0;
LargestBlock = 0;
i = 0;
while (i < SubAlloc->Size) {
TempLargest = 0;
while (
(i < SubAlloc->Size) &&
(GET_BIT_FROM_CHAR_ARRAY(SubAlloc->Allocated, i) == 0)
){
FreeCount++;
TempLargest++;
i++;
}
if (TempLargest > LargestBlock) {
LargestBlock = TempLargest;
}
//
// Skip allocated blocks
//
while (
(i < SubAlloc->Size) &&
(GET_BIT_FROM_CHAR_ARRAY(SubAlloc->Allocated, i) == 1)
) {
i++;
}
}
*FreeBytes = FreeCount * SUBALLOC_GRANULARITY;
*LargestFreeBlock = LargestBlock * SUBALLOC_GRANULARITY;
return TRUE;
}
BOOL
SAAllocate(
PVOID SubAllocation,
ULONG Size,
PULONG Address
)
/*++
Routine Description:
This function allocates a portion of the address space described by
SubAllocation. If necessary, it will commit additional blocks.
Size is rounded up to the next higher multiple of SUBALLOC_GRANULARITY.
Arguments:
SubAllocation -- Supplies the pointer returned by SAInitialize.
Size -- Supplies the size in bytes of the region to allocate.
Address -- Returns the address of the region allocated.
Return Value:
TRUE if successful. If false is returned, no address is returned.
Notes:
Zero is a valid value for the returned address.
--*/
{
ULONG AllocateSize, i, CurrentChunk;
BOOL Done = FALSE;
PSUBALLOCATIONDATA SubAlloc;
BOOL Success;
ASSERT_STEPTHROUGH;
//
// Get a typed pointer. This allows us to avoid
// casting every time we access the pointer.
//
SubAlloc = SubAllocation;
ASSERT_SUBALLOC(SubAlloc);
//
// Round size and make into number of blocks
//
AllocateSize = ALLOC_ROUND(Size);
//
// Find a chunk that is free
//
// We need this loop in spite of the fact that we
// are keeping an index to the first free block.
// We update this pointer somewhat heuristically.
// If we allocate the first free block, we update
// the index to point past the block we allocated.
// We don't repeat the free scan however, so the
// index may actually point to an allocated block.
//
CurrentChunk = SubAlloc->FirstFree;
while (CurrentChunk < SubAlloc->Size) {
if (GET_BIT_FROM_CHAR_ARRAY(SubAlloc->Allocated, CurrentChunk) == 0) {
SubAlloc->FirstFree = CurrentChunk;
break;
}
CurrentChunk++;
}
//
// Find a block that is big enough
//
while (!Done && (CurrentChunk < SubAlloc->Size)){
//
// Search for a contiguous block large enough
//
for (i = 0; i < AllocateSize; i++){
//
// Insure we don't walk off the end of the data structure
//
if ((i + CurrentChunk) >= SubAlloc->Size){
CurrentChunk += i; // Satisfy termination condition
break;
}
//
// Check to see if this chunk is free
//
if (
GET_BIT_FROM_CHAR_ARRAY(
SubAlloc->Allocated,
i + CurrentChunk
)
== 0
){
continue;
} else {
//
// Chunk is not free, so advance the search
//
CurrentChunk += i + 1;
break;
}
}
//
// Check to see if we found a chunk
//
if (i == AllocateSize) {
Done = TRUE;
}
}
//
// If we found the chunk, commit it (if necessary) and mark it allocated
//
// N.B. It is important to commit it first, and mark it allocated last,
// because we use the allocated bits to determine if the chunk is
// committed. If all of the allocated bits are clear, the chunk
// is not commited yet.
//
if (Done) {
//
// Allocate and commit the memory
//
Success = AllocateChunkAt(
SubAlloc,
AllocateSize,
CurrentChunk,
FALSE
);
if (!Success) {
return FALSE;
}
*Address = BLOCK_INDEX_TO_ADDRESS(SubAlloc, CurrentChunk);
ASSERT((SubAlloc->BaseAddress <= *Address) &&
((SubAlloc->BaseAddress + SubAlloc->Size * SUBALLOC_GRANULARITY)
> *Address));
#if 0
{
char Buffer[80];
sprintf(Buffer, "SAAllocate: Allocating at address %lx\n", *Address);
OutputDebugString(Buffer);
}
#endif
return TRUE;
} else {
return FALSE;
}
}
BOOL
SAFree(
PVOID SubAllocation,
ULONG Size,
ULONG Address
)
/*++
Routine Description:
This routine frees a sub-allocated chunk of memory. If the
entire commited block (or blocks) that the specified chunk
belongs to are free, the chunks are decommitted. Address is
rounded down to the next lower SUBALLOC_GRANULARITY boundary.
size is rounded up to the next higher multiple of SUBALLOC_GRANULARITY.
Arguments:
SubAllocation -- Supplies the pointer returned by SAInitialize.
Size -- Supplies the size in bytes of the region to free.
Address -- Supplies the address of the region to free.
Return Value:
TRUE if successful.
Notes:
It is possible to free a different size at a particular
address than was allocated. This will not cause the
SubAllocation package any problems.
BUGBUG decommit error handling?
--*/
{
PSUBALLOCATIONDATA SubAlloc;
ULONG AllocatedSize, BaseBlock;
SubAlloc = SubAllocation;
ASSERT_SUBALLOC(SubAlloc);
//
// Make sure that the space to free is really ours
// (Entire block within range, and correctly aligned)
if (
(Address < SubAlloc->BaseAddress) ||
(Address >= (SubAlloc->BaseAddress + SubAlloc->Size * SUBALLOC_GRANULARITY)) ||
((Address + Size) > (SubAlloc->BaseAddress + SubAlloc->Size * SUBALLOC_GRANULARITY)) ||
(Address % SUBALLOC_GRANULARITY)
) {
return FALSE;
}
//
// Turn Address into Block #
//
BaseBlock = ADDRESS_TO_BLOCK_INDEX(SubAlloc, Address);
//
// Round up the size
//
AllocatedSize = ALLOC_ROUND(Size);
return FreeChunk(
SubAlloc,
AllocatedSize,
BaseBlock
);
}
BOOL
SAReallocate(
PVOID SubAllocation,
ULONG OriginalSize,
ULONG OriginalAddress,
ULONG NewSize,
PULONG NewAddress
)
/*++
Routine Description:
This routine reallocates a sub allocated block of memory.
The sizes are rounded up to the next SUBALLOC_GRANULARITY.
The Original address is rounded down to the next SUBALLOC_GRANULARITY
boundary. Only min(OriginalSize, NewSize) bytes of data are copied to
the new block. The block changed in place if possible.
The following is an enumation of the possible successful reallocs.
1. NewSize < OriginalSize
free block tail
2. NewSize > OriginalSize
a.) Sufficient freespace at OriginalAddress + OriginalSize
Allocate the space at the tail of the block
b.) Sufficient free space at OriginalAddress - size delta
Allocate the space at the beginning of the block, and
copy the data.
c.) Sufficient space elsewhere in the address space
Allocate the space, and copy the block.
If none of the above is true, the realloc fails. The above are
in order of preference.
Arguments:
SubAllocation -- Supplies the pointer returned by SAInitialize.
OriginalSize -- Supplies the old size in bytes of the block.
OriginalAddress -- Supplies the old address of the block.
NewSize -- Supplies the new size in bytes of the block.
NewAddress -- Returns the new address of the block.
Return Value:
True if successful. If unsucessful, no allocation is changed.
Notes:
If the caller does not supply the correct original size for the block,
some memory may be lost, and the block may be moved unnecessarily.
--*/
{
ULONG OriginalSizeBlock, NewSizeBlock, OriginalIndex;
ULONG AdditionalBlocks, Address;
BOOL Success;
PSUBALLOCATIONDATA SubAlloc;
SubAlloc = SubAllocation;
ASSERT_SUBALLOC(SubAlloc);
//
// Convert Sizes and address to blocks
//
OriginalSizeBlock = ALLOC_ROUND(OriginalSize);
NewSizeBlock = ALLOC_ROUND(NewSize);
OriginalIndex = ADDRESS_TO_BLOCK_INDEX(SubAlloc, OriginalAddress);
//
// Check to see if we are changing the size of the block
//
// N.B. Because we have rounded the numbers to an allocation
// boundary, the following test may succeed (correctly)
// even though OriginalSize != NewSize
//
if (OriginalSizeBlock == NewSizeBlock) {
*NewAddress = OriginalAddress;
return TRUE;
}
//
// Check to see if the block is getting smaller
//
if (OriginalSizeBlock > NewSizeBlock) {
//
// Free the tail of the block
//
Success = FreeChunk(
SubAlloc,
OriginalSizeBlock - NewSizeBlock,
OriginalIndex + NewSizeBlock
);
if (Success) {
*NewAddress = OriginalAddress;
return TRUE;
} else {
return FALSE;
}
}
//
// Try to allocate the space at the end of the block
//
AdditionalBlocks = NewSizeBlock - OriginalSizeBlock;
Success = AllocateChunkAt(
SubAlloc,
AdditionalBlocks,
OriginalIndex + OriginalSizeBlock,
TRUE
);
//
// If there was space, return success
//
if (Success) {
*NewAddress = OriginalAddress;
return TRUE;
}
//
// Try to allocate space at the beginning of the block
//
Success = AllocateChunkAt(
SubAlloc,
AdditionalBlocks,
OriginalIndex - AdditionalBlocks,
TRUE
);
if (Success) {
//
// Move the data
//
// N.B. We can't just call RtlMoveMemory,
// because we don't know the correspondence
// between the address space we manage, and
// real linear addresses. In addition, for
// risc NTVDM, some additional work may have
// to be done (such as flushing caches).
//
SubAlloc->MoveMemRoutine(
BLOCK_INDEX_TO_ADDRESS(
SubAlloc,
(OriginalIndex - AdditionalBlocks)
),
OriginalAddress,
OriginalSize
);
*NewAddress = BLOCK_INDEX_TO_ADDRESS(
SubAlloc,
(OriginalIndex - AdditionalBlocks)
);
return TRUE;
}
//
// Attempt to allocate a new block
//
Success = SAAllocate(
SubAlloc,
NewSize,
&Address
);
if (Success) {
//
// Move the data
//
// N.B. We could copy the data, but it would
// require one more function pointer.
//
SubAlloc->MoveMemRoutine(
Address,
OriginalAddress,
OriginalSize
);
SAFree(
SubAlloc,
OriginalSize,
OriginalAddress
);
//
// Indicate success
//
*NewAddress = Address;
return TRUE;
}
//
// All reallocation strategies failed.
//
return FALSE;
}
BOOL
AllocateChunkAt(
PSUBALLOCATIONDATA SubAlloc,
ULONG Size,
ULONG BlockIndex,
BOOLEAN CheckFree
)
/*++
Routine Description:
This routine attempts to allocate the specified chunk
of memory. It first checks to make sure that it is
free.
Arguments:
SubAlloc -- Supplies a pointer to the suballocation data
Size -- Supplies the size of the chunk to allocate
BlockIndex -- Supplies the index of the beginning of the block
to allocate
CheckFree -- Supplies an indication of whether to check and see
if the memory is free. If this routine is called from
SAAllocate, we know the memory is free.
Return Value:
True if successful
--*/
{
ULONG i;
if (CheckFree) {
//
// Verify that the memory is free
//
for (i = 0; i < Size; i++){
//
// Insure we don't walk off the end of the data structure
//
if ((i + BlockIndex) >= SubAlloc->Size){
break;
}
//
// Check to see if this chunk is free
//
if (
GET_BIT_FROM_CHAR_ARRAY(
SubAlloc->Allocated,
i + BlockIndex
)
== 0
){
continue;
} else {
//
// Chunk is not free
//
break;
}
}
//
// If the chunk is not free
//
if (i != Size) {
return FALSE;
}
}
//
// Commit the chunk
//
if (!CommitChunk(SubAlloc, BlockIndex, Size, SACommit)) {
return FALSE;
}
//
// Mark it as allocated
//
for (i = BlockIndex; i < BlockIndex + Size; i++) {
SET_BIT_IN_CHAR_ARRAY(SubAlloc->Allocated, i);
}
//
// Update the pointer to the first free block
//
if (BlockIndex == SubAlloc->FirstFree) {
SubAlloc->FirstFree += Size;
}
return TRUE;
}
BOOL
FreeChunk(
PSUBALLOCATIONDATA SubAlloc,
ULONG Size,
ULONG BlockIndex
)
/*++
Routine Description:
This routine actually marks the memory as free
and decommits it as necessary.
Arguments:
SubAlloc -- Supplies a pointer to the suballocation data
Size -- Supplies the size (in SUBALLOC_GRANULARITY) of the
region to free
BlockIndex -- Supplies the index of the begining of the region
(in SUBALLOC_GRANULARITY)
Return Value:
TRUE if successful.
--*/
{
SUBALLOCATIONDATA LocalSubAlloc;
ULONG CurrentBlock;
BOOL Success;
//
// Save a copy of the suballoc data
//
LocalSubAlloc = *SubAlloc;
//
// reset free pointer
//
if (BlockIndex < SubAlloc->FirstFree) {
SubAlloc->FirstFree = BlockIndex;
}
//
// Mark the region as free
//
// N.B. We mark the block as free before decommitting it, because
// the decommit code will use the allocated bits to determine which
// parts can be decommitted.
//
for (CurrentBlock = BlockIndex;
CurrentBlock < BlockIndex + Size;
CurrentBlock++
) {
CLEAR_BIT_IN_CHAR_ARRAY(SubAlloc->Allocated, CurrentBlock);
}
//
// Decommit the memory
//
Success = CommitChunk(SubAlloc, BlockIndex, Size, SADecommit);
if (!Success) {
*SubAlloc = LocalSubAlloc;
}
return Success;
}
BOOL
CommitChunk(
PSUBALLOCATIONDATA SubAlloc,
ULONG StartChunk,
ULONG Size,
COMMIT_ACTION Action
)
/*++
Routine Description:
This routine commits a chunk of memory. Part or all
of the specified chunk may already be commited.
Arguments:
SubAllocation -- Supplies a pointer to the suballocation data
StartChunk -- Supplies the relative start of the region to be
committed (in SUBALLOCATION_GRANULARITY)
Size -- Supplies the size of the chunk to be commited
(in SUBALLOCATION_GRANULARITY)
Return Value:
TRUE -- If the block was successfully committed (or already committed)
FALSE -- Otherwise
Notes:
This routine depends on the allocated bits in SubAlloc to determine
whether memory is committed. When memory is to be committed, CommitBlock
must be called before the Allocated bits are modified. When memory is
decommitted, the Allocated bits must be modified before CommitBlock is
called.
--*/
{
ULONG FirstBlock, LastBlock, CurrentBlock;
NTSTATUS Status;
ASSERT_STEPTHROUGH;
ASSERT_SUBALLOC(SubAlloc);
//
// Round Start down to next COMMIT_GRANULARITY and convert to block #
//
FirstBlock = (StartChunk * SUBALLOC_GRANULARITY) / COMMIT_GRANULARITY;
//
// Round StartChunk + size up to next COMMIT_GRANULARITY
//
LastBlock = ((StartChunk + Size) * SUBALLOC_GRANULARITY +
(COMMIT_GRANULARITY - 1)) / COMMIT_GRANULARITY;
for (
CurrentBlock = FirstBlock;
CurrentBlock < LastBlock;
CurrentBlock++
) {
//
// If the block is not committed, either commit it or decommit it,
// depending on the value of Action.
//
if (!IsBlockCommitted(SubAlloc, CurrentBlock)) {
if (Action == SACommit) {
Status = (SubAlloc->CommitRoutine)(
CurrentBlock * COMMIT_GRANULARITY + SubAlloc->BaseAddress,
COMMIT_GRANULARITY
);
} else if (Action == SADecommit) {
Status = (SubAlloc->DecommitRoutine)(
CurrentBlock * COMMIT_GRANULARITY + SubAlloc->BaseAddress,
COMMIT_GRANULARITY
);
}
if (Status != STATUS_SUCCESS) {
//
// Bugbug -- decommit any blocks committed here
//
return FALSE;
}
}
}
return TRUE;
}
BOOL
IsBlockCommitted(
PSUBALLOCATIONDATA SubAlloc,
ULONG Block
)
/*++
Routine Description:
This routine checks to see if a particular block of the
suballocation is committed.
Arguments:
SubAlloc -- Supplies a pointer to the suballocation data
Block -- Supplies the number of the block to check
Return Value:
TRUE -- if the block is committed
FALSE -- if the block is not committed
Notes:
The return value is based on the state of the bits in the
suballocation data, not on information from the NT memory
manager.
--*/
{
BOOL Committed = FALSE;
ULONG i;
ASSERT_STEPTHROUGH;
ASSERT_SUBALLOC(SubAlloc);
//
// Check the bits for each of the suballoc blocks in the
// commit block
//
for (i = 0; i < COMMIT_GRANULARITY / SUBALLOC_GRANULARITY; i++) {
//
// Check to see if this suballoc block is allocated
//
if (
GET_BIT_FROM_CHAR_ARRAY(
SubAlloc->Allocated,
i + Block * COMMIT_GRANULARITY / SUBALLOC_GRANULARITY
)
) {
Committed = TRUE;
break;
}
}
return Committed;
}