mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Move bootmem allocator in a sub-pkg
This commit is contained in:
113
kernel/mem/pmm/allocator/bootmem.go
Normal file
113
kernel/mem/pmm/allocator/bootmem.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||
"github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||
)
|
||||
|
||||
var (
|
||||
// EarlyAllocator points to a static instance of the boot memory allocator
|
||||
// which is used to bootstrap the kernel before initializing a more
|
||||
// advanced memory allocator.
|
||||
EarlyAllocator BootMemAllocator
|
||||
|
||||
errBootAllocUnsupportedPageSize = &kernel.Error{Module: "boot_mem_alloc", Message: "allocator only support allocation requests of order(0)"}
|
||||
errBootAllocOutOfMemory = &kernel.Error{Module: "boot_mem_alloc", Message: "out of memory"}
|
||||
)
|
||||
|
||||
// BootMemAllocator implements a rudimentary physical memory allocator which is used
|
||||
// to bootstrap the kernel.
|
||||
//
|
||||
// The allocator implementation uses the memory region information provided by
|
||||
// the bootloader to detect free memory blocks and return the next available
|
||||
// free frame.
|
||||
//
|
||||
// Allocations are tracked via an internal counter that contains the last
|
||||
// allocated frame index. The system memory regions are mapped into a linear
|
||||
// page index by aligning the region start address to the system's page size
|
||||
// and then dividing by the page size.
|
||||
//
|
||||
// Due to the way that the allocator works, it is not possible to free
|
||||
// allocated pages. Once the kernel is properly initialized, the allocated
|
||||
// blocks will be handed over to a more advanced memory allocator that does
|
||||
// support freeing.
|
||||
type BootMemAllocator struct {
|
||||
// allocCount tracks the total number of allocated frames.
|
||||
allocCount uint64
|
||||
|
||||
// lastAllocIndex tracks the last allocated frame index.
|
||||
lastAllocIndex int64
|
||||
}
|
||||
|
||||
// Init sets up the boot memory allocator internal state and prints out the
|
||||
// system memory map.
|
||||
func (alloc *BootMemAllocator) Init() {
|
||||
alloc.lastAllocIndex = -1
|
||||
|
||||
early.Printf("[boot_mem_alloc] system memory map:\n")
|
||||
var totalFree mem.Size
|
||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||
early.Printf("\t[0x%10x - 0x%10x], size: %10d, type: %s\n", region.PhysAddress, region.PhysAddress+region.Length, region.Length, region.Type.String())
|
||||
|
||||
if region.Type == multiboot.MemAvailable {
|
||||
totalFree += mem.Size(region.Length)
|
||||
}
|
||||
return true
|
||||
})
|
||||
early.Printf("[boot_mem_alloc] free memory: %dKb\n", uint64(totalFree/mem.Kb))
|
||||
}
|
||||
|
||||
// AllocFrame scans the system memory regions reported by the bootloader and
|
||||
// reserves the next available free frame.
|
||||
//
|
||||
// AllocFrame returns an error if no more memory can be allocated or when the
|
||||
// requested page order is > 0.
|
||||
func (alloc *BootMemAllocator) AllocFrame(order mem.PageOrder) (pmm.Frame, *kernel.Error) {
|
||||
if order > 0 {
|
||||
return pmm.InvalidFrame, errBootAllocUnsupportedPageSize
|
||||
}
|
||||
|
||||
var (
|
||||
foundPageIndex int64 = -1
|
||||
regionStartPageIndex, regionEndPageIndex int64
|
||||
)
|
||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||
if region.Type != multiboot.MemAvailable {
|
||||
return true
|
||||
}
|
||||
|
||||
// Align region start address to a page boundary and find the start
|
||||
// and end page indices for the region
|
||||
regionStartPageIndex = int64(((mem.Size(region.PhysAddress) + (mem.PageSize - 1)) & ^(mem.PageSize - 1)) >> mem.PageShift)
|
||||
regionEndPageIndex = int64(((mem.Size(region.PhysAddress+region.Length) - (mem.PageSize - 1)) & ^(mem.PageSize - 1)) >> mem.PageShift)
|
||||
|
||||
// Ignore already allocated regions
|
||||
if alloc.lastAllocIndex >= regionEndPageIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
// We found a block that can be allocated. The last allocated
|
||||
// index will be either pointing to a previous region or will
|
||||
// point inside this region. In the first case we just need to
|
||||
// select the regionStartPageIndex. In the latter case we can
|
||||
// simply select the next available page in the current region.
|
||||
if alloc.lastAllocIndex < regionStartPageIndex {
|
||||
foundPageIndex = regionStartPageIndex
|
||||
} else {
|
||||
foundPageIndex = alloc.lastAllocIndex + 1
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if foundPageIndex == -1 {
|
||||
return pmm.InvalidFrame, errBootAllocOutOfMemory
|
||||
}
|
||||
|
||||
alloc.allocCount++
|
||||
alloc.lastAllocIndex = foundPageIndex
|
||||
|
||||
return pmm.Frame(foundPageIndex), nil
|
||||
}
|
||||
88
kernel/mem/pmm/allocator/bootmem_test.go
Normal file
88
kernel/mem/pmm/allocator/bootmem_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||
)
|
||||
|
||||
func TestBootMemoryAllocator(t *testing.T) {
|
||||
// Mock a tty to handle early.Printf output
|
||||
mockConsoleFb := make([]byte, 160*25)
|
||||
mockConsole := &console.Ega{}
|
||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
||||
|
||||
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||
|
||||
var totalFreeFrames uint64
|
||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||
if region.Type == multiboot.MemAvailable {
|
||||
regionStartFrameIndex := uint64(((mem.Size(region.PhysAddress) + (mem.PageSize - 1)) & ^(mem.PageSize - 1)) >> mem.PageShift)
|
||||
regionEndFrameIndex := uint64(((mem.Size(region.PhysAddress+region.Length) - (mem.PageSize - 1)) & ^(mem.PageSize - 1)) >> mem.PageShift)
|
||||
|
||||
totalFreeFrames += regionEndFrameIndex - regionStartFrameIndex + 1
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
var (
|
||||
alloc BootMemAllocator
|
||||
allocFrameCount uint64
|
||||
)
|
||||
for alloc.Init(); ; allocFrameCount++ {
|
||||
frame, err := alloc.AllocFrame(mem.PageOrder(0))
|
||||
if err != nil {
|
||||
if err == errBootAllocOutOfMemory {
|
||||
break
|
||||
}
|
||||
t.Fatalf("[frame %d] unexpected allocator error: %v", allocFrameCount, err)
|
||||
}
|
||||
|
||||
expAddress := uintptr(uint64(alloc.lastAllocIndex) * uint64(mem.PageSize))
|
||||
if got := frame.Address(); got != expAddress {
|
||||
t.Errorf("[frame %d] expected frame address to be 0x%x; got 0x%x", allocFrameCount, expAddress, got)
|
||||
}
|
||||
|
||||
if !frame.Valid() {
|
||||
t.Errorf("[frame %d] expected IsValid() to return true", allocFrameCount)
|
||||
}
|
||||
}
|
||||
|
||||
if allocFrameCount != totalFreeFrames {
|
||||
t.Fatalf("expected allocator to allocate %d frames; allocated %d", totalFreeFrames, allocFrameCount)
|
||||
}
|
||||
|
||||
// This allocator only works with order(0) blocks
|
||||
if frame, err := alloc.AllocFrame(mem.PageOrder(1)); err != errBootAllocUnsupportedPageSize || frame.Valid() {
|
||||
t.Fatalf("expected allocator to return errBootAllocUnsupportedPageSize and an invalid frame when requested to allocate a block with order > 0; got %v, %v", err, frame)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// A dump of multiboot data when running under qemu containing only the memory region tag.
|
||||
multibootMemoryMap = []byte{
|
||||
72, 5, 0, 0, 0, 0, 0, 0,
|
||||
6, 0, 0, 0, 160, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 9, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 252, 9, 0, 0, 0, 0, 0,
|
||||
0, 4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0,
|
||||
0, 0, 238, 7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 254, 7, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 255, 0, 0, 0, 0,
|
||||
0, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
|
||||
9, 0, 0, 0, 212, 3, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0,
|
||||
21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0,
|
||||
1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0,
|
||||
24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user