mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #27 from achilleasa/implement-bitmap-allocator
Implement bitmap-based physical page allocator
This commit is contained in:
commit
3923e09aac
@ -297,10 +297,11 @@ _rt0_enter_long_mode:
|
|||||||
or eax, 1 << 5
|
or eax, 1 << 5
|
||||||
mov cr4, eax
|
mov cr4, eax
|
||||||
|
|
||||||
; Now enable long mode by modifying the EFER MSR
|
; Now enable long mode (bit 8) and the no-execute support (bit 11) by
|
||||||
|
; modifying the EFER MSR
|
||||||
mov ecx, 0xc0000080
|
mov ecx, 0xc0000080
|
||||||
rdmsr ; read msr value to eax
|
rdmsr ; read msr value to eax
|
||||||
or eax, 1 << 8
|
or eax, (1 << 8) | (1<<11)
|
||||||
wrmsr
|
wrmsr
|
||||||
|
|
||||||
; Finally enable paging
|
; Finally enable paging
|
||||||
|
327
kernel/mem/pmm/allocator/bitmap_allocator.go
Normal file
327
kernel/mem/pmm/allocator/bitmap_allocator.go
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"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
|
||||||
|
|
||||||
|
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
|
||||||
|
mapFn = vmm.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
type markAs bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
markReserved markAs = false
|
||||||
|
markFree = true
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if err := alloc.setupPoolBitmaps(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.reserveKernelFrames()
|
||||||
|
alloc.reserveEarlyAllocatorFrames()
|
||||||
|
alloc.printStats()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// markFrame updates the reservation flag for the bitmap entry that corresponds
|
||||||
|
// to the supplied frame.
|
||||||
|
func (alloc *BitmapAllocator) markFrame(poolIndex int, frame pmm.Frame, flag markAs) {
|
||||||
|
if poolIndex < 0 || frame > alloc.pools[poolIndex].endFrame {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The offset in the block is given by: frame % 64. As the bitmap uses a
|
||||||
|
// big-ending representation we need to set the bit at index: 63 - offset
|
||||||
|
relFrame := frame - alloc.pools[poolIndex].startFrame
|
||||||
|
block := relFrame >> 6
|
||||||
|
mask := uint64(1 << (63 - (relFrame - block<<6)))
|
||||||
|
switch flag {
|
||||||
|
case markFree:
|
||||||
|
alloc.pools[poolIndex].freeBitmap[block] &^= mask
|
||||||
|
alloc.pools[poolIndex].freeCount++
|
||||||
|
alloc.reservedPages--
|
||||||
|
case markReserved:
|
||||||
|
alloc.pools[poolIndex].freeBitmap[block] |= mask
|
||||||
|
alloc.pools[poolIndex].freeCount--
|
||||||
|
alloc.reservedPages++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// poolForFrame returns the index of the pool that contains frame or -1 if
|
||||||
|
// the frame is not contained in any of the available memory pools (e.g it
|
||||||
|
// points to a reserved memory region).
|
||||||
|
func (alloc *BitmapAllocator) poolForFrame(frame pmm.Frame) int {
|
||||||
|
for poolIndex, pool := range alloc.pools {
|
||||||
|
if frame >= pool.startFrame && frame <= pool.endFrame {
|
||||||
|
return poolIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// reserveKernelFrames makes as reserved the bitmap entries for the frames
|
||||||
|
// occupied by the kernel image.
|
||||||
|
func (alloc *BitmapAllocator) reserveKernelFrames() {
|
||||||
|
// Flag frames used by kernel image as reserved. Since the kernel must
|
||||||
|
// occupy a contiguous memory block we assume that all its frames will
|
||||||
|
// fall into one of the available memory pools
|
||||||
|
poolIndex := alloc.poolForFrame(earlyAllocator.kernelStartFrame)
|
||||||
|
for frame := earlyAllocator.kernelStartFrame; frame <= earlyAllocator.kernelEndFrame; frame++ {
|
||||||
|
alloc.markFrame(poolIndex, frame, markReserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reserveEarlyAllocatorFrames makes as reserved the bitmap entries for the frames
|
||||||
|
// already allocated by the early allocator.
|
||||||
|
func (alloc *BitmapAllocator) reserveEarlyAllocatorFrames() {
|
||||||
|
// We now need to decomission the early allocator by flagging all frames
|
||||||
|
// allocated by it as reserved. The allocator itself does not track
|
||||||
|
// individual frames but only a counter of allocated frames. To get
|
||||||
|
// the list of frames we reset its internal state and "replay" the
|
||||||
|
// allocation requests to get the correct frames.
|
||||||
|
allocCount := earlyAllocator.allocCount
|
||||||
|
earlyAllocator.allocCount, earlyAllocator.lastAllocFrame = 0, 0
|
||||||
|
for i := uint64(0); i < allocCount; i++ {
|
||||||
|
frame, _ := earlyAllocator.AllocFrame()
|
||||||
|
alloc.markFrame(
|
||||||
|
alloc.poolForFrame(frame),
|
||||||
|
frame,
|
||||||
|
markReserved,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (alloc *BitmapAllocator) printStats() {
|
||||||
|
early.Printf(
|
||||||
|
"[bitmap_alloc] page stats: free: %d/%d (%d reserved)\n",
|
||||||
|
alloc.totalPages-alloc.reservedPages,
|
||||||
|
alloc.totalPages,
|
||||||
|
alloc.reservedPages,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// confuses the compiler's escape analysis into thinking that
|
||||||
|
// earlyAllocator.Frame escapes to heap.
|
||||||
|
func earlyAllocFrame() (pmm.Frame, *kernel.Error) {
|
||||||
|
return earlyAllocator.AllocFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysAllocFrame is a helper that delegates a frame allocation request to the
|
||||||
|
// bitmap allocator instance.
|
||||||
|
func sysAllocFrame() (pmm.Frame, *kernel.Error) {
|
||||||
|
return FrameAllocator.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)
|
||||||
|
if err := FrameAllocator.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vmm.SetFrameAllocator(sysAllocFrame)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
432
kernel/mem/pmm/allocator/bitmap_allocator_test.go
Normal file
432
kernel/mem/pmm/allocator/bitmap_allocator_test.go
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"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 TestBitmapAllocatorMarkFrame(t *testing.T) {
|
||||||
|
var alloc = BitmapAllocator{
|
||||||
|
pools: []framePool{
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(0),
|
||||||
|
endFrame: pmm.Frame(127),
|
||||||
|
freeCount: 128,
|
||||||
|
freeBitmap: make([]uint64, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalPages: 128,
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFrame := pmm.Frame(alloc.totalPages)
|
||||||
|
for frame := pmm.Frame(0); frame < lastFrame; frame++ {
|
||||||
|
alloc.markFrame(0, frame, markReserved)
|
||||||
|
|
||||||
|
block := uint64(frame / 64)
|
||||||
|
blockOffset := uint64(frame % 64)
|
||||||
|
bitIndex := (63 - blockOffset)
|
||||||
|
bitMask := uint64(1 << bitIndex)
|
||||||
|
|
||||||
|
if alloc.pools[0].freeBitmap[block]&bitMask != bitMask {
|
||||||
|
t.Errorf("[frame %d] expected block[%d], bit %d to be set", frame, block, bitIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.markFrame(0, frame, markFree)
|
||||||
|
|
||||||
|
if alloc.pools[0].freeBitmap[block]&bitMask != 0 {
|
||||||
|
t.Errorf("[frame %d] expected block[%d], bit %d to be unset", frame, block, bitIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling markFrame with a frame not part of the pool should be a no-op
|
||||||
|
alloc.markFrame(0, pmm.Frame(0xbadf00d), markReserved)
|
||||||
|
for blockIndex, block := range alloc.pools[0].freeBitmap {
|
||||||
|
if block != 0 {
|
||||||
|
t.Errorf("expected all blocks to be set to 0; block %d is set to %d", blockIndex, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling markFrame with a negative pool index should be a no-op
|
||||||
|
alloc.markFrame(-1, pmm.Frame(0), markReserved)
|
||||||
|
for blockIndex, block := range alloc.pools[0].freeBitmap {
|
||||||
|
if block != 0 {
|
||||||
|
t.Errorf("expected all blocks to be set to 0; block %d is set to %d", blockIndex, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmapAllocatorPoolForFrame(t *testing.T) {
|
||||||
|
var alloc = BitmapAllocator{
|
||||||
|
pools: []framePool{
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(0),
|
||||||
|
endFrame: pmm.Frame(63),
|
||||||
|
freeCount: 64,
|
||||||
|
freeBitmap: make([]uint64, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(128),
|
||||||
|
endFrame: pmm.Frame(191),
|
||||||
|
freeCount: 64,
|
||||||
|
freeBitmap: make([]uint64, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalPages: 128,
|
||||||
|
}
|
||||||
|
|
||||||
|
specs := []struct {
|
||||||
|
frame pmm.Frame
|
||||||
|
expIndex int
|
||||||
|
}{
|
||||||
|
{pmm.Frame(0), 0},
|
||||||
|
{pmm.Frame(63), 0},
|
||||||
|
{pmm.Frame(64), -1},
|
||||||
|
{pmm.Frame(128), 1},
|
||||||
|
{pmm.Frame(192), -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
if got := alloc.poolForFrame(spec.frame); got != spec.expIndex {
|
||||||
|
t.Errorf("[spec %d] expected to get pool index %d; got %d", specIndex, spec.expIndex, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmapAllocatorReserveKernelFrames(t *testing.T) {
|
||||||
|
var alloc = BitmapAllocator{
|
||||||
|
pools: []framePool{
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(0),
|
||||||
|
endFrame: pmm.Frame(7),
|
||||||
|
freeCount: 8,
|
||||||
|
freeBitmap: make([]uint64, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(64),
|
||||||
|
endFrame: pmm.Frame(191),
|
||||||
|
freeCount: 128,
|
||||||
|
freeBitmap: make([]uint64, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalPages: 136,
|
||||||
|
}
|
||||||
|
|
||||||
|
// kernel occupies 16 frames and starts at the beginning of pool 1
|
||||||
|
earlyAllocator.kernelStartFrame = pmm.Frame(64)
|
||||||
|
earlyAllocator.kernelEndFrame = pmm.Frame(79)
|
||||||
|
kernelSizePages := uint32(earlyAllocator.kernelEndFrame - earlyAllocator.kernelStartFrame + 1)
|
||||||
|
alloc.reserveKernelFrames()
|
||||||
|
|
||||||
|
if exp, got := kernelSizePages, alloc.reservedPages; got != exp {
|
||||||
|
t.Fatalf("expected reserved page counter to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := uint32(8), alloc.pools[0].freeCount; got != exp {
|
||||||
|
t.Fatalf("expected free count for pool 0 to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := 128-kernelSizePages, alloc.pools[1].freeCount; got != exp {
|
||||||
|
t.Fatalf("expected free count for pool 1 to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first 16 bits of block 0 in pool 1 should all be set to 1
|
||||||
|
if exp, got := uint64(((1<<16)-1)<<48), alloc.pools[1].freeBitmap[0]; got != exp {
|
||||||
|
t.Fatalf("expected block 0 in pool 1 to be:\n%064s\ngot:\n%064s",
|
||||||
|
strconv.FormatUint(exp, 2),
|
||||||
|
strconv.FormatUint(got, 2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmapAllocatorReserveEarlyAllocatorFrames(t *testing.T) {
|
||||||
|
var alloc = BitmapAllocator{
|
||||||
|
pools: []framePool{
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(0),
|
||||||
|
endFrame: pmm.Frame(63),
|
||||||
|
freeCount: 64,
|
||||||
|
freeBitmap: make([]uint64, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startFrame: pmm.Frame(64),
|
||||||
|
endFrame: pmm.Frame(191),
|
||||||
|
freeCount: 128,
|
||||||
|
freeBitmap: make([]uint64, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalPages: 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
multiboot.SetInfoPtr(uintptr(unsafe.Pointer(&multibootMemoryMap[0])))
|
||||||
|
|
||||||
|
// Simulate 16 allocations made using the early allocator in region 0
|
||||||
|
// as reported by the multiboot data and move the kernel to pool 1
|
||||||
|
allocCount := uint32(16)
|
||||||
|
earlyAllocator.allocCount = uint64(allocCount)
|
||||||
|
earlyAllocator.kernelStartFrame = pmm.Frame(256)
|
||||||
|
earlyAllocator.kernelEndFrame = pmm.Frame(256)
|
||||||
|
alloc.reserveEarlyAllocatorFrames()
|
||||||
|
|
||||||
|
if exp, got := allocCount, alloc.reservedPages; got != exp {
|
||||||
|
t.Fatalf("expected reserved page counter to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := 64-allocCount, alloc.pools[0].freeCount; got != exp {
|
||||||
|
t.Fatalf("expected free count for pool 0 to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, got := uint32(128), alloc.pools[1].freeCount; got != exp {
|
||||||
|
t.Fatalf("expected free count for pool 1 to be %d; got %d", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first 16 bits of block 0 in pool 0 should all be set to 1
|
||||||
|
if exp, got := uint64(((1<<16)-1)<<48), alloc.pools[0].freeBitmap[0]; got != exp {
|
||||||
|
t.Fatalf("expected block 0 in pool 0 to be:\n%064s\ngot:\n%064s",
|
||||||
|
strconv.FormatUint(exp, 2),
|
||||||
|
strconv.FormatUint(got, 2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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", poolIndex, 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", poolIndex, 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
|
||||||
|
reserveRegionFn = vmm.EarlyReserveRegion
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
physMem = make([]byte, 2*mem.PageSize)
|
||||||
|
)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTTY()
|
||||||
|
if err := Init(0x100000, 0x1fa7c8); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point sysAllocFrame should work
|
||||||
|
if _, err := sysAllocFrame(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
35
kernel/mem/vmm/addr_space.go
Normal file
35
kernel/mem/vmm/addr_space.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package vmm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/achilleasa/gopher-os/kernel"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// earlyReserveLastUsed tracks the last reserved page address and is
|
||||||
|
// decreased after each allocation request. Initially, it points to
|
||||||
|
// tempMappingAddr which coincides with the end of the kernel address
|
||||||
|
// space.
|
||||||
|
earlyReserveLastUsed = tempMappingAddr
|
||||||
|
|
||||||
|
errEarlyReserveNoSpace = &kernel.Error{Module: "early_reserve", Message: "remaining virtual address space not large enough to satisfy reservation request"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EarlyReserveRegion reserves a page-aligned contiguous virtual memory region
|
||||||
|
// with the requested size in the kernel address space and returns its virtual
|
||||||
|
// address. If size is not a multiple of mem.PageSize it will be automatically
|
||||||
|
// rounded up.
|
||||||
|
//
|
||||||
|
// This function allocates regions starting at the end of the kernel address
|
||||||
|
// space. It should only be used during the early stages of kernel initialization.
|
||||||
|
func EarlyReserveRegion(size mem.Size) (uintptr, *kernel.Error) {
|
||||||
|
size = (size + (mem.PageSize - 1)) & ^(mem.PageSize - 1)
|
||||||
|
|
||||||
|
// reserving a region of the requested size will cause an underflow
|
||||||
|
if uintptr(size) > earlyReserveLastUsed {
|
||||||
|
return 0, errEarlyReserveNoSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
earlyReserveLastUsed -= uintptr(size)
|
||||||
|
return earlyReserveLastUsed, nil
|
||||||
|
}
|
29
kernel/mem/vmm/addr_space_test.go
Normal file
29
kernel/mem/vmm/addr_space_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package vmm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEarlyReserveAmd64(t *testing.T) {
|
||||||
|
if runtime.GOARCH != "amd64" {
|
||||||
|
t.Skip("test requires amd64 runtime; skipping")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(origLastUsed uintptr) {
|
||||||
|
earlyReserveLastUsed = origLastUsed
|
||||||
|
}(earlyReserveLastUsed)
|
||||||
|
|
||||||
|
earlyReserveLastUsed = 4096
|
||||||
|
next, err := EarlyReserveRegion(42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp := uintptr(0); next != exp {
|
||||||
|
t.Fatal("expected reservation request to be rounded to nearest page")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = EarlyReserveRegion(1); err != errEarlyReserveNoSpace {
|
||||||
|
t.Fatalf("expected to get errEarlyReserveNoSpace; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
@ -23,14 +23,11 @@ var (
|
|||||||
errNoHugePageSupport = &kernel.Error{Module: "vmm", Message: "huge pages are not supported"}
|
errNoHugePageSupport = &kernel.Error{Module: "vmm", Message: "huge pages are not supported"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// FrameAllocatorFn is a function that can allocate physical frames.
|
|
||||||
type FrameAllocatorFn func() (pmm.Frame, *kernel.Error)
|
|
||||||
|
|
||||||
// Map establishes a mapping between a virtual page and a physical memory frame
|
// Map establishes a mapping between a virtual page and a physical memory frame
|
||||||
// using the currently active page directory table. Calls to Map will use the
|
// using the currently active page directory table. Calls to Map will use the
|
||||||
// supplied physical frame allocator to initialize missing page tables at each
|
// supplied physical frame allocator to initialize missing page tables at each
|
||||||
// paging level supported by the MMU.
|
// paging level supported by the MMU.
|
||||||
func Map(page Page, frame pmm.Frame, flags PageTableEntryFlag, allocFn FrameAllocatorFn) *kernel.Error {
|
func Map(page Page, frame pmm.Frame, flags PageTableEntryFlag) *kernel.Error {
|
||||||
var err *kernel.Error
|
var err *kernel.Error
|
||||||
|
|
||||||
walk(page.Address(), func(pteLevel uint8, pte *pageTableEntry) bool {
|
walk(page.Address(), func(pteLevel uint8, pte *pageTableEntry) bool {
|
||||||
@ -53,7 +50,7 @@ func Map(page Page, frame pmm.Frame, flags PageTableEntryFlag, allocFn FrameAllo
|
|||||||
// physical frame for it map it and clear its contents.
|
// physical frame for it map it and clear its contents.
|
||||||
if !pte.HasFlags(FlagPresent) {
|
if !pte.HasFlags(FlagPresent) {
|
||||||
var newTableFrame pmm.Frame
|
var newTableFrame pmm.Frame
|
||||||
newTableFrame, err = allocFn()
|
newTableFrame, err = frameAllocator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -78,8 +75,8 @@ func Map(page Page, frame pmm.Frame, flags PageTableEntryFlag, allocFn FrameAllo
|
|||||||
// to a fixed virtual address overwriting any previous mapping. The temporary
|
// to a fixed virtual address overwriting any previous mapping. The temporary
|
||||||
// mapping mechanism is primarily used by the kernel to access and initialize
|
// mapping mechanism is primarily used by the kernel to access and initialize
|
||||||
// inactive page tables.
|
// inactive page tables.
|
||||||
func MapTemporary(frame pmm.Frame, allocFn FrameAllocatorFn) (Page, *kernel.Error) {
|
func MapTemporary(frame pmm.Frame) (Page, *kernel.Error) {
|
||||||
if err := Map(PageFromAddress(tempMappingAddr), frame, FlagRW, allocFn); err != nil {
|
if err := Map(PageFromAddress(tempMappingAddr), frame, FlagRW); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,17 +26,18 @@ func TestMapTemporaryAmd64(t *testing.T) {
|
|||||||
ptePtrFn = origPtePtr
|
ptePtrFn = origPtePtr
|
||||||
nextAddrFn = origNextAddrFn
|
nextAddrFn = origNextAddrFn
|
||||||
flushTLBEntryFn = origFlushTLBEntryFn
|
flushTLBEntryFn = origFlushTLBEntryFn
|
||||||
|
frameAllocator = nil
|
||||||
}(ptePtrFn, nextAddrFn, flushTLBEntryFn)
|
}(ptePtrFn, nextAddrFn, flushTLBEntryFn)
|
||||||
|
|
||||||
var physPages [pageLevels][mem.PageSize >> mem.PointerShift]pageTableEntry
|
var physPages [pageLevels][mem.PageSize >> mem.PointerShift]pageTableEntry
|
||||||
nextPhysPage := 0
|
nextPhysPage := 0
|
||||||
|
|
||||||
// allocFn returns pages from index 1; we keep index 0 for the P4 entry
|
// allocFn returns pages from index 1; we keep index 0 for the P4 entry
|
||||||
allocFn := func() (pmm.Frame, *kernel.Error) {
|
SetFrameAllocator(func() (pmm.Frame, *kernel.Error) {
|
||||||
nextPhysPage++
|
nextPhysPage++
|
||||||
pageAddr := unsafe.Pointer(&physPages[nextPhysPage][0])
|
pageAddr := unsafe.Pointer(&physPages[nextPhysPage][0])
|
||||||
return pmm.Frame(uintptr(pageAddr) >> mem.PageShift), nil
|
return pmm.Frame(uintptr(pageAddr) >> mem.PageShift), nil
|
||||||
}
|
})
|
||||||
|
|
||||||
pteCallCount := 0
|
pteCallCount := 0
|
||||||
ptePtrFn = func(entry uintptr) unsafe.Pointer {
|
ptePtrFn = func(entry uintptr) unsafe.Pointer {
|
||||||
@ -64,7 +65,7 @@ func TestMapTemporaryAmd64(t *testing.T) {
|
|||||||
frame := pmm.Frame(123)
|
frame := pmm.Frame(123)
|
||||||
levelIndices := []uint{510, 511, 511, 511}
|
levelIndices := []uint{510, 511, 511, 511}
|
||||||
|
|
||||||
page, err := MapTemporary(frame, allocFn)
|
page, err := MapTemporary(frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -124,21 +125,22 @@ func TestMapTemporaryErrorsAmd64(t *testing.T) {
|
|||||||
return unsafe.Pointer(&physPages[0][pteIndex])
|
return unsafe.Pointer(&physPages[0][pteIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := MapTemporary(frame, nil); err != errNoHugePageSupport {
|
if _, err := MapTemporary(frame); err != errNoHugePageSupport {
|
||||||
t.Fatalf("expected to get errNoHugePageSupport; got %v", err)
|
t.Fatalf("expected to get errNoHugePageSupport; got %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("allocFn returns an error", func(t *testing.T) {
|
t.Run("allocFn returns an error", func(t *testing.T) {
|
||||||
|
defer func() { frameAllocator = nil }()
|
||||||
physPages[0][p4Index] = 0
|
physPages[0][p4Index] = 0
|
||||||
|
|
||||||
expErr := &kernel.Error{Module: "test", Message: "out of memory"}
|
expErr := &kernel.Error{Module: "test", Message: "out of memory"}
|
||||||
|
|
||||||
allocFn := func() (pmm.Frame, *kernel.Error) {
|
SetFrameAllocator(func() (pmm.Frame, *kernel.Error) {
|
||||||
return 0, expErr
|
return 0, expErr
|
||||||
}
|
})
|
||||||
|
|
||||||
if _, err := MapTemporary(frame, allocFn); err != expErr {
|
if _, err := MapTemporary(frame); err != expErr {
|
||||||
t.Fatalf("got unexpected error %v", err)
|
t.Fatalf("got unexpected error %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -39,7 +39,7 @@ type PageDirectoryTable struct {
|
|||||||
// Init can:
|
// Init can:
|
||||||
// - call mem.Memset to clear the frame contents
|
// - call mem.Memset to clear the frame contents
|
||||||
// - setup a recursive mapping for the last table entry to the page itself.
|
// - setup a recursive mapping for the last table entry to the page itself.
|
||||||
func (pdt *PageDirectoryTable) Init(pdtFrame pmm.Frame, allocFn FrameAllocatorFn) *kernel.Error {
|
func (pdt *PageDirectoryTable) Init(pdtFrame pmm.Frame) *kernel.Error {
|
||||||
pdt.pdtFrame = pdtFrame
|
pdt.pdtFrame = pdtFrame
|
||||||
|
|
||||||
// Check active PDT physical address. If it matches the input pdt then
|
// Check active PDT physical address. If it matches the input pdt then
|
||||||
@ -50,7 +50,7 @@ func (pdt *PageDirectoryTable) Init(pdtFrame pmm.Frame, allocFn FrameAllocatorFn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary mapping for the pdt frame so we can work on it
|
// Create a temporary mapping for the pdt frame so we can work on it
|
||||||
pdtPage, err := mapTemporaryFn(pdtFrame, allocFn)
|
pdtPage, err := mapTemporaryFn(pdtFrame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ func (pdt *PageDirectoryTable) Init(pdtFrame pmm.Frame, allocFn FrameAllocatorFn
|
|||||||
// function with the difference that it also supports inactive page PDTs by
|
// function with the difference that it also supports inactive page PDTs by
|
||||||
// establishing a temporary mapping so that Map() can access the inactive PDT
|
// establishing a temporary mapping so that Map() can access the inactive PDT
|
||||||
// entries.
|
// entries.
|
||||||
func (pdt PageDirectoryTable) Map(page Page, frame pmm.Frame, flags PageTableEntryFlag, allocFn FrameAllocatorFn) *kernel.Error {
|
func (pdt PageDirectoryTable) Map(page Page, frame pmm.Frame, flags PageTableEntryFlag) *kernel.Error {
|
||||||
var (
|
var (
|
||||||
activePdtFrame = pmm.Frame(activePDTFn() >> mem.PageShift)
|
activePdtFrame = pmm.Frame(activePDTFn() >> mem.PageShift)
|
||||||
lastPdtEntryAddr uintptr
|
lastPdtEntryAddr uintptr
|
||||||
@ -89,7 +89,7 @@ func (pdt PageDirectoryTable) Map(page Page, frame pmm.Frame, flags PageTableEnt
|
|||||||
flushTLBEntryFn(lastPdtEntryAddr)
|
flushTLBEntryFn(lastPdtEntryAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mapFn(page, frame, flags, allocFn)
|
err := mapFn(page, frame, flags)
|
||||||
|
|
||||||
if activePdtFrame != pdt.pdtFrame {
|
if activePdtFrame != pdt.pdtFrame {
|
||||||
lastPdtEntry.SetFrame(activePdtFrame)
|
lastPdtEntry.SetFrame(activePdtFrame)
|
||||||
|
@ -15,7 +15,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
t.Skip("test requires amd64 runtime; skipping")
|
t.Skip("test requires amd64 runtime; skipping")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(origFlushTLBEntry func(uintptr), origActivePDT func() uintptr, origMapTemporary func(pmm.Frame, FrameAllocatorFn) (Page, *kernel.Error), origUnmap func(Page) *kernel.Error) {
|
defer func(origFlushTLBEntry func(uintptr), origActivePDT func() uintptr, origMapTemporary func(pmm.Frame) (Page, *kernel.Error), origUnmap func(Page) *kernel.Error) {
|
||||||
flushTLBEntryFn = origFlushTLBEntry
|
flushTLBEntryFn = origFlushTLBEntry
|
||||||
activePDTFn = origActivePDT
|
activePDTFn = origActivePDT
|
||||||
mapTemporaryFn = origMapTemporary
|
mapTemporaryFn = origMapTemporary
|
||||||
@ -32,7 +32,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
return pdtFrame.Address()
|
return pdtFrame.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
mapTemporaryFn = func(_ pmm.Frame, _ FrameAllocatorFn) (Page, *kernel.Error) {
|
mapTemporaryFn = func(_ pmm.Frame) (Page, *kernel.Error) {
|
||||||
t.Fatal("unexpected call to MapTemporary")
|
t.Fatal("unexpected call to MapTemporary")
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdt.Init(pdtFrame, nil); err != nil {
|
if err := pdt.Init(pdtFrame); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -61,7 +61,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
mapTemporaryFn = func(_ pmm.Frame, _ FrameAllocatorFn) (Page, *kernel.Error) {
|
mapTemporaryFn = func(_ pmm.Frame) (Page, *kernel.Error) {
|
||||||
return PageFromAddress(uintptr(unsafe.Pointer(&physPage[0]))), nil
|
return PageFromAddress(uintptr(unsafe.Pointer(&physPage[0]))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdt.Init(pdtFrame, nil); err != nil {
|
if err := pdt.Init(pdtFrame); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
|
|
||||||
expErr := &kernel.Error{Module: "test", Message: "error mapping page"}
|
expErr := &kernel.Error{Module: "test", Message: "error mapping page"}
|
||||||
|
|
||||||
mapTemporaryFn = func(_ pmm.Frame, _ FrameAllocatorFn) (Page, *kernel.Error) {
|
mapTemporaryFn = func(_ pmm.Frame) (Page, *kernel.Error) {
|
||||||
return 0, expErr
|
return 0, expErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ func TestPageDirectoryTableInitAmd64(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdt.Init(pdtFrame, nil); err != expErr {
|
if err := pdt.Init(pdtFrame); err != expErr {
|
||||||
t.Fatalf("expected to get error: %v; got %v", *expErr, err)
|
t.Fatalf("expected to get error: %v; got %v", *expErr, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -130,7 +130,7 @@ func TestPageDirectoryTableMapAmd64(t *testing.T) {
|
|||||||
t.Skip("test requires amd64 runtime; skipping")
|
t.Skip("test requires amd64 runtime; skipping")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(origFlushTLBEntry func(uintptr), origActivePDT func() uintptr, origMap func(Page, pmm.Frame, PageTableEntryFlag, FrameAllocatorFn) *kernel.Error) {
|
defer func(origFlushTLBEntry func(uintptr), origActivePDT func() uintptr, origMap func(Page, pmm.Frame, PageTableEntryFlag) *kernel.Error) {
|
||||||
flushTLBEntryFn = origFlushTLBEntry
|
flushTLBEntryFn = origFlushTLBEntry
|
||||||
activePDTFn = origActivePDT
|
activePDTFn = origActivePDT
|
||||||
mapFn = origMap
|
mapFn = origMap
|
||||||
@ -147,7 +147,7 @@ func TestPageDirectoryTableMapAmd64(t *testing.T) {
|
|||||||
return pdtFrame.Address()
|
return pdtFrame.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
mapFn = func(_ Page, _ pmm.Frame, _ PageTableEntryFlag, _ FrameAllocatorFn) *kernel.Error {
|
mapFn = func(_ Page, _ pmm.Frame, _ PageTableEntryFlag) *kernel.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ func TestPageDirectoryTableMapAmd64(t *testing.T) {
|
|||||||
flushCallCount++
|
flushCallCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdt.Map(page, pmm.Frame(321), FlagRW, nil); err != nil {
|
if err := pdt.Map(page, pmm.Frame(321), FlagRW); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ func TestPageDirectoryTableMapAmd64(t *testing.T) {
|
|||||||
return activePdtFrame.Address()
|
return activePdtFrame.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
mapFn = func(_ Page, _ pmm.Frame, _ PageTableEntryFlag, _ FrameAllocatorFn) *kernel.Error {
|
mapFn = func(_ Page, _ pmm.Frame, _ PageTableEntryFlag) *kernel.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ func TestPageDirectoryTableMapAmd64(t *testing.T) {
|
|||||||
flushCallCount++
|
flushCallCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdt.Map(page, pmm.Frame(321), FlagRW, nil); err != nil {
|
if err := pdt.Map(page, pmm.Frame(321), FlagRW); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
kernel/mem/vmm/vmm.go
Normal file
17
kernel/mem/vmm/vmm.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package vmm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/achilleasa/gopher-os/kernel"
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var frameAllocator FrameAllocatorFn
|
||||||
|
|
||||||
|
// FrameAllocatorFn is a function that can allocate physical frames.
|
||||||
|
type FrameAllocatorFn func() (pmm.Frame, *kernel.Error)
|
||||||
|
|
||||||
|
// SetFrameAllocator registers a frame allocator function that will be used by
|
||||||
|
// the vmm code when new physical frames need to be allocated.
|
||||||
|
func SetFrameAllocator(allocFn FrameAllocatorFn) {
|
||||||
|
frameAllocator = allocFn
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user