diff --git a/kernel/mem/pmm/allocator/bitmap_allocator.go b/kernel/mem/pmm/allocator/bitmap_allocator.go index a4cb686..7fcd817 100644 --- a/kernel/mem/pmm/allocator/bitmap_allocator.go +++ b/kernel/mem/pmm/allocator/bitmap_allocator.go @@ -1,6 +1,7 @@ package allocator import ( + "math" "reflect" "unsafe" @@ -17,6 +18,10 @@ var ( // primary allocator for reserving pages. FrameAllocator BitmapAllocator + errBitmapAllocOutOfMemory = &kernel.Error{Module: "bitmap_alloc", Message: "out of memory"} + errBitmapAllocFrameNotManaged = &kernel.Error{Module: "bitmap_alloc", Message: "frame not managed by this allocator"} + errBitmapAllocDoubleFree = &kernel.Error{Module: "bitmap_alloc", Message: "frame is already free"} + // The followning functions are used by tests to mock calls to the vmm package // and are automatically inlined by the compiler. reserveRegionFn = vmm.EarlyReserveRegion @@ -238,6 +243,60 @@ func (alloc *BitmapAllocator) printStats() { ) } +// AllocFrame reserves and returns a physical memory frame. An error will be +// returned if no more memory can be allocated. +func (alloc *BitmapAllocator) AllocFrame() (pmm.Frame, *kernel.Error) { + for poolIndex := 0; poolIndex < len(alloc.pools); poolIndex++ { + if alloc.pools[poolIndex].freeCount == 0 { + continue + } + + fullBlock := uint64(math.MaxUint64) + for blockIndex, block := range alloc.pools[poolIndex].freeBitmap { + if block == fullBlock { + continue + } + + // Block has at least one free slot; we need to scan its bits + for blockOffset, mask := 0, uint64(1<<63); mask > 0; blockOffset, mask = blockOffset+1, mask>>1 { + if block&mask != 0 { + continue + } + + alloc.pools[poolIndex].freeCount-- + alloc.pools[poolIndex].freeBitmap[blockIndex] |= mask + alloc.reservedPages++ + return alloc.pools[poolIndex].startFrame + pmm.Frame((blockIndex<<6)+blockOffset), nil + } + } + } + + return pmm.InvalidFrame, errBitmapAllocOutOfMemory +} + +// FreeFrame releases a frame previously allocated via a call to AllocFrame. +// Trying to release a frame not part of the allocator pools or a frame that +// is already marked as free will cause an error to be returned. +func (alloc *BitmapAllocator) FreeFrame(frame pmm.Frame) *kernel.Error { + poolIndex := alloc.poolForFrame(frame) + if poolIndex < 0 { + return errBitmapAllocFrameNotManaged + } + + relFrame := frame - alloc.pools[poolIndex].startFrame + block := relFrame >> 6 + mask := uint64(1 << (63 - (relFrame - block<<6))) + + if alloc.pools[poolIndex].freeBitmap[block]&mask == 0 { + return errBitmapAllocDoubleFree + } + + alloc.pools[poolIndex].freeBitmap[block] &^= mask + alloc.pools[poolIndex].freeCount++ + alloc.reservedPages-- + return nil +} + // earlyAllocFrame is a helper that delegates a frame allocation request to the // early allocator instance. This function is passed as an argument to // vmm.SetFrameAllocator instead of earlyAllocator.AllocFrame. The latter diff --git a/kernel/mem/pmm/allocator/bitmap_allocator_test.go b/kernel/mem/pmm/allocator/bitmap_allocator_test.go index 4af2d36..702e329 100644 --- a/kernel/mem/pmm/allocator/bitmap_allocator_test.go +++ b/kernel/mem/pmm/allocator/bitmap_allocator_test.go @@ -313,6 +313,80 @@ func TestBitmapAllocatorReserveEarlyAllocatorFrames(t *testing.T) { } } +func TestBitmapAllocatorAllocAndFreeFrame(t *testing.T) { + var alloc = BitmapAllocator{ + pools: []framePool{ + { + startFrame: pmm.Frame(0), + endFrame: pmm.Frame(7), + freeCount: 8, + // only the first 8 bits of block 0 are used + freeBitmap: make([]uint64, 1), + }, + { + startFrame: pmm.Frame(64), + endFrame: pmm.Frame(191), + freeCount: 128, + freeBitmap: make([]uint64, 2), + }, + }, + totalPages: 136, + } + + // Test Alloc + for poolIndex, pool := range alloc.pools { + for expFrame := pool.startFrame; expFrame <= pool.endFrame; expFrame++ { + got, err := alloc.AllocFrame() + if err != nil { + t.Fatalf("[pool %d] unexpected error: %v", err) + } + + if got != expFrame { + t.Errorf("[pool %d] expected allocated frame to be %d; got %d", poolIndex, expFrame, got) + } + } + + if alloc.pools[poolIndex].freeCount != 0 { + t.Errorf("[pool %d] expected free count to be 0; got %d", poolIndex, alloc.pools[poolIndex].freeCount) + } + } + + if alloc.reservedPages != alloc.totalPages { + t.Errorf("expected reservedPages to match totalPages(%d); got %d", alloc.totalPages, alloc.reservedPages) + } + + if _, err := alloc.AllocFrame(); err != errBitmapAllocOutOfMemory { + t.Fatalf("expected error errBitmapAllocOutOfMemory; got %v", err) + } + + // Test Free + expFreeCount := []uint32{8, 128} + for poolIndex, pool := range alloc.pools { + for frame := pool.startFrame; frame <= pool.endFrame; frame++ { + if err := alloc.FreeFrame(frame); err != nil { + t.Fatalf("[pool %d] unexpected error: %v", err) + } + } + + if alloc.pools[poolIndex].freeCount != expFreeCount[poolIndex] { + t.Errorf("[pool %d] expected free count to be %d; got %d", poolIndex, expFreeCount[poolIndex], alloc.pools[poolIndex].freeCount) + } + } + + if alloc.reservedPages != 0 { + t.Errorf("expected reservedPages to be 0; got %d", alloc.reservedPages) + } + + // Test Free errors + if err := alloc.FreeFrame(pmm.Frame(0)); err != errBitmapAllocDoubleFree { + t.Fatalf("expected error errBitmapAllocDoubleFree; got %v", err) + } + + if err := alloc.FreeFrame(pmm.Frame(0xbadf00d)); err != errBitmapAllocFrameNotManaged { + t.Fatalf("expected error errBitmapFrameNotManaged; got %v", err) + } +} + func TestAllocatorPackageInit(t *testing.T) { defer func() { mapFn = vmm.Map