mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Reserve and initialize the BitmapAllocator frame pools
This commit is contained in:
parent
1c3bfcd58d
commit
bc44151c93
164
kernel/mem/pmm/allocator/bitmap_allocator.go
Normal file
164
kernel/mem/pmm/allocator/bitmap_allocator.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/achilleasa/gopher-os/kernel"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/vmm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// FrameAllocator is a BitmapAllocator instance that serves as the
|
||||||
|
// primary allocator for reserving pages.
|
||||||
|
FrameAllocator BitmapAllocator
|
||||||
|
|
||||||
|
// The followning functions are used by tests to mock calls to the vmm package
|
||||||
|
// and are automatically inlined by the compiler.
|
||||||
|
reserveRegionFn = vmm.EarlyReserveRegion
|
||||||
|
mapFn = vmm.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
type framePool struct {
|
||||||
|
// startFrame is the frame number for the first page in this pool.
|
||||||
|
// each free bitmap entry i corresponds to frame (startFrame + i).
|
||||||
|
startFrame pmm.Frame
|
||||||
|
|
||||||
|
// endFrame tracks the last frame in the pool. The total number of
|
||||||
|
// frames is given by: (endFrame - startFrame) - 1
|
||||||
|
endFrame pmm.Frame
|
||||||
|
|
||||||
|
// freeCount tracks the available pages in this pool. The allocator
|
||||||
|
// can use this field to skip fully allocated pools without the need
|
||||||
|
// to scan the free bitmap.
|
||||||
|
freeCount uint32
|
||||||
|
|
||||||
|
// freeBitmap tracks used/free pages in the pool.
|
||||||
|
freeBitmap []uint64
|
||||||
|
freeBitmapHdr reflect.SliceHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitmapAllocator implements a physical frame allocator that tracks frame
|
||||||
|
// reservations across the available memory pools using bitmaps.
|
||||||
|
type BitmapAllocator struct {
|
||||||
|
// totalPages tracks the total number of pages across all pools.
|
||||||
|
totalPages uint32
|
||||||
|
|
||||||
|
// reservedPages tracks the number of reserved pages across all pools.
|
||||||
|
reservedPages uint32
|
||||||
|
|
||||||
|
pools []framePool
|
||||||
|
poolsHdr reflect.SliceHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// init allocates space for the allocator structures using the early bootmem
|
||||||
|
// allocator and flags any allocated pages as reserved.
|
||||||
|
func (alloc *BitmapAllocator) init() *kernel.Error {
|
||||||
|
return alloc.setupPoolBitmaps()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupPoolBitmaps uses the early allocator and vmm region reservation helper
|
||||||
|
// to initialize the list of available pools and their free bitmap slices.
|
||||||
|
func (alloc *BitmapAllocator) setupPoolBitmaps() *kernel.Error {
|
||||||
|
var (
|
||||||
|
err *kernel.Error
|
||||||
|
sizeofPool = unsafe.Sizeof(framePool{})
|
||||||
|
pageSizeMinus1 = uint64(mem.PageSize - 1)
|
||||||
|
requiredBitmapBytes mem.Size
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detect available memory regions and calculate their pool bitmap
|
||||||
|
// requirements.
|
||||||
|
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||||
|
if region.Type != multiboot.MemAvailable {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.poolsHdr.Len++
|
||||||
|
alloc.poolsHdr.Cap++
|
||||||
|
|
||||||
|
// Reported addresses may not be page-aligned; round up to get
|
||||||
|
// the start frame and round down to get the end frame
|
||||||
|
regionStartFrame := pmm.Frame(((region.PhysAddress + pageSizeMinus1) & ^pageSizeMinus1) >> mem.PageShift)
|
||||||
|
regionEndFrame := pmm.Frame(((region.PhysAddress+region.Length) & ^pageSizeMinus1)>>mem.PageShift) - 1
|
||||||
|
pageCount := uint32(regionEndFrame - regionStartFrame)
|
||||||
|
alloc.totalPages += pageCount
|
||||||
|
|
||||||
|
// To represent the free page bitmap we need pageCount bits. Since our
|
||||||
|
// slice uses uint64 for storing the bitmap we need to round up the
|
||||||
|
// required bits so they are a multiple of 64 bits
|
||||||
|
requiredBitmapBytes += mem.Size(((pageCount + 63) &^ 63) >> 3)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reserve enough pages to hold the allocator state
|
||||||
|
requiredBytes := mem.Size(((uint64(uintptr(alloc.poolsHdr.Len)*sizeofPool) + uint64(requiredBitmapBytes)) + pageSizeMinus1) & ^pageSizeMinus1)
|
||||||
|
requiredPages := requiredBytes >> mem.PageShift
|
||||||
|
alloc.poolsHdr.Data, err = reserveRegionFn(requiredBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for page, index := vmm.PageFromAddress(alloc.poolsHdr.Data), mem.Size(0); index < requiredPages; page, index = page+1, index+1 {
|
||||||
|
nextFrame, err := earlyAllocFrame()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mapFn(page, nextFrame, vmm.FlagPresent|vmm.FlagRW|vmm.FlagNoExecute); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.Memset(page.Address(), 0, mem.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.pools = *(*[]framePool)(unsafe.Pointer(&alloc.poolsHdr))
|
||||||
|
|
||||||
|
// Run a second pass to initialize the free bitmap slices for all pools
|
||||||
|
bitmapStartAddr := alloc.poolsHdr.Data + uintptr(alloc.poolsHdr.Len)*sizeofPool
|
||||||
|
poolIndex := 0
|
||||||
|
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||||
|
if region.Type != multiboot.MemAvailable {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
regionStartFrame := pmm.Frame(((region.PhysAddress + pageSizeMinus1) & ^pageSizeMinus1) >> mem.PageShift)
|
||||||
|
regionEndFrame := pmm.Frame(((region.PhysAddress+region.Length) & ^pageSizeMinus1)>>mem.PageShift) - 1
|
||||||
|
bitmapBytes := uintptr((((regionEndFrame - regionStartFrame) + 63) &^ 63) >> 3)
|
||||||
|
|
||||||
|
alloc.pools[poolIndex].startFrame = regionStartFrame
|
||||||
|
alloc.pools[poolIndex].endFrame = regionEndFrame
|
||||||
|
alloc.pools[poolIndex].freeCount = uint32(regionEndFrame - regionStartFrame + 1)
|
||||||
|
alloc.pools[poolIndex].freeBitmapHdr.Len = int(bitmapBytes >> 3)
|
||||||
|
alloc.pools[poolIndex].freeBitmapHdr.Cap = alloc.pools[poolIndex].freeBitmapHdr.Len
|
||||||
|
alloc.pools[poolIndex].freeBitmapHdr.Data = bitmapStartAddr
|
||||||
|
alloc.pools[poolIndex].freeBitmap = *(*[]uint64)(unsafe.Pointer(&alloc.pools[poolIndex].freeBitmapHdr))
|
||||||
|
|
||||||
|
bitmapStartAddr += bitmapBytes
|
||||||
|
poolIndex++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
// confuses the compiler's escape analysis into thinking that
|
||||||
|
// earlyAllocator.Frame escapes to heap.
|
||||||
|
func earlyAllocFrame() (pmm.Frame, *kernel.Error) {
|
||||||
|
return earlyAllocator.AllocFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init sets up the kernel physical memory allocation sub-system.
|
||||||
|
func Init(kernelStart, kernelEnd uintptr) *kernel.Error {
|
||||||
|
earlyAllocator.init(kernelStart, kernelEnd)
|
||||||
|
earlyAllocator.printMemoryMap()
|
||||||
|
|
||||||
|
vmm.SetFrameAllocator(earlyAllocFrame)
|
||||||
|
return FrameAllocator.init()
|
||||||
|
}
|
183
kernel/mem/pmm/allocator/bitmap_allocator_test.go
Normal file
183
kernel/mem/pmm/allocator/bitmap_allocator_test.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/achilleasa/gopher-os/kernel"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/vmm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetupPoolBitmaps(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
mapFn = vmm.Map
|
||||||
|
reserveRegionFn = vmm.EarlyReserveRegion
|
||||||
|
}()
|
||||||
|
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
|
||||||
|
// The captured multiboot data corresponds to qemu running with 128M RAM.
|
||||||
|
// The allocator will need to reserve 2 pages to store the bitmap data.
|
||||||
|
var (
|
||||||
|
alloc BitmapAllocator
|
||||||
|
physMem = make([]byte, 2*mem.PageSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init phys mem with junk
|
||||||
|
for i := 0; i < len(physMem); i++ {
|
||||||
|
physMem[i] = 0xf0
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCallCount := 0
|
||||||
|
mapFn = func(page vmm.Page, frame pmm.Frame, flags vmm.PageTableEntryFlag) *kernel.Error {
|
||||||
|
mapCallCount++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reserveCallCount := 0
|
||||||
|
reserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) {
|
||||||
|
reserveCallCount++
|
||||||
|
return uintptr(unsafe.Pointer(&physMem[0])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := alloc.setupPoolBitmaps(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := 2; mapCallCount != exp {
|
||||||
|
t.Fatalf("expected allocator to call vmm.Map %d times; called %d", exp, mapCallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := 1; reserveCallCount != exp {
|
||||||
|
t.Fatalf("expected allocator to call vmm.EarlyReserveRegion %d times; called %d", exp, reserveCallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := 2, len(alloc.pools); got != exp {
|
||||||
|
t.Fatalf("expected allocator to initialize %d pools; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
for poolIndex, pool := range alloc.pools {
|
||||||
|
if expFreeCount := uint32(pool.endFrame - pool.startFrame + 1); pool.freeCount != expFreeCount {
|
||||||
|
t.Errorf("[pool %d] expected free count to be %d; got %d", poolIndex, expFreeCount, pool.freeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := int(math.Ceil(float64(pool.freeCount)/64.0)), len(pool.freeBitmap); got != exp {
|
||||||
|
t.Errorf("[pool %d] expected bitmap len to be %d; got %d", poolIndex, exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
for blockIndex, block := range pool.freeBitmap {
|
||||||
|
if block != 0 {
|
||||||
|
t.Errorf("[pool %d] expected bitmap block %d to be cleared; got %d", poolIndex, blockIndex, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupPoolBitmapsErrors(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
mapFn = vmm.Map
|
||||||
|
reserveRegionFn = vmm.EarlyReserveRegion
|
||||||
|
}()
|
||||||
|
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
var alloc BitmapAllocator
|
||||||
|
|
||||||
|
t.Run("vmm.EarlyReserveRegion returns an error", func(t *testing.T) {
|
||||||
|
expErr := &kernel.Error{Module: "test", Message: "something went wrong"}
|
||||||
|
|
||||||
|
reserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) {
|
||||||
|
return 0, expErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := alloc.setupPoolBitmaps(); err != expErr {
|
||||||
|
t.Fatalf("expected to get error: %v; got %v", expErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("vmm.Map returns an error", func(t *testing.T) {
|
||||||
|
expErr := &kernel.Error{Module: "test", Message: "something went wrong"}
|
||||||
|
|
||||||
|
reserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mapFn = func(page vmm.Page, frame pmm.Frame, flags vmm.PageTableEntryFlag) *kernel.Error {
|
||||||
|
return expErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := alloc.setupPoolBitmaps(); err != expErr {
|
||||||
|
t.Fatalf("expected to get error: %v; got %v", expErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("earlyAllocator returns an error", func(t *testing.T) {
|
||||||
|
emptyInfoData := []byte{
|
||||||
|
0, 0, 0, 0, // size
|
||||||
|
0, 0, 0, 0, // reserved
|
||||||
|
0, 0, 0, 0, // tag with type zero and length zero
|
||||||
|
0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&emptyInfoData[0])))
|
||||||
|
|
||||||
|
if err := alloc.setupPoolBitmaps(); err != errBootAllocOutOfMemory {
|
||||||
|
t.Fatalf("expected to get error: %v; got %v", errBootAllocOutOfMemory, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocatorPackageInit(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
mapFn = vmm.Map
|
||||||
|
reserveRegionFn = vmm.EarlyReserveRegion
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
physMem = make([]byte, 2*mem.PageSize)
|
||||||
|
fb = mockTTY()
|
||||||
|
buf bytes.Buffer
|
||||||
|
)
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
mapFn = func(page vmm.Page, frame pmm.Frame, flags vmm.PageTableEntryFlag) *kernel.Error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) {
|
||||||
|
return uintptr(unsafe.Pointer(&physMem[0])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Init(0x100000, 0x1fa7c8); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(fb); i += 2 {
|
||||||
|
if fb[i] == 0x0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.WriteByte(fb[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := "[boot_mem_alloc] system memory map: [0x0000000000 - 0x000009fc00], size: 654336, type: available [0x000009fc00 - 0x00000a0000], size: 1024, type: reserved [0x00000f0000 - 0x0000100000], size: 65536, type: reserved [0x0000100000 - 0x0007fe0000], size: 133038080, type: available [0x0007fe0000 - 0x0008000000], size: 131072, type: reserved [0x00fffc0000 - 0x0100000000], size: 262144, type: reserved[boot_mem_alloc] available memory: 130559Kb[boot_mem_alloc] kernel loaded at 0x100000 - 0x1fa7c8[boot_mem_alloc] size: 1025992 bytes, reserved pages: 251"
|
||||||
|
if got := buf.String(); got != exp {
|
||||||
|
t.Fatalf("expected printMemoryMap to generate the following output:\n%q\ngot:\n%q", exp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
expErr := &kernel.Error{Module: "test", Message: "something went wrong"}
|
||||||
|
|
||||||
|
mapFn = func(page vmm.Page, frame pmm.Frame, flags vmm.PageTableEntryFlag) *kernel.Error {
|
||||||
|
return expErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Init(0x100000, 0x1fa7c8); err != expErr {
|
||||||
|
t.Fatalf("expected to get error: %v; got %v", expErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -134,10 +134,3 @@ func (alloc *bootMemAllocator) printMemoryMap() {
|
|||||||
uint64(alloc.kernelEndFrame-alloc.kernelStartFrame+1),
|
uint64(alloc.kernelEndFrame-alloc.kernelStartFrame+1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init sets up the kernel physical memory allocation sub-system.
|
|
||||||
func Init(kernelStart, kernelEnd uintptr) *kernel.Error {
|
|
||||||
earlyAllocator.init(kernelStart, kernelEnd)
|
|
||||||
earlyAllocator.printMemoryMap()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package allocator
|
package allocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -94,26 +93,6 @@ func TestBootMemoryAllocator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllocatorPackageInit(t *testing.T) {
|
|
||||||
fb := mockTTY()
|
|
||||||
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
|
||||||
|
|
||||||
Init(0x100000, 0x1fa7c8)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i := 0; i < len(fb); i += 2 {
|
|
||||||
if fb[i] == 0x0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.WriteByte(fb[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := "[boot_mem_alloc] system memory map: [0x0000000000 - 0x000009fc00], size: 654336, type: available [0x000009fc00 - 0x00000a0000], size: 1024, type: reserved [0x00000f0000 - 0x0000100000], size: 65536, type: reserved [0x0000100000 - 0x0007fe0000], size: 133038080, type: available [0x0007fe0000 - 0x0008000000], size: 131072, type: reserved [0x00fffc0000 - 0x0100000000], size: 262144, type: reserved[boot_mem_alloc] available memory: 130559Kb[boot_mem_alloc] kernel loaded at 0x100000 - 0x1fa7c8[boot_mem_alloc] size: 1025992 bytes, reserved pages: 251"
|
|
||||||
if got := buf.String(); got != exp {
|
|
||||||
t.Fatalf("expected printMemoryMap to generate the following output:\n%q\ngot:\n%q", exp, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// A dump of multiboot data when running under qemu containing only the
|
// A dump of multiboot data when running under qemu containing only the
|
||||||
// memory region tag. The dump encodes the following available memory
|
// memory region tag. The dump encodes the following available memory
|
||||||
|
Loading…
x
Reference in New Issue
Block a user