Skip to content

Commit 2f64268

Browse files
kolyshkingopherbot
authored andcommitted
os: make use of pidfd on linux
Use Process.handle field to store pidfd, and make use of it. Only use pidfd functionality if all the needed syscalls are available. 1. Add/use pidfdWorks, which checks that all needed pidfd-related functionality works. 2. os.StartProcess: obtain the pidfd from the kernel, if possible, using the functionality added by CL 520266. Note we could not modify syscall.StartProcess to return pidfd directly because it is a public API and its callers do not expect it, so we have to use ensurePidfd and getPidfd. 3. (*Process).Kill: use pidfdSendSignal, if available and the pidfd is known. Otherwise, fall back to the old implementation. 4. (*Process).Wait: use pidfdWait, if available, otherwise fall back to using waitid/wait4. This is more complicated than expected due to struct siginfo_t idiosyncrasy. NOTE pidfdSendSignal and pidfdWait are used without a race workaround (blockUntilWaitable and sigMu, added by CL 23967) because with pidfd, PID recycle issue doesn't exist (IOW, pidfd, unlike PID, is guaranteed to refer to one particular process) and thus the race doesn't exist either. Rework of CL 528438 (reverted in CL 566477 because of #65857). For #62654. Updates #13987. Change-Id: If5ef8920bd8619dc428b6282ffe4fb8c258ca224 Reviewed-on: https://go-review.googlesource.com/c/go/+/570036 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Kirill Kolyshkin <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Cherry Mui <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 69105d7 commit 2f64268

20 files changed

+393
-16
lines changed

src/internal/syscall/unix/pidfd_linux.go

+8
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@ func PidFDSendSignal(pidfd uintptr, s syscall.Signal) error {
1313
}
1414
return nil
1515
}
16+
17+
func PidFDOpen(pid, flags int) (uintptr, error) {
18+
pidfd, _, errno := syscall.Syscall(pidfdOpenTrap, uintptr(pid), uintptr(flags), 0)
19+
if errno != 0 {
20+
return ^uintptr(0), errno
21+
}
22+
return uintptr(pidfd), nil
23+
}
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unix
6+
7+
import (
8+
"syscall"
9+
)
10+
11+
const is64bit = ^uint(0) >> 63 // 0 for 32-bit hosts, 1 for 64-bit ones.
12+
13+
// SiginfoChild is a struct filled in by Linux waitid syscall.
14+
// In C, siginfo_t contains a union with multiple members;
15+
// this struct corresponds to one used when Signo is SIGCHLD.
16+
//
17+
// NOTE fields are exported to be used by TestSiginfoChildLayout.
18+
type SiginfoChild struct {
19+
Signo int32
20+
siErrnoCode // Two int32 fields, swapped on MIPS.
21+
_ [is64bit]int32 // Extra padding for 64-bit hosts only.
22+
23+
// End of common part. Beginning of signal-specific part.
24+
25+
Pid int32
26+
Uid uint32
27+
Status int32
28+
29+
// Pad to 128 bytes.
30+
_ [128 - (6+is64bit)*4]byte
31+
}
32+
33+
const (
34+
// Possible values for SiginfoChild.Code field.
35+
_CLD_EXITED int32 = 1
36+
_CLD_KILLED = 2
37+
_CLD_DUMPED = 3
38+
_CLD_TRAPPED = 4
39+
_CLD_STOPPED = 5
40+
_CLD_CONTINUED = 6
41+
42+
// These are the same as in syscall/syscall_linux.go.
43+
core = 0x80
44+
stopped = 0x7f
45+
continued = 0xffff
46+
)
47+
48+
// WaitStatus converts SiginfoChild, as filled in by the waitid syscall,
49+
// to syscall.WaitStatus.
50+
func (s *SiginfoChild) WaitStatus() (ws syscall.WaitStatus) {
51+
switch s.Code {
52+
case _CLD_EXITED:
53+
ws = syscall.WaitStatus(s.Status << 8)
54+
case _CLD_DUMPED:
55+
ws = syscall.WaitStatus(s.Status) | core
56+
case _CLD_KILLED:
57+
ws = syscall.WaitStatus(s.Status)
58+
case _CLD_TRAPPED, _CLD_STOPPED:
59+
ws = syscall.WaitStatus(s.Status<<8) | stopped
60+
case _CLD_CONTINUED:
61+
ws = continued
62+
}
63+
return
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build linux && (mips || mipsle || mips64 || mips64le)
6+
7+
package unix
8+
9+
type siErrnoCode struct {
10+
Code int32
11+
Errno int32
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build linux && !(mips || mipsle || mips64 || mips64le)
6+
7+
package unix
8+
9+
type siErrnoCode struct {
10+
Errno int32
11+
Code int32
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unix_test
6+
7+
import (
8+
"internal/goarch"
9+
"internal/syscall/unix"
10+
"runtime"
11+
"strings"
12+
"testing"
13+
"unsafe"
14+
)
15+
16+
// TestSiginfoChildLayout validates SiginfoChild layout. Modelled after
17+
// static assertions in linux kernel's arch/*/kernel/signal*.c.
18+
func TestSiginfoChildLayout(t *testing.T) {
19+
var si unix.SiginfoChild
20+
21+
const host64bit = goarch.PtrSize == 8
22+
23+
if v := unsafe.Sizeof(si); v != 128 {
24+
t.Fatalf("sizeof: got %d, want 128", v)
25+
}
26+
27+
ofSigno := 0
28+
ofErrno := 4
29+
ofCode := 8
30+
if strings.HasPrefix(runtime.GOARCH, "mips") {
31+
// These two fields are swapped on MIPS platforms.
32+
ofErrno, ofCode = ofCode, ofErrno
33+
}
34+
ofPid := 12
35+
if host64bit {
36+
ofPid = 16
37+
}
38+
ofUid := ofPid + 4
39+
ofStatus := ofPid + 8
40+
41+
offsets := []struct {
42+
name string
43+
got uintptr
44+
want int
45+
}{
46+
{"Signo", unsafe.Offsetof(si.Signo), ofSigno},
47+
{"Errno", unsafe.Offsetof(si.Errno), ofErrno},
48+
{"Code", unsafe.Offsetof(si.Code), ofCode},
49+
{"Pid", unsafe.Offsetof(si.Pid), ofPid},
50+
{"Uid", unsafe.Offsetof(si.Uid), ofUid},
51+
{"Status", unsafe.Offsetof(si.Status), ofStatus},
52+
}
53+
54+
for _, tc := range offsets {
55+
if int(tc.got) != tc.want {
56+
t.Errorf("offsetof %s: got %d, want %d", tc.name, tc.got, tc.want)
57+
}
58+
}
59+
}

src/internal/syscall/unix/sysnum_linux_386.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const (
88
getrandomTrap uintptr = 355
99
copyFileRangeTrap uintptr = 377
1010
pidfdSendSignalTrap uintptr = 424
11+
pidfdOpenTrap uintptr = 434
1112
)

src/internal/syscall/unix/sysnum_linux_amd64.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const (
88
getrandomTrap uintptr = 318
99
copyFileRangeTrap uintptr = 326
1010
pidfdSendSignalTrap uintptr = 424
11+
pidfdOpenTrap uintptr = 434
1112
)

src/internal/syscall/unix/sysnum_linux_arm.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const (
88
getrandomTrap uintptr = 384
99
copyFileRangeTrap uintptr = 391
1010
pidfdSendSignalTrap uintptr = 424
11+
pidfdOpenTrap uintptr = 434
1112
)

src/internal/syscall/unix/sysnum_linux_generic.go

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ const (
1414
getrandomTrap uintptr = 278
1515
copyFileRangeTrap uintptr = 285
1616
pidfdSendSignalTrap uintptr = 424
17+
pidfdOpenTrap uintptr = 434
1718
)

src/internal/syscall/unix/sysnum_linux_mips64x.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
getrandomTrap uintptr = 5313
1111
copyFileRangeTrap uintptr = 5320
1212
pidfdSendSignalTrap uintptr = 5424
13+
pidfdOpenTrap uintptr = 5434
1314
)

src/internal/syscall/unix/sysnum_linux_mipsx.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
getrandomTrap uintptr = 4353
1111
copyFileRangeTrap uintptr = 4360
1212
pidfdSendSignalTrap uintptr = 4424
13+
pidfdOpenTrap uintptr = 4434
1314
)

src/internal/syscall/unix/sysnum_linux_ppc64x.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
getrandomTrap uintptr = 359
1111
copyFileRangeTrap uintptr = 379
1212
pidfdSendSignalTrap uintptr = 424
13+
pidfdOpenTrap uintptr = 434
1314
)

