Skip to content

Commit

Permalink
runtime: use vDSO for getrandom() on linux
Browse files Browse the repository at this point in the history
Linux 6.11 supports calling getrandom() from the vDSO. It operates on a
thread-local opaque state allocated with mmap using flags specified by
the vDSO.

Opaque states are allocated in chunks, ideally ncpu at a time as a hint,
rounding up to as many fit in a complete page. On first use, a state is
assigned to an m, which owns that state, until the m exits, at which
point it is given back to the pool.

Performance appears to be quite good:

           │    sec/op    │   sec/op       vs base                 │
Read/4-16    222.45n ± 3%   27.13n   ± 6%  -87.80% (p=0.000 n=10)
           │     B/s      │      B/s       vs base                 │
Read/4-16    17.15Mi ± 3%   140.61Mi ± 6%  +719.82% (p=0.000 n=10)

Fixes #69577.

Change-Id: Ib6f44e8f2f3940c94d970eaada0eb566ec297dc7
Reviewed-on: https://go-review.googlesource.com/c/go/+/614835
Reviewed-by: Filippo Valsorda <[email protected]>
Reviewed-by: Cuong Manh Le <[email protected]>
Auto-Submit: Jason Donenfeld <[email protected]>
Reviewed-by: Paul Murphy <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: David Chase <[email protected]>
Reviewed-by: Michael Pratt <[email protected]>
  • Loading branch information
zx2c4 authored and gopherbot committed Sep 28, 2024
1 parent 677b6cc commit eb6f2c2
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/crypto/rand/rand_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func TestReadEmpty(t *testing.T) {
}

