mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
sync: implement spinlock primitive
This commit is contained in:
parent
eb0ef4bcc0
commit
50cabf1d95
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