src/internal/syscall/unix/sysnum_linux_s390x.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const (
88
getrandomTrap uintptr = 349
99
copyFileRangeTrap uintptr = 375
1010
pidfdSendSignalTrap uintptr = 424
11+
pidfdOpenTrap uintptr = 434
1112
)

src/os/exec.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ var ErrProcessDone = errors.New("os: process already finished")
2020
// Process stores the information about a process created by [StartProcess].
2121
type Process struct {
2222
Pid int
23-
handle atomic.Uintptr
24-
isdone atomic.Bool // process has been successfully waited on
25-
sigMu sync.RWMutex // avoid race between wait and signal
23+
handle atomic.Uintptr // Process handle for Windows, pidfd for Linux.
24+
isdone atomic.Bool // process has been successfully waited on
25+
sigMu sync.RWMutex // avoid race between wait and signal
2626
}
2727

2828
func newProcess(pid int, handle uintptr) *Process {

src/os/exec_posix.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"syscall"
1414
)
1515

16+
// unsetHandle is a value for Process.handle used when the handle is not set.
17+
// Same as syscall.InvalidHandle for Windows.
18+
const unsetHandle = ^uintptr(0)
19+
1620
// The only signal values guaranteed to be present in the os package on all
1721
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
1822
// the process to exit). On Windows, sending os.Interrupt to a process with
@@ -38,7 +42,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
3842
sysattr := &syscall.ProcAttr{
3943
Dir: attr.Dir,
4044
Env: attr.Env,
41-
Sys: attr.Sys,
45+
Sys: ensurePidfd(attr.Sys),
4246
}
4347
if sysattr.Env == nil {
4448
sysattr.Env, err = execenv.Default(sysattr.Sys)
@@ -60,6 +64,11 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
6064
return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
6165
}
6266

