mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Refactor allocator and unexport it
AllocFrame now rounds up the region start address to the nearest page multiple and rounds down the region end address to the nearest page multiple. It also ignores memory regions with size smaller than a page. Instead of using frame indices and converting them to a pmm.Frame, the allocator now just keeps track of the last allocated pmm.Frame. As the allocator is now unexported, a package-exported Init() method is now provided whose purpose is to initialize the physical allocator sub-system.
This commit is contained in:
parent
c0a9e07e83
commit
ad0bf0a4ca
@ -3,7 +3,8 @@ package kmain
|
|||||||
import (
|
import (
|
||||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||||
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
"github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/pmm/allocator"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Kmain is the only Go symbol that is visible (exported) from the rt0 initialization
|
// Kmain is the only Go symbol that is visible (exported) from the rt0 initialization
|
||||||
@ -23,5 +24,7 @@ func Kmain(multibootInfoPtr uintptr) {
|
|||||||
hal.InitTerminal()
|
hal.InitTerminal()
|
||||||
hal.ActiveTerminal.Clear()
|
hal.ActiveTerminal.Clear()
|
||||||
|
|
||||||
pmm.EarlyAllocator.Init()
|
if err := allocator.Init(); err != nil {
|
||||||
|
early.Printf("[%s] error: %s\n", err.Module, err.Message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,38 +9,87 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// earlyAllocator is a boot mem allocator instance used for page
|
||||||
|
// allocations before switching to a more advanced allocator.
|
||||||
|
earlyAllocator bootMemAllocator
|
||||||
|
|
||||||
errBootAllocOutOfMemory = &kernel.Error{Module: "boot_mem_alloc", Message: "out of memory"}
|
errBootAllocOutOfMemory = &kernel.Error{Module: "boot_mem_alloc", Message: "out of memory"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// BootMemAllocator implements a rudimentary physical memory allocator which is used
|
// bootMemAllocator implements a rudimentary physical memory allocator which is
|
||||||
// to bootstrap the kernel.
|
// used to bootstrap the kernel.
|
||||||
//
|
//
|
||||||
// The allocator implementation uses the memory region information provided by
|
// The allocator implementation uses the memory region information provided by
|
||||||
// the bootloader to detect free memory blocks and return the next available
|
// the bootloader to detect free memory blocks and return the next available
|
||||||
// free frame.
|
// free frame. Allocations are tracked via an internal counter that contains
|
||||||
//
|
// the last allocated 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
|
// Due to the way that the allocator works, it is not possible to free
|
||||||
// allocated pages. Once the kernel is properly initialized, the allocated
|
// allocated pages. Once the kernel is properly initialized, the allocated
|
||||||
// blocks will be handed over to a more advanced memory allocator that does
|
// blocks will be handed over to a more advanced memory allocator that does
|
||||||
// support freeing.
|
// support freeing.
|
||||||
type BootMemAllocator struct {
|
type bootMemAllocator struct {
|
||||||
// allocCount tracks the total number of allocated frames.
|
// allocCount tracks the total number of allocated frames.
|
||||||
allocCount uint64
|
allocCount uint64
|
||||||
|
|
||||||
// lastAllocIndex tracks the last allocated frame index.
|
// lastAllocFrame tracks the last allocated frame number.
|
||||||
lastAllocIndex int64
|
lastAllocFrame pmm.Frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init sets up the boot memory allocator internal state and prints out the
|
// init sets up the boot memory allocator internal state.
|
||||||
// system memory map.
|
func (alloc *bootMemAllocator) init() {
|
||||||
func (alloc *BootMemAllocator) Init() {
|
// TODO
|
||||||
alloc.lastAllocIndex = -1
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (alloc *bootMemAllocator) AllocFrame() (pmm.Frame, *kernel.Error) {
|
||||||
|
var err = errBootAllocOutOfMemory
|
||||||
|
|
||||||
|
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||||
|
// Ignore reserved regions and regions smaller than a single page
|
||||||
|
if region.Type != multiboot.MemAvailable || region.Length < uint64(mem.PageSize) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reported addresses may not be page-aligned; round up to get
|
||||||
|
// the start frame and round down to get the end frame
|
||||||
|
pageSizeMinus1 := uint64(mem.PageSize - 1)
|
||||||
|
regionStartFrame := pmm.Frame(((region.PhysAddress + pageSizeMinus1) & ^pageSizeMinus1) >> mem.PageShift)
|
||||||
|
regionEndFrame := pmm.Frame(((region.PhysAddress+region.Length) & ^pageSizeMinus1)>>mem.PageShift) - 1
|
||||||
|
|
||||||
|
// Ignore already allocated regions
|
||||||
|
if alloc.allocCount != 0 && alloc.lastAllocFrame >= regionEndFrame {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last allocated frame will be either pointing to a
|
||||||
|
// previous region or will point inside this region. In the
|
||||||
|
// first case (or if this is the first allocation) we select
|
||||||
|
// the start frame for this region. In the latter case we
|
||||||
|
// select the next available frame.
|
||||||
|
if alloc.allocCount == 0 || alloc.lastAllocFrame < regionStartFrame {
|
||||||
|
alloc.lastAllocFrame = regionStartFrame
|
||||||
|
} else {
|
||||||
|
alloc.lastAllocFrame++
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return pmm.InvalidFrame, errBootAllocOutOfMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.allocCount++
|
||||||
|
return alloc.lastAllocFrame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printMemoryMap scans the memory region information provided by the
|
||||||
|
// bootloader and prints out the system's memory map.
|
||||||
|
func (alloc *bootMemAllocator) printMemoryMap() {
|
||||||
early.Printf("[boot_mem_alloc] system memory map:\n")
|
early.Printf("[boot_mem_alloc] system memory map:\n")
|
||||||
var totalFree mem.Size
|
var totalFree mem.Size
|
||||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||||
@ -54,49 +103,9 @@ func (alloc *BootMemAllocator) Init() {
|
|||||||
early.Printf("[boot_mem_alloc] free memory: %dKb\n", uint64(totalFree/mem.Kb))
|
early.Printf("[boot_mem_alloc] free memory: %dKb\n", uint64(totalFree/mem.Kb))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllocFrame scans the system memory regions reported by the bootloader and
|
// Init sets up the kernel physical memory allocation sub-system.
|
||||||
// reserves the next available free frame.
|
func Init() *kernel.Error {
|
||||||
//
|
earlyAllocator.init()
|
||||||
// AllocFrame returns an error if no more memory can be allocated.
|
earlyAllocator.printMemoryMap()
|
||||||
func (alloc *BootMemAllocator) AllocFrame() (pmm.Frame, *kernel.Error) {
|
return nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,27 @@
|
|||||||
package allocator
|
package allocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||||
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||||
"github.com/achilleasa/gopher-os/kernel/mem"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBootMemoryAllocator(t *testing.T) {
|
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])))
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
|
||||||
var totalFreeFrames uint64
|
// region 1 extents get rounded to [0, 9f000] and provides 159 frames [0 to 158]
|
||||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
// region 1 uses the original extents [100000 - 7fe0000] and provides 32480 frames [256-32735]
|
||||||
if region.Type == multiboot.MemAvailable {
|
var totalFreeFrames uint64 = 159 + 32480
|
||||||
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 (
|
var (
|
||||||
alloc BootMemAllocator
|
alloc bootMemAllocator
|
||||||
allocFrameCount uint64
|
allocFrameCount uint64
|
||||||
)
|
)
|
||||||
for alloc.Init(); ; allocFrameCount++ {
|
for {
|
||||||
frame, err := alloc.AllocFrame()
|
frame, err := alloc.AllocFrame()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errBootAllocOutOfMemory {
|
if err == errBootAllocOutOfMemory {
|
||||||
@ -43,10 +29,9 @@ func TestBootMemoryAllocator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Fatalf("[frame %d] unexpected allocator error: %v", allocFrameCount, err)
|
t.Fatalf("[frame %d] unexpected allocator error: %v", allocFrameCount, err)
|
||||||
}
|
}
|
||||||
|
allocFrameCount++
|
||||||
expAddress := uintptr(uint64(alloc.lastAllocIndex) * uint64(mem.PageSize))
|
if frame != alloc.lastAllocFrame {
|
||||||
if got := frame.Address(); got != expAddress {
|
t.Errorf("[frame %d] expected allocated frame to be %d; got %d", allocFrameCount, alloc.lastAllocFrame, frame)
|
||||||
t.Errorf("[frame %d] expected frame address to be 0x%x; got 0x%x", allocFrameCount, expAddress, got)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !frame.Valid() {
|
if !frame.Valid() {
|
||||||
@ -59,8 +44,32 @@ func TestBootMemoryAllocator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllocatorPackageInit(t *testing.T) {
|
||||||
|
fb := mockTTY()
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
|
||||||
|
Init()
|
||||||
|
|
||||||
|
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] free memory: 130559Kb"
|
||||||
|
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 memory region tag.
|
// A dump of multiboot data when running under qemu containing only the
|
||||||
|
// memory region tag. The dump encodes the following available memory
|
||||||
|
// regions:
|
||||||
|
// [ 0 - 9fc00] length: 654336
|
||||||
|
// [100000 - 7fe0000] length: 133038080
|
||||||
multibootMemoryMap = []byte{
|
multibootMemoryMap = []byte{
|
||||||
72, 5, 0, 0, 0, 0, 0, 0,
|
72, 5, 0, 0, 0, 0, 0, 0,
|
||||||
6, 0, 0, 0, 160, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0,
|
6, 0, 0, 0, 160, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@ -81,3 +90,13 @@ var (
|
|||||||
24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mockTTY() []byte {
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
return mockConsoleFb
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user