func BenchmarkRead(b *testing.B) {
b.Run("4", func(b *testing.B) {
benchmarkRead(b, 4)
})
b.Run("32", func(b *testing.B) {
benchmarkRead(b, 32)
})
Expand Down
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/getrandom.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ import (
"unsafe"
)

//go:linkname vgetrandom runtime.vgetrandom
func vgetrandom(p []byte, flags uint32) (ret int, supported bool)

var getrandomUnsupported atomic.Bool

// GetRandomFlag is a flag supported by the getrandom system call.
type GetRandomFlag uintptr

// GetRandom calls the getrandom system call.
func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) {
ret, supported := vgetrandom(p, uint32(flags))
if supported {
if ret < 0 {
return 0, syscall.Errno(-ret)
}
return ret, nil
}
if getrandomUnsupported.Load() {
return 0, syscall.ENOSYS
}
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type mOS struct {
// needPerThreadSyscall indicates that a per-thread syscall is required
// for doAllThreadsSyscall.
needPerThreadSyscall atomic.Uint8

// This is a pointer to a chunk of memory allocated with a special
// mmap invocation in vgetrandomGetState().
vgetrandomState uintptr
}

//go:noescape
Expand Down Expand Up @@ -344,6 +348,7 @@ func osinit() {
ncpu = getproccount()
physHugePageSize = getHugePageSize()
osArchInit()
vgetrandomInit()
}

var urandom_dev = []byte("/dev/urandom\x00")
Expand Down Expand Up @@ -400,6 +405,10 @@ func unminit() {
// Called from exitm, but not from drop, to undo the effect of thread-owned
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
func mdestroy(mp *m) {
if mp.vgetrandomState != 0 {
vgetrandomPutState(mp.vgetrandomState)
mp.vgetrandomState = 0
}
}

// #ifdef GOARCH_386
Expand Down
33 changes: 33 additions & 0 deletions src/runtime/sys_linux_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,36 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-8
SYSCALL
MOVQ AX, ret+0(FP)
RET

// func vgetrandom1(buf *byte, length uintptr, flags uint32, state uintptr, stateSize uintptr) int
TEXT runtime·vgetrandom1<ABIInternal>(SB),NOSPLIT,$16-48
MOVQ SI, R8 // stateSize
MOVL CX, DX // flags
MOVQ DI, CX // state
MOVQ BX, SI // length
MOVQ AX, DI // buf

MOVQ SP, R12

MOVQ runtime·vdsoGetrandomSym(SB), AX
MOVQ g_m(R14), BX

MOVQ m_vdsoPC(BX), R9
MOVQ R9, 0(SP)
MOVQ m_vdsoSP(BX), R9
MOVQ R9, 8(SP)
LEAQ buf+0(FP), R9
MOVQ R9, m_vdsoSP(BX)
MOVQ -8(R9), R9
MOVQ R9, m_vdsoPC(BX)

ANDQ $~15, SP

CALL AX

MOVQ R12, SP
MOVQ 8(SP), R9
MOVQ R9, m_vdsoSP(BX)
MOVQ 0(SP), R9
MOVQ R9, m_vdsoPC(BX)
RET
45 changes: 45 additions & 0 deletions src/runtime/sys_linux_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,48 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-8
SVC
MOVD R0, ret+0(FP)
RET

// func vgetrandom1(buf *byte, length uintptr, flags uint32, state uintptr, stateSize uintptr) int
TEXT runtime·vgetrandom1<ABIInternal>(SB),NOSPLIT,$16-48
MOVD RSP, R20

MOVD runtime·vdsoGetrandomSym(SB), R8
MOVD g_m(g), R21

MOVD m_vdsoPC(R21), R9
MOVD R9, 8(RSP)
MOVD m_vdsoSP(R21), R9
MOVD R9, 16(RSP)
MOVD LR, m_vdsoPC(R21)
MOVD $buf-8(FP), R9
MOVD R9, m_vdsoSP(R21)

MOVD RSP, R9
BIC $15, R9
MOVD R9, RSP

MOVBU runtime·iscgo(SB), R9
CBNZ R9, nosaveg
MOVD m_gsignal(R21), R9
CBZ R9, nosaveg
CMP g, R9
BEQ nosaveg
MOVD (g_stack+stack_lo)(R9), R22
MOVD g, (R22)

BL (R8)

MOVD ZR, (R22)
B restore

nosaveg:
BL (R8)

restore:
MOVD R20, RSP
MOVD 16(RSP), R1
MOVD R1, m_vdsoSP(R21)
MOVD 8(RSP), R1
MOVD R1, m_vdsoPC(R21)
NOP R0 // Satisfy go vet, since the return value comes from the vDSO function.
RET
43 changes: 43 additions & 0 deletions src/runtime/sys_linux_loong64.s
Original file line number Diff line number Diff line change
Expand Up @@ -657,3 +657,46 @@ TEXT runtime·socket(SB),$0-20
MOVV R0, 2(R0) // unimplemented, only needed for android; declared in stubs_linux.go
MOVW R0, ret+16(FP) // for vet
RET

// func vgetrandom1(buf *byte, length uintptr, flags uint32, state uintptr, stateSize uintptr) int
TEXT runtime·vgetrandom1<ABIInternal>(SB),NOSPLIT,$16-48
MOVV R3, R23

MOVV runtime·vdsoGetrandomSym(SB), R12

MOVV g_m(g), R24

MOVV m_vdsoPC(R24), R13
MOVV R13, 8(R3)
MOVV m_vdsoSP(R24), R13
MOVV R13, 16(R3)
MOVV R1, m_vdsoPC(R24)
MOVV $buf-8(FP), R13
MOVV R13, m_vdsoSP(R24)

AND $~15, R3

MOVBU runtime·iscgo(SB), R13
BNE R13, nosaveg
MOVV m_gsignal(R24), R13
BEQ R13, nosaveg
BEQ g, R13, nosaveg
MOVV (g_stack+stack_lo)(R13), R25
MOVV g, (R25)

JAL (R12)

MOVV R0, (R25)
JMP restore

nosaveg:
JAL (R12)

restore:
MOVV R23, R3
MOVV 16(R3), R25
MOVV R25, m_vdsoSP(R24)
MOVV 8(R3), R25
MOVV R25, m_vdsoPC(R24)
NOP R4 // Satisfy go vet, since the return value comes from the vDSO function.
RET
50 changes: 50 additions & 0 deletions src/runtime/sys_linux_ppc64x.s
Original file line number Diff line number Diff line change
Expand Up @@ -757,3 +757,53 @@ TEXT runtime·socket(SB),$0-20
MOVD R0, 0(R0) // unimplemented, only needed for android; declared in stubs_linux.go
MOVW R0, ret+16(FP) // for vet
RET

// func vgetrandom1(buf *byte, length uintptr, flags uint32, state uintptr, stateSize uintptr) int
TEXT runtime·vgetrandom1<ABIInternal>(SB),NOSPLIT,$16-48
MOVD R1, R15

MOVD runtime·vdsoGetrandomSym(SB), R12
MOVD R12, CTR
MOVD g_m(g), R21

MOVD m_vdsoPC(R21), R22
MOVD R22, 32(R1)
MOVD m_vdsoSP(R21), R22
MOVD R22, 40(R1)
MOVD LR, m_vdsoPC(R21)
MOVD $buf-FIXED_FRAME(FP), R22
MOVD R22, m_vdsoSP(R21)

RLDICR $0, R1, $59, R1

MOVBZ runtime·iscgo(SB), R22
CMP R22, $0
BNE nosaveg
MOVD m_gsignal(R21), R22
CMP R22, $0
BEQ nosaveg
CMP R22, g
BEQ nosaveg
MOVD (g_stack+stack_lo)(R22), R22
MOVD g, (R22)

BL (CTR)

MOVD $0, (R22)
JMP restore

nosaveg:
BL (CTR)

restore:
MOVD $0, R0
MOVD R15, R1
MOVD 40(R1), R22
MOVD R22, m_vdsoSP(R21)
MOVD 32(R1), R22
MOVD R22, m_vdsoPC(R21)

BVC out
NEG R3, R3
out:
RET
50 changes: 50 additions & 0 deletions src/runtime/sys_linux_s390x.s
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,53 @@ TEXT runtime·socket(SB),$0-20
MOVD $0, 2(R0) // unimplemented, only needed for android; declared in stubs_linux.go
MOVW R0, ret+16(FP)
RET

// func vgetrandom1(buf *byte, length uintptr, flags uint32, state uintptr, stateSize uintptr) int
TEXT runtime·vgetrandom1(SB),NOSPLIT,$16-48
MOVD buf+0(FP), R2
MOVD length+8(FP), R3
MOVW flags+16(FP), R4
MOVD state+24(FP), R5
MOVD stateSize+32(FP), R6

MOVD R15, R7

MOVD runtime·vdsoGetrandomSym(SB), R1
MOVD g_m(g), R9

MOVD m_vdsoPC(R9), R12
MOVD R12, 8(R15)
MOVD m_vdsoSP(R9), R12
MOVD R12, 16(R15)
MOVD R14, m_vdsoPC(R9)
MOVD $buf+0(FP), R12
MOVD R12, m_vdsoSP(R9)

SUB $160, R15
MOVD $~7, R12
AND R12, R15

MOVB runtime·iscgo(SB), R12
CMPBNE R12, $0, nosaveg
MOVD m_gsignal(R9), R12
CMPBEQ R12, $0, nosaveg
CMPBEQ g, R12, nosaveg
MOVD (g_stack+stack_lo)(R12), R12
MOVD g, (R12)

BL R1

MOVD $0, (R12)
JMP restore

nosaveg:
BL R1

restore:
MOVD R7, R15
MOVD 16(R15), R12
MOVD R12, m_vdsoSP(R9)
MOVD 8(R15), R12
MOVD R12, m_vdsoPC(R9)
MOVD R2, ret+40(FP)
RET
2 changes: 2 additions & 0 deletions src/runtime/vdso_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ var vdsoLinuxVersion = vdsoVersionKey{"LINUX_2.6", 0x3ae75f6}
var vdsoSymbolKeys = []vdsoSymbolKey{
{"__vdso_gettimeofday", 0x315ca59, 0xb01bca00, &vdsoGettimeofdaySym},
{"__vdso_clock_gettime", 0xd35ec75, 0x6e43a318, &vdsoClockgettimeSym},
{"__vdso_getrandom", 0x25425d, 0x84a559bf, &vdsoGetrandomSym},
}

var (
vdsoGettimeofdaySym uintptr
vdsoClockgettimeSym uintptr
vdsoGetrandomSym uintptr
)

// vdsoGettimeofdaySym is accessed from the syscall package.
Expand Down
7 changes: 5 additions & 2 deletions src/runtime/vdso_linux_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ var vdsoLinuxVersion = vdsoVersionKey{"LINUX_2.6.39", 0x75fcb89}

var vdsoSymbolKeys = []vdsoSymbolKey{
{"__kernel_clock_gettime", 0xb0cd725, 0xdfa941fd, &vdsoClockgettimeSym},
{"__kernel_getrandom", 0x9800c0d, 0x540d4e24, &vdsoGetrandomSym},
}

// initialize to fall back to syscall
var vdsoClockgettimeSym uintptr = 0
var (
vdsoClockgettimeSym uintptr
vdsoGetrandomSym uintptr
)
5 changes: 3 additions & 2 deletions src/runtime/vdso_linux_loong64.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ var vdsoLinuxVersion = vdsoVersionKey{"LINUX_5.10", 0xae78f70}

var vdsoSymbolKeys = []vdsoSymbolKey{
{"__vdso_clock_gettime", 0xd35ec75, 0x6e43a318, &vdsoClockgettimeSym},
{"__vdso_getrandom", 0x25425d, 0x84a559bf, &vdsoGetrandomSym},
}

// initialize to fall back to syscall
var (
vdsoClockgettimeSym uintptr = 0
vdsoClockgettimeSym uintptr
vdsoGetrandomSym uintptr
)
5 changes: 3 additions & 2 deletions src/runtime/vdso_linux_ppc64x.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ var vdsoLinuxVersion = vdsoVersionKey{"LINUX_2.6.15", 0x75fcba5}

var vdsoSymbolKeys = []vdsoSymbolKey{
{"__kernel_clock_gettime", 0xb0cd725, 0xdfa941fd, &vdsoClockgettimeSym},
{"__kernel_getrandom", 0x9800c0d, 0x540d4e24, &vdsoGetrandomSym},
}

// initialize with vsyscall fallbacks
var (
vdsoClockgettimeSym uintptr = 0
vdsoClockgettimeSym uintptr
vdsoGetrandomSym uintptr
)
5 changes: 3 additions & 2 deletions src/runtime/vdso_linux_s390x.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ var vdsoLinuxVersion = vdsoVersionKey{"LINUX_2.6.29", 0x75fcbb9}

var vdsoSymbolKeys = []vdsoSymbolKey{
{"__kernel_clock_gettime", 0xb0cd725, 0xdfa941fd, &vdsoClockgettimeSym},
{"__kernel_getrandom", 0x9800c0d, 0x540d4e24, &vdsoGetrandomSym},
}

// initialize with vsyscall fallbacks
var (
vdsoClockgettimeSym uintptr = 0
vdsoClockgettimeSym uintptr
vdsoGetrandomSym uintptr
)
Loading

0 comments on commit eb6f2c2

Please sign in to comment.