67+
// For Windows, syscall.StartProcess above already returned a process handle.
68+
if runtime.GOOS != "windows" {
69+
h = getPidfd(sysattr.Sys)
70+
}
71+
6372
return newProcess(pid, h), nil
6473
}
6574

src/os/exec_unix.go

+26-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ func (p *Process) wait() (ps *ProcessState, err error) {
1717
if p.Pid == -1 {
1818
return nil, syscall.EINVAL
1919
}
20+
// Wait on pidfd if possible; fallback to using pid on ENOSYS.
21+
//
22+
// When pidfd is used, there is no wait/kill race (described in CL 23967)
23+
// because PID recycle issue doesn't exist (IOW, pidfd, unlike PID, is
24+
// guaranteed to refer to one particular process). Thus, there is no
25+
// need for the workaround (blockUntilWaitable + sigMu) below.
26+
if ps, e := p.pidfdWait(); e != syscall.ENOSYS {
27+
return ps, NewSyscallError("waitid", e)
28+
}
2029

2130
// If we can block until Wait4 will succeed immediately, do so.
2231
ready, err := p.blockUntilWaitable()
@@ -64,26 +73,31 @@ func (p *Process) signal(sig Signal) error {
6473
if p.Pid == 0 {
6574
return errors.New("os: process not initialized")
6675
}
76+
s, ok := sig.(syscall.Signal)
77+
if !ok {
78+
return errors.New("os: unsupported signal type")
79+
}
80+
// Use pidfd if possible; fallback on ENOSYS.
81+
if err := p.pidfdSendSignal(s); err != syscall.ENOSYS {
82+
return err
83+
}
6784
p.sigMu.RLock()
6885
defer p.sigMu.RUnlock()
6986
if p.done() {
7087
return ErrProcessDone
7188
}
72-
s, ok := sig.(syscall.Signal)
73-
if !ok {
74-
return errors.New("os: unsupported signal type")
75-
}
76-
if e := syscall.Kill(p.Pid, s); e != nil {
77-
if e == syscall.ESRCH {
78-
return ErrProcessDone
79-
}
80-
return e
89+
return convertESRCH(syscall.Kill(p.Pid, s))
90+
}
91+
92+
func convertESRCH(err error) error {
93+
if err == syscall.ESRCH {
94+
return ErrProcessDone
8195
}
82-
return nil
96+
return err
8397
}
8498

8599
func (p *Process) release() error {
86-
// NOOP for unix.
100+
p.pidfdRelease()
87101
p.Pid = -1
88102
// no need for a finalizer anymore
89103
runtime.SetFinalizer(p, nil)
@@ -92,7 +106,7 @@ func (p *Process) release() error {
92106

93107
func findProcess(pid int) (p *Process, err error) {
94108
// NOOP for unix.
95-
return newProcess(pid, 0), nil
109+
return newProcess(pid, unsetHandle), nil
96110
}
97111

98112
func (p *ProcessState) userTime() time.Duration {

src/os/export_linux_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ var (
88
PollCopyFileRangeP = &pollCopyFileRange
99
PollSpliceFile = &pollSplice
1010
GetPollFDAndNetwork = getPollFDAndNetwork
11+
CheckPidfdOnce = checkPidfdOnce
1112
)

0 commit comments

Comments
 (0)