//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: PhysicalHeap.cs - a simple heap // // Note: // // This heap manages physical memory so as to be able to satisfy requests // for blocks of contiguous memory. This is used for the restricted // purpose of allocating I/O memory to processes that need, for example, // to perform DMA to hardware. // // The implementation of the heap assumes that the contiguous range of // memory that it is pointed to at initialization is always mapped. // using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.GCs; using Microsoft.Singularity; namespace Microsoft.Singularity.Memory { [NoCCtor] [CLSCompliant(false)] public struct PhysicalHeap { ///////////////////////////////////// // CONSTANTS ///////////////////////////////////// // We maintain a table of our heap's available pages, one 16-bit // half-word per page. The 16 bits simply indicate the PID of the // process owning the page, or zero if the page is free. private const uint BytesPerTableEntry = 2; private const ushort FreePage = 0x0000; private const ushort KernelPage = 0x0001; ///////////////////////////////////// // FIELDS ///////////////////////////////////// // When allocating block of this size or smaller, we will // start from the tail of the freeList instead of the head. // The freeList is kept sorted by size. private const uint SmallSize = MemoryManager.PageSize; // The start and end of heap pages *that can be used for data* // (the page table resides immediately *before* this range!) private UIntPtr startAddr; private UIntPtr heapLimit; // A count of the *data pages* in the heap (doesn't count the // pages consumed by the page table) private UIntPtr pageCount; // The address of the page table private unsafe ushort* pageTable; // Protects access to the heap private SpinLock heapLock; // A chained list of free blocks, sorted by block size. private FreeList freeList; ///////////////////////////////////// // PUBLIC METHODS ///////////////////////////////////// public unsafe PhysicalHeap(UIntPtr start, UIntPtr limit) { DebugStub.Assert(MemoryManager.IsPageAligned(start)); DebugStub.Assert(MemoryManager.IsPageAligned(limit)); // Note that this wastes a little bit of memory by allocating // table space to describe page-table memory! UIntPtr numPages = MemoryManager.PagesFromBytes(limit - start); UIntPtr bytesForTable = numPages * BytesPerTableEntry; bytesForTable = MemoryManager.PagePad(bytesForTable); UIntPtr pagesForTable = MemoryManager.PagesFromBytes(bytesForTable); pageCount = numPages - pagesForTable; startAddr = start + bytesForTable; heapLimit = limit; pageTable = (ushort*)start; heapLock = new SpinLock(SpinLock.Types.PhysicalHeap); // The entire heap is free to start out with freeList = new FreeList(); // Initialize the page table SetPages(startAddr, pageCount, FreePage); fixed (PhysicalHeap* thisPtr = &this) { freeList.CreateAndInsert(thisPtr, startAddr, pageCount); } CheckConsistency(); } // We always hand out blocks consisting of complete // pages of memory. [NoStackLinkCheckTrans] public unsafe UIntPtr Allocate(UIntPtr bytes, Process process) { return Allocate(0, bytes, 0, process); } [NoStackLinkCheckTrans] public unsafe UIntPtr Allocate(UIntPtr limitAddr, UIntPtr bytes, UIntPtr alignment, Process process) { ushort tag = process != null ? (ushort)process.ProcessId : KernelPage; UIntPtr blockPtr; bool iflag = Lock(); if (alignment < MemoryManager.PageSize) { alignment = MemoryManager.PageSize; } try { CheckConsistency(); // Find an appropriately-sized block FreeNode *foundNode = freeList.FindGoodFit(bytes, alignment); if (foundNode == null) { return UIntPtr.Zero; } DebugStub.Assert(MemoryManager.IsPageAligned((UIntPtr)foundNode)); // Respect alignment within the node blockPtr = MemoryManager.Pad((UIntPtr)foundNode, alignment); UIntPtr alignedSize = bytes + SpaceToAlign((UIntPtr)foundNode, alignment); DebugStub.Assert(alignedSize == (blockPtr + bytes) - (UIntPtr)foundNode); DebugStub.Assert(foundNode->bytes >= alignedSize); // Give back any extra pages UIntPtr numPages = MemoryManager.PagesFromBytes(MemoryManager.PagePad(alignedSize)); UIntPtr chunkPages = MemoryManager.PagesFromBytes(foundNode->bytes); DebugStub.Assert(chunkPages >= numPages); UIntPtr extraPages = chunkPages - numPages; if (extraPages > 0) { // Give back the extra memory UIntPtr remainderPtr = (UIntPtr)foundNode + (numPages * MemoryManager.PageSize); fixed (PhysicalHeap* thisPtr = &this) { freeList.CreateAndInsert(thisPtr, remainderPtr, extraPages); } } SetPages((UIntPtr)foundNode, numPages, tag); CheckConsistency(); } finally { Unlock(iflag); } // TODO: Flexible limit specification not yet implemented if (limitAddr > UIntPtr.Zero) { DebugStub.Assert(blockPtr < limitAddr); } return blockPtr; } [NoStackLinkCheckTrans] public unsafe void Free(UIntPtr addr, UIntPtr bytes, Process process) { if (addr == UIntPtr.Zero) { // Silently accept freeing null return; } // We always hand out memory in page-size chunks, so round up what // the caller thinks their block size is bytes = MemoryManager.PagePad(bytes); // Our blocks always start on page boundaries DebugStub.Assert(MemoryManager.IsPageAligned(addr)); ushort tag = process != null ? (ushort)process.ProcessId : KernelPage; bool iflag = Lock(); try { CheckConsistency(); UIntPtr numPages = MemoryManager.PagesFromBytes(bytes); VerifyOwner(addr, numPages, tag); FreeNode *nextBlock = null; FreeNode *prevBlock = null; if ((addr + bytes) < heapLimit) { fixed (PhysicalHeap* thisPtr = &this) { nextBlock = FreeNode.GetNodeAt(thisPtr, addr + bytes); } } if (addr > startAddr) { fixed (PhysicalHeap* thisPtr = &this) { prevBlock = LastNode.GetNodeFromLast(thisPtr, addr - MemoryManager.PageSize); } } // Don't mark pages as free until we're done discovering the // previous and next blocks, or the attempt to discover // the previous and next blocks gets confused to find itself // adjacent to a free block. SetPages(addr, numPages, FreePage); // Coalesce with the preceding region if (prevBlock != null) { addr = (UIntPtr)prevBlock; bytes += prevBlock->bytes; freeList.Remove(prevBlock); } // Coalesce with the following region if (nextBlock != null) { bytes += nextBlock->bytes; freeList.Remove(nextBlock); } // Blocks should always be integral numbers of pages DebugStub.Assert(MemoryManager.IsPageAligned(bytes)); // Create the free node. fixed (PhysicalHeap* thisPtr = &this) { freeList.CreateAndInsert(thisPtr, addr, bytes / MemoryManager.PageSize); } CheckConsistency(); } finally { Unlock(iflag); } } ///////////////////////////////////// // PRIVATE METHODS ///////////////////////////////////// private unsafe UIntPtr FreePageCountFromList() { UIntPtr retval = 0; FreeNode* entry = freeList.head; FreeNode* prev = null; while (entry != null) { DebugStub.Assert(MemoryManager.IsPageAligned(entry->bytes)); retval += MemoryManager.PagesFromBytes(entry->bytes); DebugStub.Assert(entry->prev == prev); prev = entry; entry = entry->next; } return retval; } private unsafe void CheckConsistency() { #if SELF_TEST UIntPtr freePagesByTable = 0; for (UIntPtr i = 0; i < pageCount; i++) { UIntPtr pageAddr = startAddr + (MemoryManager.PageSize * i); if (PageWord(i) == FreePage) { // Validate this block's free information FreeNode* thisBlock = (FreeNode*)pageAddr; DebugStub.Assert(thisBlock->signature == FreeNode.Signature); if (thisBlock->last != null) { // Multi-page free block; validate and skip ahead DebugStub.Assert(thisBlock->last->node == thisBlock); DebugStub.Assert(thisBlock->last->signature == LastNode.Signature); UIntPtr numBytes = (UIntPtr)thisBlock->last - (UIntPtr)pageAddr + MemoryManager.PageSize; DebugStub.Assert(numBytes == thisBlock->bytes); DebugStub.Assert(MemoryManager.IsPageAligned(numBytes)); UIntPtr numPages = MemoryManager.PagesFromBytes(numBytes); for (UIntPtr j = i; j < i + numPages; j++) { DebugStub.Assert(PageWord(j) == FreePage); } i += numPages - 1; freePagesByTable += numPages; } else { // Single-page free block if (i != pageCount - 1) { DebugStub.Assert(PageWord(i + 1) != FreePage); } freePagesByTable++; } } } // Now make sure all free pages are accounted for UIntPtr freePagesByList = FreePageCountFromList(); DebugStub.Assert(freePagesByList == freePagesByTable); #endif } [NoStackLinkCheckTrans] private unsafe void SetPages(UIntPtr addr, UIntPtr pageCount, ushort tag) { UIntPtr idx = PageIndex(addr); ushort* descriptor = pageTable + (ulong)idx; while (pageCount > 0) { *descriptor++ = tag; pageCount--; } } [NoStackLinkCheckTrans] private unsafe void VerifyOwner(UIntPtr addr, UIntPtr pages, ushort tag) { UIntPtr idx = PageIndex(addr); for (UIntPtr i = 0; i < pages; i++) { DebugStub.Assert ((*(pageTable + (ulong)idx + (ulong)i)) == tag, "PhysicalHeap.VerifyOwner addr={0} i={1} tag={2}", __arglist(addr, i, tag)); } } [Inline] private UIntPtr PageIndex(UIntPtr pageAddr) { DebugStub.Assert(MemoryManager.IsPageAligned(pageAddr)); DebugStub.Assert(pageAddr >= startAddr && pageAddr <= heapLimit, "PhysicalHeap.PageIndex pageAddr = {0:x} Range = {1:x} ... {2:x}", __arglist(pageAddr, startAddr, heapLimit)); DebugStub.Assert(pageAddr < heapLimit); return MemoryManager.PagesFromBytes(pageAddr - startAddr); } [Inline] private unsafe ushort PageWord(UIntPtr pageIdx) { DebugStub.Assert(pageIdx < pageCount, "PhysicalHeap.PageWord pageIdx {0} >= pageCount {1}", __arglist(pageIdx, pageCount)); return *(pageTable + (ulong)pageIdx); } [NoStackLinkCheckTrans] private bool Lock() { bool enabled = Processor.DisableInterrupts(); #if SINGULARITY_MP heapLock.Acquire(Thread.CurrentThread); #endif // SINGULARITY_MP return enabled; } [NoStackLinkCheckTrans] private void Unlock(bool iflag) { #if SINGULARITY_MP heapLock.Release(Thread.CurrentThread); #endif // SINGULARITY_MP Processor.RestoreInterrupts(iflag); } ///////////////////////////////////// // HELPER CLASSES ///////////////////////////////////// [StructLayout(LayoutKind.Sequential)] private struct FreeList { internal unsafe FreeNode* head; internal unsafe FreeNode* tail; [NoStackLinkCheckTrans] internal unsafe void CreateAndInsert(PhysicalHeap* inHeap, UIntPtr addr, UIntPtr pages) { DebugStub.Assert(MemoryManager.IsPageAligned(addr), "PhysicalHeap.CreateAndInsert non page-aligned addr={0:x}", __arglist(addr)); FreeNode * node = FreeNode.Create(inHeap, addr, pages); DebugStub.Assert(MemoryManager.IsPageAligned(node->bytes), "PhysicalHeap.CreateAndInsert non page-sized node->bytes={0:x}", __arglist(node->bytes)); InsertBySize(node); } internal unsafe void Remove(FreeNode* node) { if (node->prev != null) { node->prev->next = node->next; } else { DebugStub.Assert(head == node); head = node->next; } if (node->next != null) { node->next->prev = node->prev; } else { DebugStub.Assert(tail == node); tail = node->prev; } node->Remove(); } private unsafe void InsertAsHead(FreeNode* node) { if (head != null) { head->prev = node; } node->next = head; head = node; } private unsafe void InsertAsTail(FreeNode* node) { if (tail != null) { tail->next = node; } node->prev = tail; tail = node; } private unsafe void InsertAsNext(FreeNode* target, FreeNode* node) { DebugStub.Assert(target != null); if (target == tail) { InsertAsTail(node); } else { node->next = target->next; node->prev = target; target->next = node; if (node->next != null) { node->next->prev = node; } } } private unsafe void InsertAsPrev(FreeNode* target, FreeNode* node) { DebugStub.Assert(target != null); if (target == head) { InsertAsHead(node); } else { node->prev = target->prev; node->next = target; target->prev = node; if (node->prev != null) { node->prev->next = node; } } } [NoStackLinkCheckTrans] internal unsafe void InsertBySize(FreeNode* node) { if (head == null) { // Empty list DebugStub.Assert(tail == null); head = node; tail = node; } else { if (node->bytes <= SmallSize) { // If the size is pretty small, we insert from the back of the list... for (FreeNode *step = tail; step != null; step = step->prev) { if (step->bytes >= node->bytes) { InsertAsNext(step, node); return; } } // Every entry in the list is smaller than us. Therefore, we're the // new head. InsertAsHead(node); } else { // Insert a region into the list by size. for (FreeNode *step = head; step != null; step = step->next) { if (step->bytes <= node->bytes) { InsertAsPrev(step, node); return; } } // Every entry in the list is larger than us. Therefore, we're // the new tail. InsertAsTail(node); } } } [NoStackLinkCheckTrans] internal unsafe FreeNode * FindGoodFit(UIntPtr bytes, UIntPtr alignment) { DebugStub.Assert(alignment >= MemoryManager.PageSize); // If it is a small allocation, we try to accelerate the search. if (bytes <= SmallSize) { for (FreeNode *node = tail; node != null; node = node->prev) { UIntPtr alignedSize = SpaceToAlign((UIntPtr)node, alignment) + bytes; if (alignedSize <= node->bytes) { Remove(node); return node; } } return null; } else { // First try to find a region closest in size to bytes... FreeNode *best = null; for (FreeNode *node = head; node != null; node = node->next) { UIntPtr alignedSize = SpaceToAlign((UIntPtr)node, alignment) + bytes; if (alignedSize <= node->bytes) { // If we find a candidate, remember it. best = node; if (bytes == node->bytes) { // Stop if it is the ideal region. break; } } else { // Stop if we've reached smaller regions break; } } if (best != null) { Remove(best); } return best; } } } [Inline] private static UIntPtr SpaceToAlign(UIntPtr data, UIntPtr size) { return MemoryManager.Pad(data, size) - data; } [StructLayout(LayoutKind.Sequential)] private struct FreeNode { internal const uint Signature = 0x22aaaa22; internal const uint Removed = 0x11eeee11; internal uint signature; internal unsafe FreeNode * prev; internal unsafe FreeNode * next; internal unsafe LastNode * last; internal UIntPtr bytes; [NoStackLinkCheckTrans] internal static unsafe FreeNode * Create(PhysicalHeap* inHeap, UIntPtr addr, UIntPtr pages) { DebugStub.Assert(addr >= inHeap->startAddr); DebugStub.Assert((addr + (pages * MemoryManager.PageSize)) <= inHeap->heapLimit); FreeNode * node = (FreeNode *)addr; // This had better be a free page in the main table DebugStub.Assert(inHeap->PageWord(inHeap->PageIndex(addr)) == FreePage, "Creating a FreeNode for non-free page {0:x}", __arglist(addr)); node->signature = FreeNode.Signature; node->bytes = pages * MemoryManager.PageSize; node->prev = null; node->next = null; node->last = null; if (pages > 1) { node->last = LastNode.Create(inHeap, addr, node); } return node; } [NoStackLinkCheckTrans] internal static unsafe FreeNode * GetNodeAt(PhysicalHeap* inHeap, UIntPtr addr) { UIntPtr idx = inHeap->PageIndex(addr); if (inHeap->PageWord(idx) != FreePage) { // This address designates a page that is in use. return null; } if ((idx > 0) && (inHeap->PageWord(idx - 1) == FreePage)) { // This address is in the middle of a free block; it does // not designate the beginning of a free block. return null; } DebugStub.Assert(((FreeNode*)addr)->signature == Signature); return (FreeNode*)addr; } internal unsafe void Remove() { signature = Removed; prev = null; next = null; if (last != null) { last->Remove(); } } } [StructLayout(LayoutKind.Sequential)] private struct LastNode { internal const uint Signature = 0xaa2222aa; internal const uint Removed = 0xee1111ee; internal uint signature; internal unsafe FreeNode * node; [NoStackLinkCheckTrans] internal static unsafe LastNode * Create(PhysicalHeap* inHeap, UIntPtr addr, FreeNode *node) { LastNode *last = (LastNode *)(addr + node->bytes - MemoryManager.PageSize); DebugStub.Assert((UIntPtr)last >= inHeap->startAddr); DebugStub.Assert((UIntPtr)last <= inHeap->heapLimit); DebugStub.Assert(MemoryManager.IsPageAligned((UIntPtr)last)); last->signature = LastNode.Signature; last->node = node; return last; } [NoStackLinkCheckTrans] internal unsafe void Remove() { signature = Removed; node = null; } [NoStackLinkCheckTrans] internal static unsafe FreeNode * GetNodeFromLast(PhysicalHeap* inHeap, UIntPtr addr) { UIntPtr idx = inHeap->PageIndex(addr); // addr must specify a free page if (inHeap->PageWord(idx) != FreePage) { // addr does not specify a LastNode return null; } // addr must specify a page such that the next page (if there // is one) is not free if ((idx != inHeap->pageCount - 1) && (inHeap->PageWord(idx + 1) == FreePage)) { return null; } if (idx == 0) { // This is a one-page block DebugStub.Assert(((FreeNode*)addr)->signature == FreeNode.Signature); return (FreeNode*)addr; } // If the preceding page is free, then addr specifies // the last page in a multi-page block, otherwise it // specifies the only page in a one-page block. if (inHeap->PageWord(idx - 1) == FreePage) { DebugStub.Assert(((LastNode*)addr)->signature == Signature); return ((LastNode *)addr)->node; } else { DebugStub.Assert(((FreeNode*)addr)->signature == FreeNode.Signature); return (FreeNode*)addr; } } } } }