mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #71 from achilleasa/implement-and-use-spinlocks
Implement and use spinlocks
This commit is contained in:
commit
1a7aca464e
4
Makefile
4
Makefile
@ -9,7 +9,6 @@ QEMU ?= qemu-system-x86_64
|
|||||||
|
|
||||||
# If your go is called something else set it on the commandline, like this: make run GO=go1.8
|
# If your go is called something else set it on the commandline, like this: make run GO=go1.8
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS := linux
|
|
||||||
GOARCH := amd64
|
GOARCH := amd64
|
||||||
GOROOT := $(shell $(GO) env GOROOT)
|
GOROOT := $(shell $(GO) env GOROOT)
|
||||||
|
|
||||||
@ -34,6 +33,8 @@ FUZZ_PKG_LIST := src/gopheros/device/acpi/aml
|
|||||||
# FUZZ_PKG_LIST += path-to-pkg
|
# FUZZ_PKG_LIST += path-to-pkg
|
||||||
|
|
||||||
ifeq ($(OS), Linux)
|
ifeq ($(OS), Linux)
|
||||||
|
GOOS := linux
|
||||||
|
|
||||||
MIN_OBJCOPY_VERSION := 2.26.0
|
MIN_OBJCOPY_VERSION := 2.26.0
|
||||||
HAVE_VALID_OBJCOPY := $(shell objcopy -V | head -1 | awk -F ' ' '{print "$(MIN_OBJCOPY_VERSION)\n" $$NF}' | sort -ct. -k1,1n -k2,2n && echo "y")
|
HAVE_VALID_OBJCOPY := $(shell objcopy -V | head -1 | awk -F ' ' '{print "$(MIN_OBJCOPY_VERSION)\n" $$NF}' | sort -ct. -k1,1n -k2,2n && echo "y")
|
||||||
|
|
||||||
@ -190,6 +191,7 @@ lint: lint-check-deps
|
|||||||
--exclude 'x \^ 0 always equals x' \
|
--exclude 'x \^ 0 always equals x' \
|
||||||
--exclude 'dispatchInterrupt is unused' \
|
--exclude 'dispatchInterrupt is unused' \
|
||||||
--exclude 'interruptGateEntries is unused' \
|
--exclude 'interruptGateEntries is unused' \
|
||||||
|
--exclude 'yieldFn is unused' \
|
||||||
src/...
|
src/...
|
||||||
|
|
||||||
lint-check-deps:
|
lint-check-deps:
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"gopheros/kernel/kfmt"
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mm"
|
"gopheros/kernel/mm"
|
||||||
"gopheros/kernel/mm/vmm"
|
"gopheros/kernel/mm/vmm"
|
||||||
|
"gopheros/kernel/sync"
|
||||||
"gopheros/multiboot"
|
"gopheros/multiboot"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -51,6 +52,8 @@ type framePool struct {
|
|||||||
// BitmapAllocator implements a physical frame allocator that tracks frame
|
// BitmapAllocator implements a physical frame allocator that tracks frame
|
||||||
// reservations across the available memory pools using bitmaps.
|
// reservations across the available memory pools using bitmaps.
|
||||||
type BitmapAllocator struct {
|
type BitmapAllocator struct {
|
||||||
|
mutex sync.Spinlock
|
||||||
|
|
||||||
// totalPages tracks the total number of pages across all pools.
|
// totalPages tracks the total number of pages across all pools.
|
||||||
totalPages uint32
|
totalPages uint32
|
||||||
|
|
||||||
@ -240,6 +243,8 @@ func (alloc *BitmapAllocator) printStats() {
|
|||||||
// AllocFrame reserves and returns a physical memory frame. An error will be
|
// AllocFrame reserves and returns a physical memory frame. An error will be
|
||||||
// returned if no more memory can be allocated.
|
// returned if no more memory can be allocated.
|
||||||
func (alloc *BitmapAllocator) AllocFrame() (mm.Frame, *kernel.Error) {
|
func (alloc *BitmapAllocator) AllocFrame() (mm.Frame, *kernel.Error) {
|
||||||
|
alloc.mutex.Acquire()
|
||||||
|
|
||||||
for poolIndex := 0; poolIndex < len(alloc.pools); poolIndex++ {
|
for poolIndex := 0; poolIndex < len(alloc.pools); poolIndex++ {
|
||||||
if alloc.pools[poolIndex].freeCount == 0 {
|
if alloc.pools[poolIndex].freeCount == 0 {
|
||||||
continue
|
continue
|
||||||
@ -260,11 +265,13 @@ func (alloc *BitmapAllocator) AllocFrame() (mm.Frame, *kernel.Error) {
|
|||||||
alloc.pools[poolIndex].freeCount--
|
alloc.pools[poolIndex].freeCount--
|
||||||
alloc.pools[poolIndex].freeBitmap[blockIndex] |= mask
|
alloc.pools[poolIndex].freeBitmap[blockIndex] |= mask
|
||||||
alloc.reservedPages++
|
alloc.reservedPages++
|
||||||
|
alloc.mutex.Release()
|
||||||
return alloc.pools[poolIndex].startFrame + mm.Frame((blockIndex<<6)+blockOffset), nil
|
return alloc.pools[poolIndex].startFrame + mm.Frame((blockIndex<<6)+blockOffset), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alloc.mutex.Release()
|
||||||
return mm.InvalidFrame, errBitmapAllocOutOfMemory
|
return mm.InvalidFrame, errBitmapAllocOutOfMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,8 +279,11 @@ func (alloc *BitmapAllocator) AllocFrame() (mm.Frame, *kernel.Error) {
|
|||||||
// Trying to release a frame not part of the allocator pools or a frame that
|
// 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.
|
// is already marked as free will cause an error to be returned.
|
||||||
func (alloc *BitmapAllocator) FreeFrame(frame mm.Frame) *kernel.Error {
|
func (alloc *BitmapAllocator) FreeFrame(frame mm.Frame) *kernel.Error {
|
||||||
|
alloc.mutex.Acquire()
|
||||||
|
|
||||||
poolIndex := alloc.poolForFrame(frame)
|
poolIndex := alloc.poolForFrame(frame)
|
||||||
if poolIndex < 0 {
|
if poolIndex < 0 {
|
||||||
|
alloc.mutex.Release()
|
||||||
return errBitmapAllocFrameNotManaged
|
return errBitmapAllocFrameNotManaged
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,11 +292,13 @@ func (alloc *BitmapAllocator) FreeFrame(frame mm.Frame) *kernel.Error {
|
|||||||
mask := uint64(1 << (63 - (relFrame - block<<6)))
|
mask := uint64(1 << (63 - (relFrame - block<<6)))
|
||||||
|
|
||||||
if alloc.pools[poolIndex].freeBitmap[block]&mask == 0 {
|
if alloc.pools[poolIndex].freeBitmap[block]&mask == 0 {
|
||||||
|
alloc.mutex.Release()
|
||||||
return errBitmapAllocDoubleFree
|
return errBitmapAllocDoubleFree
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc.pools[poolIndex].freeBitmap[block] &^= mask
|
alloc.pools[poolIndex].freeBitmap[block] &^= mask
|
||||||
alloc.pools[poolIndex].freeCount++
|
alloc.pools[poolIndex].freeCount++
|
||||||
alloc.reservedPages--
|
alloc.reservedPages--
|
||||||
|
alloc.mutex.Release()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
38
src/gopheros/kernel/sync/spinlock.go
Normal file
38
src/gopheros/kernel/sync/spinlock.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Package sync provides synchronization primitive implementations for spinlocks
|
||||||
|
// and semaphore.
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO: replace with real yield function when context-switching is implemented.
|
||||||
|
yieldFn func()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spinlock implements a lock where each task trying to acquire it busy-waits
|
||||||
|
// till the lock becomes available.
|
||||||
|
type Spinlock struct {
|
||||||
|
state uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire blocks until the lock can be acquired by the currently active task.
|
||||||
|
// Any attempt to re-acquire a lock already held by the current task will cause
|
||||||
|
// a deadlock.
|
||||||
|
func (l *Spinlock) Acquire() {
|
||||||
|
archAcquireSpinlock(&l.state, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryToAcquire attempts to acquire the lock and returns true if the lock could
|
||||||
|
// be acquired or false otherwise.
|
||||||
|
func (l *Spinlock) TryToAcquire() bool {
|
||||||
|
return atomic.SwapUint32(&l.state, 1) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release relinquishes a held lock allowing other tasks to acquire it. Calling
|
||||||
|
// Release while the lock is free has no effect.
|
||||||
|
func (l *Spinlock) Release() {
|
||||||
|
atomic.StoreUint32(&l.state, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// archAcquireSpinlock is an arch-specific implementation for acquiring the lock.
|
||||||
|
func archAcquireSpinlock(state *uint32, attemptsBeforeYielding uint32)
|
41
src/gopheros/kernel/sync/spinlock_amd64.s
Normal file
41
src/gopheros/kernel/sync/spinlock_amd64.s
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·archAcquireSpinlock(SB),NOSPLIT,$0-12
|
||||||
|
MOVQ state+0(FP), AX
|
||||||
|
MOVL attemptsBeforeYielding+8(FP), CX
|
||||||
|
|
||||||
|
try_acquire:
|
||||||
|
MOVL $1, BX
|
||||||
|
XCHGL 0(AX), BX
|
||||||
|
TESTL BX, BX
|
||||||
|
JNZ spin
|
||||||
|
|
||||||
|
// Lock succesfully acquired
|
||||||
|
RET
|
||||||
|
|
||||||
|
spin:
|
||||||
|
// Send hint to the CPU that we are in a spinlock loop
|
||||||
|
PAUSE
|
||||||
|
|
||||||
|
// Do a dirty read to check the state and try to acquire the lock
|
||||||
|
// once we detect it is free
|
||||||
|
MOVL 0(AX), BX
|
||||||
|
TESTL BX, BX
|
||||||
|
JZ try_acquire
|
||||||
|
|
||||||
|
// Keep retrying till we exceed attemptsBeforeYielding; this allows us
|
||||||
|
// to grab the lock if a task on another CPU releases the lock while we
|
||||||
|
// spin.
|
||||||
|
DECL CX
|
||||||
|
JNZ spin
|
||||||
|
|
||||||
|
// Yield (if yieldFn is set) and spin again
|
||||||
|
MOVQ ·yieldFn+0(SB), AX
|
||||||
|
TESTQ AX, AX
|
||||||
|
JZ replenish_attempt_counter
|
||||||
|
CALL 0(AX)
|
||||||
|
|
||||||
|
replenish_attempt_counter:
|
||||||
|
MOVQ state+0(FP), AX
|
||||||
|
MOVL attemptsBeforeYielding+8(FP), CX
|
||||||
|
JMP spin
|
39
src/gopheros/kernel/sync/spinlock_test.go
Normal file
39
src/gopheros/kernel/sync/spinlock_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpinlock(t *testing.T) {
|
||||||
|
// Substitute the yieldFn with runtime.Gosched to avoid deadlocks while testing
|
||||||
|
defer func(origYieldFn func()) { yieldFn = origYieldFn }(yieldFn)
|
||||||
|
yieldFn = runtime.Gosched
|
||||||
|
|
||||||
|
var (
|
||||||
|
sl Spinlock
|
||||||
|
wg sync.WaitGroup
|
||||||
|
numWorkers = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
sl.Acquire()
|
||||||
|
|
||||||
|
if sl.TryToAcquire() != false {
|
||||||
|
t.Error("expected TryToAcquire to return false when lock is held")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(numWorkers)
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
go func(worker int) {
|
||||||
|
sl.Acquire()
|
||||||
|
sl.Release()
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(100 * time.Millisecond)
|
||||||
|
sl.Release()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user