mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add FileSystemBuddyHeap
This commit is contained in:
parent
454f38f1bb
commit
8e1eb0d057
@ -94,6 +94,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
||||
2,3258,,AllocationFailureInProgramRegistryManagerA,In RegisterProgram allocating ProgramInfoNode
|
||||
2,3264,,AllocationFailureFatFileSystemA,In Initialize allocating ProgramInfoNode
|
||||
2,3280,,AllocationFailureInPartitionFileSystemCreatorA,In Create allocating PartitionFileSystemCore
|
||||
2,3294,,AllocationFailureInFileSystemBuddyHeapA,In Initialize allocating free areas array
|
||||
2,3295,,AllocationFailureInFileSystemBufferManagerA,In CacheHandleTable::Initialize allocating an entry buffer
|
||||
2,3312,,AllocationFailureInAesXtsFileA,In Initialize allocating FileStorage
|
||||
2,3313,,AllocationFailureInAesXtsFileB,In Initialize allocating AesXtsStorage
|
||||
2,3314,,AllocationFailureInAesXtsFileC,In Initialize allocating AlignmentMatchingStoragePooledBuffer
|
||||
|
|
16
src/LibHac/Fs/Buffers/Buffer.cs
Normal file
16
src/LibHac/Fs/Buffers/Buffer.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public readonly struct Buffer
|
||||
{
|
||||
public Memory<byte> Memory { get; }
|
||||
public Span<byte> Span => Memory.Span;
|
||||
|
||||
public Buffer(Memory<byte> buffer)
|
||||
{
|
||||
Memory = buffer;
|
||||
}
|
||||
}
|
||||
}
|
60
src/LibHac/Fs/Buffers/IBufferManager.cs
Normal file
60
src/LibHac/Fs/Buffers/IBufferManager.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
using CacheHandle = System.Int64;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public abstract class IBufferManager : IDisposable
|
||||
{
|
||||
public readonly struct BufferAttribute
|
||||
{
|
||||
public int Level { get; }
|
||||
|
||||
public BufferAttribute(int level)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
}
|
||||
|
||||
public const int BufferLevelMin = 0;
|
||||
|
||||
public (UIntPtr address, nuint size) AllocateBuffer(nuint size, BufferAttribute attribute) =>
|
||||
DoAllocateBuffer(size, attribute);
|
||||
|
||||
public void DeallocateBuffer(UIntPtr address, nuint size) => DoDeallocateBuffer(address, size);
|
||||
|
||||
public CacheHandle RegisterCache(UIntPtr address, nuint size, BufferAttribute attribute) =>
|
||||
DoRegisterCache(address, size, attribute);
|
||||
|
||||
public (UIntPtr address, nuint size) AcquireCache(CacheHandle handle) => DoAcquireCache(handle);
|
||||
public nuint GetTotalSize() => DoGetTotalSize();
|
||||
public nuint GetFreeSize() => DoGetFreeSize();
|
||||
public nuint GetTotalAllocatableSize() => DoGetTotalAllocatableSize();
|
||||
public nuint GetFreeSizePeak() => DoGetFreeSizePeak();
|
||||
public nuint GetTotalAllocatableSizePeak() => DoGetTotalAllocatableSizePeak();
|
||||
public nuint GetRetriedCount() => DoGetRetriedCount();
|
||||
public void ClearPeak() => DoClearPeak();
|
||||
|
||||
protected abstract (UIntPtr address, nuint size) DoAllocateBuffer(nuint size, BufferAttribute attribute);
|
||||
protected abstract void DoDeallocateBuffer(UIntPtr address, nuint size);
|
||||
protected abstract CacheHandle DoRegisterCache(UIntPtr address, nuint size, BufferAttribute attribute);
|
||||
protected abstract (UIntPtr address, nuint size) DoAcquireCache(CacheHandle handle);
|
||||
protected abstract nuint DoGetTotalSize();
|
||||
protected abstract nuint DoGetFreeSize();
|
||||
protected abstract nuint DoGetTotalAllocatableSize();
|
||||
protected abstract nuint DoGetFreeSizePeak();
|
||||
protected abstract nuint DoGetTotalAllocatableSizePeak();
|
||||
protected abstract nuint DoGetRetriedCount();
|
||||
protected abstract void DoClearPeak();
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -106,6 +106,10 @@ namespace LibHac.Fs
|
||||
public static Result.Base AllocationFailureFatFileSystemA => new Result.Base(ModuleFs, 3264);
|
||||
/// <summary>In Create allocating PartitionFileSystemCore<br/>Error code: 2002-3280; Inner value: 0x19a002</summary>
|
||||
public static Result.Base AllocationFailureInPartitionFileSystemCreatorA => new Result.Base(ModuleFs, 3280);
|
||||
/// <summary>In Initialize allocating free areas array<br/>Error code: 2002-3294; Inner value: 0x19bc02</summary>
|
||||
public static Result.Base AllocationFailureInFileSystemBuddyHeapA => new Result.Base(ModuleFs, 3294);
|
||||
/// <summary>In CacheHandleTable::Initialize allocating an entry buffer<br/>Error code: 2002-3295; Inner value: 0x19be02</summary>
|
||||
public static Result.Base AllocationFailureInFileSystemBufferManagerA => new Result.Base(ModuleFs, 3295);
|
||||
/// <summary>In Initialize allocating FileStorage<br/>Error code: 2002-3312; Inner value: 0x19e002</summary>
|
||||
public static Result.Base AllocationFailureInAesXtsFileA => new Result.Base(ModuleFs, 3312);
|
||||
/// <summary>In Initialize allocating AesXtsStorage<br/>Error code: 2002-3313; Inner value: 0x19e202</summary>
|
||||
|
595
src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs
Normal file
595
src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs
Normal file
@ -0,0 +1,595 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public unsafe class FileSystemBuddyHeap : IDisposable
|
||||
{
|
||||
private static readonly nuint BufferAlignment = (nuint)Unsafe.SizeOf<nuint>();
|
||||
private static readonly nuint BlockSizeMin = (nuint)(2 * Unsafe.SizeOf<nuint>());
|
||||
private const int OrderUpperLimit = 8 * sizeof(int) - 1;
|
||||
|
||||
private nuint BlockSize { get; set; }
|
||||
private int OrderMax { get; set; }
|
||||
private UIntPtr HeapStart { get; set; }
|
||||
private nuint HeapSize { get; set; }
|
||||
|
||||
private PageList* FreeLists { get; set; }
|
||||
private nuint TotalFreeSize { get; set; }
|
||||
private PageList* ExternalFreeLists { get; set; }
|
||||
private PageList[] InternalFreeLists { get; set; }
|
||||
|
||||
// Addition: Handle to allow initialization with a Memory<byte>
|
||||
private MemoryHandle PinnedMemoryHandle { get; set; }
|
||||
|
||||
private struct PageList
|
||||
{
|
||||
private PageEntry* FirstPageEntry { get; set; }
|
||||
private PageEntry* LastPageEntry { get; set; }
|
||||
private int EntryCount { get; set; }
|
||||
|
||||
public bool IsEmpty() => EntryCount == 0;
|
||||
public int GetSize() => EntryCount;
|
||||
|
||||
public PageEntry* GetFront() => FirstPageEntry;
|
||||
|
||||
public PageEntry* PopFront()
|
||||
{
|
||||
Assert.True(EntryCount > 0);
|
||||
|
||||
// Get the first entry.
|
||||
PageEntry* pageEntry = FirstPageEntry;
|
||||
|
||||
// Advance our list.
|
||||
FirstPageEntry = pageEntry->Next;
|
||||
pageEntry->Next = null;
|
||||
|
||||
// Decrement our count.
|
||||
EntryCount--;
|
||||
Assert.True(EntryCount >= 0);
|
||||
|
||||
// If this was our last page, clear our last entry.
|
||||
if (EntryCount == 0)
|
||||
{
|
||||
LastPageEntry = null;
|
||||
}
|
||||
|
||||
return pageEntry;
|
||||
}
|
||||
|
||||
public void PushBack(PageEntry* pageEntry)
|
||||
{
|
||||
Assert.True(pageEntry != null);
|
||||
|
||||
// If we're empty, we want to set the first page entry.
|
||||
if (IsEmpty())
|
||||
{
|
||||
FirstPageEntry = pageEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're not empty, so push the page to the back.
|
||||
Assert.True(LastPageEntry != pageEntry);
|
||||
LastPageEntry->Next = pageEntry;
|
||||
}
|
||||
|
||||
// Set our last page entry to be this one, and link it to the list.
|
||||
LastPageEntry = pageEntry;
|
||||
LastPageEntry->Next = null;
|
||||
|
||||
// Increment our entry count.
|
||||
EntryCount++;
|
||||
Assert.True(EntryCount > 0);
|
||||
}
|
||||
|
||||
public bool Remove(PageEntry* pageEntry)
|
||||
{
|
||||
Assert.True(pageEntry != null);
|
||||
|
||||
// If we're empty, we can't remove the page list.
|
||||
if (IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're going to loop over all pages to find this one, then unlink it.
|
||||
PageEntry* prevEntry = null;
|
||||
PageEntry* curEntry = FirstPageEntry;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Check if we found the page.
|
||||
if (curEntry == pageEntry)
|
||||
{
|
||||
if (curEntry == FirstPageEntry)
|
||||
{
|
||||
// If it's the first page, we just set our first.
|
||||
FirstPageEntry = curEntry->Next;
|
||||
}
|
||||
else if (curEntry == LastPageEntry)
|
||||
{
|
||||
// If it's the last page, we set our last.
|
||||
LastPageEntry = prevEntry;
|
||||
LastPageEntry->Next = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it's in the middle, we just unlink.
|
||||
prevEntry->Next = curEntry->Next;
|
||||
}
|
||||
|
||||
// Unlink this entry's next.
|
||||
curEntry->Next = null;
|
||||
|
||||
// Update our entry count.
|
||||
EntryCount--;
|
||||
Assert.True(EntryCount >= 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have no next page, we can't remove.
|
||||
if (curEntry->Next == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advance to the next item in the list.
|
||||
prevEntry = curEntry;
|
||||
curEntry = curEntry->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PageEntry
|
||||
{
|
||||
public PageEntry* Next;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FreeLists = null;
|
||||
ExternalFreeLists = null;
|
||||
InternalFreeLists = null;
|
||||
PinnedMemoryHandle.Dispose();
|
||||
}
|
||||
|
||||
public static int GetBlockCountFromOrder(int order)
|
||||
{
|
||||
Assert.True(0 <= order);
|
||||
Assert.True(order < OrderUpperLimit);
|
||||
return 1 << order;
|
||||
}
|
||||
|
||||
public static nuint QueryWorkBufferSize(int orderMax)
|
||||
{
|
||||
Assert.InRange(orderMax, 1, OrderUpperLimit);
|
||||
|
||||
var pageListSize = (nint)Unsafe.SizeOf<PageList>();
|
||||
uint pageListAlignment = (uint)Unsafe.SizeOf<nint>();
|
||||
const uint ulongAlignment = 8;
|
||||
|
||||
return (nuint)Alignment.AlignUpPow2(pageListSize * (orderMax + 1) + pageListAlignment, ulongAlignment);
|
||||
}
|
||||
|
||||
public static int QueryOrderMax(nuint size, nuint blockSize)
|
||||
{
|
||||
Assert.True(size >= blockSize);
|
||||
Assert.True(blockSize >= BlockSizeMin);
|
||||
Assert.True(BitUtil.IsPowerOfTwo(blockSize));
|
||||
|
||||
int blockCount = (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize);
|
||||
for (int order = 1; ; order++)
|
||||
{
|
||||
if (blockCount <= GetBlockCountFromOrder(order))
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, uint blockSize, int orderMax)
|
||||
{
|
||||
PinnedMemoryHandle = heapBuffer.Pin();
|
||||
|
||||
var address = (UIntPtr)PinnedMemoryHandle.Pointer;
|
||||
var size = (nuint)heapBuffer.Length;
|
||||
|
||||
return Initialize(address, size, blockSize, orderMax);
|
||||
}
|
||||
|
||||
public Result Initialize(UIntPtr address, nuint size, nuint blockSize, void* workBuffer, nuint workBufferSize)
|
||||
{
|
||||
return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize), workBuffer, workBufferSize);
|
||||
}
|
||||
|
||||
public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax, void* workBuffer,
|
||||
nuint workBufferSize)
|
||||
{
|
||||
// Note: Buffer size assert is done before adjusting for alignment
|
||||
Assert.True(workBufferSize >= QueryWorkBufferSize(orderMax));
|
||||
|
||||
uint pageListAlignment = (uint)Unsafe.SizeOf<nint>();
|
||||
var alignedWork = (void*)Alignment.AlignUpPow2((ulong)workBuffer, pageListAlignment);
|
||||
ExternalFreeLists = (PageList*)alignedWork;
|
||||
|
||||
return Initialize(address, size, blockSize, orderMax);
|
||||
}
|
||||
|
||||
public Result Initialize(UIntPtr address, nuint size, nuint blockSize)
|
||||
{
|
||||
return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize));
|
||||
}
|
||||
|
||||
public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax)
|
||||
{
|
||||
Assert.True(FreeLists == null);
|
||||
Assert.True(address != UIntPtr.Zero);
|
||||
Assert.True(Alignment.IsAlignedPow2(address.ToUInt64(), (uint)BufferAlignment));
|
||||
Assert.True(blockSize >= BlockSizeMin);
|
||||
Assert.True(BitUtil.IsPowerOfTwo(blockSize));
|
||||
Assert.True(size >= blockSize);
|
||||
Assert.True(orderMax > 0);
|
||||
Assert.True(orderMax < OrderUpperLimit);
|
||||
|
||||
// Set up our basic member variables
|
||||
BlockSize = blockSize;
|
||||
OrderMax = orderMax;
|
||||
HeapStart = address;
|
||||
HeapSize = size;
|
||||
|
||||
TotalFreeSize = 0;
|
||||
|
||||
// Determine page sizes
|
||||
nuint maxPageSize = BlockSize << OrderMax;
|
||||
nuint maxPageCount = (nuint)Alignment.AlignUp(HeapSize, (uint)maxPageSize) / maxPageSize;
|
||||
Assert.True(maxPageCount > 0);
|
||||
|
||||
// Setup the free lists
|
||||
if (ExternalFreeLists is not null)
|
||||
{
|
||||
Assert.Null(InternalFreeLists);
|
||||
FreeLists = ExternalFreeLists;
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalFreeLists = new PageList[OrderMax + 1];
|
||||
FreeLists = (PageList*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(InternalFreeLists));
|
||||
if (InternalFreeLists == null)
|
||||
return ResultFs.AllocationFailureInFileSystemBuddyHeapA.Log();
|
||||
}
|
||||
|
||||
// All but the last page region should go to the max order.
|
||||
for (nuint i = 0; i < maxPageCount - 1; i++)
|
||||
{
|
||||
PageEntry* pageEntry = GetPageEntryFromAddress(HeapStart + i * maxPageSize);
|
||||
FreeLists[orderMax].PushBack(pageEntry);
|
||||
}
|
||||
|
||||
TotalFreeSize += (nuint)FreeLists[orderMax].GetSize() * GetBytesFromOrder(orderMax);
|
||||
|
||||
// Allocate remaining space to smaller orders as possible.
|
||||
{
|
||||
nuint remaining = HeapSize - (maxPageCount - 1) * maxPageSize;
|
||||
nuint curAddress = (nuint)HeapStart - (maxPageCount - 1) * maxPageSize;
|
||||
Assert.True(Alignment.IsAlignedPow2(remaining, (uint)BlockSize));
|
||||
|
||||
do
|
||||
{
|
||||
// Determine what order we can use.
|
||||
int order = GetOrderFromBytes(remaining + 1);
|
||||
if (order < 0)
|
||||
{
|
||||
Assert.True(GetOrderFromBytes(remaining) == orderMax);
|
||||
order = OrderMax + 1;
|
||||
}
|
||||
|
||||
Assert.True(0 < order);
|
||||
Assert.True(order <= OrderMax + 1);
|
||||
|
||||
// Add to the correct free list.
|
||||
FreeLists[order - 1].PushBack(GetPageEntryFromAddress(curAddress));
|
||||
TotalFreeSize += GetBytesFromOrder(order - 1);
|
||||
|
||||
// Move on to the next order.
|
||||
nuint pageSize = GetBytesFromOrder(order - 1);
|
||||
curAddress += pageSize;
|
||||
remaining -= pageSize;
|
||||
} while (BlockSize <= remaining);
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void* AllocateByOrder(int order)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
// Get the page entry.
|
||||
PageEntry* pageEntry = GetFreePageEntry(order);
|
||||
|
||||
if (pageEntry != null)
|
||||
{
|
||||
// Ensure we're allocating an unlinked page.
|
||||
Assert.True(pageEntry->Next == null);
|
||||
|
||||
// Return the address for this entry.
|
||||
return (void*)GetAddressFromPageEntry(pageEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(void* pointer, int order)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
// Allow Free(null)
|
||||
if (pointer == null)
|
||||
return;
|
||||
|
||||
// Ensure the pointer is block aligned.
|
||||
Assert.True(Alignment.IsAlignedPow2((nuint)pointer - HeapStart, (uint)GetBlockSize()));
|
||||
|
||||
// Get the page entry.
|
||||
PageEntry* pageEntry = GetPageEntryFromAddress((UIntPtr)pointer);
|
||||
Assert.True(IsAlignedToOrder(pageEntry, order));
|
||||
|
||||
/* Reinsert into the free lists. */
|
||||
JoinBuddies(pageEntry, order);
|
||||
}
|
||||
|
||||
public nuint GetTotalFreeSize()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return TotalFreeSize;
|
||||
}
|
||||
|
||||
public nuint GetAllocatableSizeMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
|
||||
// The maximum allocatable size is a chunk from the biggest non-empty order.
|
||||
for (int order = GetOrderMax(); order >= 0; order--)
|
||||
{
|
||||
if (FreeLists[order].IsEmpty())
|
||||
{
|
||||
return GetBytesFromOrder(order);
|
||||
}
|
||||
}
|
||||
|
||||
// If all orders are empty, then we can't allocate anything.
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Dump()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetOrderFromBytes(nuint size)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return GetOrderFromBlockCount(GetBlockCountFromSize(size));
|
||||
}
|
||||
|
||||
public nuint GetBytesFromOrder(int order)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
return GetBlockSize() << order;
|
||||
}
|
||||
|
||||
public int GetOrderMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return OrderMax;
|
||||
}
|
||||
|
||||
public nuint GetBlockSize()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return BlockSize;
|
||||
}
|
||||
|
||||
// Not in original
|
||||
public int GetPageBlockCountMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return 1 << GetOrderMax();
|
||||
}
|
||||
|
||||
// Not in original
|
||||
public nuint GetPageSizeMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return (nuint)GetPageBlockCountMax() * GetBlockSize();
|
||||
}
|
||||
|
||||
private void DivideBuddies(PageEntry* pageEntry, int requiredOrder, int chosenOrder)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
Assert.True(requiredOrder >= 0);
|
||||
Assert.True(chosenOrder >= requiredOrder);
|
||||
Assert.True(chosenOrder <= GetOrderMax());
|
||||
|
||||
// Start at the end of the entry.
|
||||
nuint address = GetAddressFromPageEntry(pageEntry) + GetBytesFromOrder(chosenOrder);
|
||||
|
||||
for (int order = chosenOrder; order > requiredOrder; order--)
|
||||
{
|
||||
// For each order, subtract that order's size from the address to get the start of a new block.
|
||||
address -= GetBytesFromOrder(order - 1);
|
||||
PageEntry* dividedEntry = GetPageEntryFromAddress(address);
|
||||
|
||||
// Push back to the list.
|
||||
FreeLists[order - 1].PushBack(dividedEntry);
|
||||
TotalFreeSize += GetBytesFromOrder(order - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void JoinBuddies(PageEntry* pageEntry, int order)
|
||||
{
|
||||
Assert.True(pageEntry != null);
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
PageEntry* curEntry = pageEntry;
|
||||
int curOrder = order;
|
||||
|
||||
while (curOrder < GetOrderMax())
|
||||
{
|
||||
// Get the buddy page.
|
||||
PageEntry* buddyEntry = GetBuddy(curEntry, curOrder);
|
||||
|
||||
// Check whether the buddy is in the relevant free list.
|
||||
if (buddyEntry != null && FreeLists[curOrder].Remove(buddyEntry))
|
||||
{
|
||||
TotalFreeSize -= GetBytesFromOrder(curOrder);
|
||||
|
||||
// Ensure we coalesce with the correct buddy when page is aligned
|
||||
if (!IsAlignedToOrder(curEntry, curOrder + 1))
|
||||
{
|
||||
curEntry = buddyEntry;
|
||||
}
|
||||
|
||||
curOrder++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Buddy isn't in the free list, so we can't coalesce.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the coalesced entry into the free list.
|
||||
FreeLists[curOrder].PushBack(curEntry);
|
||||
TotalFreeSize += GetBytesFromOrder(curOrder);
|
||||
}
|
||||
|
||||
private PageEntry* GetBuddy(PageEntry* pageEntry, int order)
|
||||
{
|
||||
Assert.True(pageEntry != null);
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
nuint address = GetAddressFromPageEntry(pageEntry);
|
||||
nuint offset = (nuint)GetBlockCountFromOrder(order) * GetBlockSize();
|
||||
|
||||
if (IsAlignedToOrder(pageEntry, order + 1))
|
||||
{
|
||||
// If the page entry is aligned to the next order,
|
||||
// return the buddy block to the right of the current entry.
|
||||
return address + offset < HeapStart + HeapSize ? GetPageEntryFromAddress(address + offset) : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the page entry isn't aligned, return the buddy block to the left of the current entry.
|
||||
return HeapStart <= address - offset ? GetPageEntryFromAddress(address - offset) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private PageEntry* GetFreePageEntry(int order)
|
||||
{
|
||||
Assert.True(order >= 0);
|
||||
Assert.True(order <= GetOrderMax());
|
||||
|
||||
// Try orders from low to high until we find a free page entry.
|
||||
for (int curOrder = order; curOrder <= GetOrderMax(); curOrder++)
|
||||
{
|
||||
ref PageList freeList = ref FreeLists[curOrder];
|
||||
if (!freeList.IsEmpty())
|
||||
{
|
||||
// The current list isn't empty, so grab an entry from it.
|
||||
PageEntry* pageEntry = freeList.PopFront();
|
||||
Assert.True(pageEntry != null);
|
||||
|
||||
// Update size bookkeeping.
|
||||
TotalFreeSize -= GetBytesFromOrder(curOrder);
|
||||
|
||||
// If we allocated more memory than needed, free the unneeded portion.
|
||||
DivideBuddies(pageEntry, order, curOrder);
|
||||
Assert.True(pageEntry->Next == null);
|
||||
|
||||
// Return the newly-divided entry.
|
||||
return pageEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to find a free page.
|
||||
return null;
|
||||
}
|
||||
|
||||
private int GetOrderFromBlockCount(int blockCount)
|
||||
{
|
||||
Assert.True(blockCount >= 0);
|
||||
|
||||
// Return the first order with a big enough block count.
|
||||
for (int order = 0; order <= GetOrderMax(); ++order)
|
||||
{
|
||||
if (blockCount <= GetBlockCountFromOrder(order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int GetBlockCountFromSize(nuint size)
|
||||
{
|
||||
nuint blockSize = GetBlockSize();
|
||||
return (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize);
|
||||
}
|
||||
|
||||
private UIntPtr GetAddressFromPageEntry(PageEntry* pageEntry)
|
||||
{
|
||||
var address = new UIntPtr(pageEntry);
|
||||
|
||||
Assert.True((nuint)HeapStart <= address);
|
||||
Assert.True(address < HeapStart + HeapSize);
|
||||
Assert.True(Alignment.IsAlignedPow2((nuint)address - HeapStart, (uint)GetBlockSize()));
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
private PageEntry* GetPageEntryFromAddress(UIntPtr address)
|
||||
{
|
||||
Assert.True((nuint)HeapStart <= address);
|
||||
Assert.True(address < HeapStart + HeapSize);
|
||||
|
||||
ulong blockStart = (ulong)HeapStart +
|
||||
Alignment.AlignDownPow2((nuint)address - HeapStart, (uint)GetBlockSize());
|
||||
return (PageEntry*)blockStart;
|
||||
}
|
||||
|
||||
private int GetIndexFromPageEntry(PageEntry* pageEntry)
|
||||
{
|
||||
var address = (nuint)pageEntry;
|
||||
|
||||
Assert.True(HeapStart <= address);
|
||||
Assert.True(address < HeapStart + HeapSize);
|
||||
Assert.True(Alignment.IsAlignedPow2(address - HeapStart, (uint)GetBlockSize()));
|
||||
|
||||
return (int)((address - HeapStart) / GetBlockSize());
|
||||
}
|
||||
|
||||
private bool IsAlignedToOrder(PageEntry* pageEntry, int order)
|
||||
{
|
||||
return Alignment.IsAlignedPow2(GetIndexFromPageEntry(pageEntry), (uint)GetBlockCountFromOrder(order));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user