Files
visor/runsc/cgroup/cgroup_v2_unsafe.go
Etienne Perot 2d5d753c32 Spawn subprocesses in target cgroup with clone3(CLONE_INTO_CGROUP).
Prior to this change, sandbox subprocesses (`runsc boot` and `runsc gofer`)
were spawned into their target cgroup by having the `runsc create` process
switch into the target cgroup and fork/exec. This stalls for an RCU grace
period. `clone3(CLONE_INTO_CGROUP)` does this in a single operation, and
does not stall.

Benchmarks:

```
$ runsc do /bin/true

                 │    before     │                 after                 │
                 │    sec/op     │    sec/op     vs base                 │
RunscDo            100.16m ±  2%   69.03m ±  1%  -31.09% (n=256)

Basic OCI operations:

                 │    before     │                 after                 │
                 │    sec/op     │    sec/op     vs base                 │
OCICreate           55.59m ±  3%   26.46m ±  1%  -52.40% (n=256)
OCIReady            70.44m ±  3%   40.48m ±  1%  -42.53% (n=256)
OCIStart            14.17m ±  2%   13.87m ±  2%   -2.17% (p=0.033 n=256)
OCITotal            77.88m ±  2%   47.99m ±  1%  -38.38% (n=256)
```

`docker run` unaffected as it uses the systemd cgroup controller.
2026-08-14 09:53:37 -07:00

64 lines
2.3 KiB
Go

// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cgroup
import (
"fmt"
"math"
"sync"
"unsafe"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
)
// cannotCloneIntoCgroupReason returns the reason why
// `clone3(CLONE_INTO_CGROUP)` doesn't work, if any.
// Returns empty string if it does work.
var cannotCloneIntoCgroupReason = sync.OnceValue(func() string {
// This makes a `clone3` call that always fails, but can fail for
// different reasons. See the `switch` below.
args := linux.CloneArgs{
// We use CLONE_VM here too because otherwise the kernel will copy the
// process page table entries before it gets to the cgroup FD check
// part, which can be expensive.
Flags: linux.CLONE_INTO_CGROUP | linux.CLONE_VM,
ExitSignal: uint64(unix.SIGCHLD),
Cgroup: math.MaxInt32,
}
_, _, errno := unix.RawSyscall(unix.SYS_CLONE3, uintptr(unsafe.Pointer(&args)), unsafe.Sizeof(args), 0)
switch errno {
case unix.ENOSYS:
// `clone3` doesn't exist, i.e. Linux 5.2 or lower.
// This can also happen under Docker's default profile, which injects
// ENOSYS as the return code for `clone3`.
return "clone3 syscall not implemented (kernel too old, or clone3 denied by container runtime policy)"
case unix.EPERM:
// Seccomp denial as well, such as Docker's profile before it
// explicitly injected `ENOSYS` for `clone3`.
return "clone3 syscall denied (perhaps by container runtime policy)"
case unix.E2BIG:
// The `Cgroup` field is beyond the struct size that `clone3`
// expects. So `clone3` exists, but `CLONE_INTO_CGROUP` does not.
// Linux 5.3, 5.4, 5.5, or 5.6.
return "kernel too old"
case unix.EBADF:
return "" // Happy case
default:
return fmt.Sprintf("clone3 probe returned errno %v", errno)
}
})