Skip to content

Commit 7f3fdc9

Browse files
misterwilliamgvisor-bot
authored andcommitted
Implement core tagging
Links to some possibly useful reference material on core tagging: * LWN article: https://lwn.net/Articles/861251/ * Kernel docs: https://www.kernel.org/doc/Documentation/admin-guide/hw-vuln/core-scheduling.rst PiperOrigin-RevId: 431093418
1 parent 488841f commit 7f3fdc9

File tree

14 files changed

+304
-39
lines changed

14 files changed

+304
-39
lines changed

WORKSPACE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ go_repository(
8888
go_repository(
8989
name = "org_golang_x_sys",
9090
importpath = "golang.org/x/sys",
91-
sum = "h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=",
92-
version = "v0.0.0-20211007075335-d3039528d8ac",
91+
sum = "h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=",
92+
version = "v0.0.0-20220224120231-95c6836cb0e7",
9393
)
9494

9595
go_repository(

pkg/abi/linux/prctl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ const (
145145
// Protection eXtensions (MPX) bounds tables.
146146
PR_MPX_DISABLE_MANAGEMENT = 44
147147

148+
// The following constants are used to control thread scheduling on cores.
149+
PR_SCHED_CORE_SCOPE_THREAD = 0
150+
PR_SCHED_CORE_SCOPE_THREAD_GROUP = 1
151+
148152
// PR_SET_PTRACER allows a specific process (or any, if PR_SET_PTRACER_ANY is
149153
// specified) to ptrace the current task.
150154
PR_SET_PTRACER = 0x59616d61

pkg/coretag/BUILD

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
load("//tools:defs.bzl", "go_library", "go_test")
2+
3+
package(licenses = ["notice"])
4+
5+
go_library(
6+
name = "coretag",
7+
srcs = [
8+
"coretag.go",
9+
"coretag_unsafe.go",
10+
],
11+
visibility = ["//:sandbox"],
12+
deps = [
13+
"//pkg/abi/linux",
14+
"@org_golang_x_sys//unix:go_default_library",
15+
],
16+
)
17+
18+
go_test(
19+
name = "coretag_test",
20+
size = "small",
21+
srcs = [
22+
"coretag_test.go",
23+
],
24+
library = ":coretag",
25+
deps = [
26+
"//pkg/hostos",
27+
],
28+
)

pkg/coretag/coretag.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2022 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package coretag implements core tagging.
16+
package coretag
17+
18+
import (
19+
"fmt"
20+
"io/ioutil"
21+
"strconv"
22+
23+
"golang.org/x/sys/unix"
24+
"gvisor.dev/gvisor/pkg/abi/linux"
25+
)
26+
27+
// Enable core tagging. If this returns with no error, all threads in the
28+
// current thread group will be run in a core tagged thread. Only available on
29+
// linux kernel >= 5.14.
30+
func Enable() error {
31+
// Set core tag on current thread group.
32+
// prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid=0,
33+
// PR_SCHED_CORE_SCOPE_THREAD_GROUP, cookie=nullptr)
34+
// pid=0 means current pid.
35+
// cookie=nullptr is required for PR_SCHED_CORE_CREATE.
36+
if _, _, errno := unix.Syscall6(unix.SYS_PRCTL, unix.PR_SCHED_CORE,
37+
unix.PR_SCHED_CORE_CREATE, 0 /*pid*/, linux.PR_SCHED_CORE_SCOPE_THREAD_GROUP, 0, 0); errno != 0 {
38+
return fmt.Errorf("failed to core tag sentry: %w", errno)
39+
}
40+
return nil
41+
}
42+
43+
// GetAllCoreTags returns the core tag of all the threads in the thread group.
44+
func GetAllCoreTags(pid int) ([]uint64, error) {
45+
// prctl(PR_SCHED_CORE_GET, PR_SCHED_CORE_SCOPE_THREAD_GROUP, ...) is not supported
46+
// in linux. So instead we get all threads from /proc/<pid>/task and get all the
47+
// core tags individually.
48+
tagSet := make(map[uint64]struct{})
49+
// Get current pid core tag.
50+
tag, err := getCoreTag(pid)
51+
if err != nil {
52+
return nil, err
53+
}
54+
tagSet[tag] = struct{}{}
55+
56+
// Get core tags of tids.
57+
tids, err := getTids(pid)
58+
if err != nil {
59+
return nil, err
60+
}
61+
for tid := range tids {
62+
tag, err := getCoreTag(tid)
63+
if err != nil {
64+
return nil, err
65+
}
66+
tagSet[tag] = struct{}{}
67+
}
68+
69+
// Return set of tags as a slice.
70+
tags := make([]uint64, 0, len(tagSet))
71+
for t := range tagSet {
72+
tags = append(tags, t)
73+
}
74+
return tags, nil
75+
}
76+
77+
// getTids returns set of tids as reported by /proc/<pid>/task.
78+
func getTids(pid int) (map[int]struct{}, error) {
79+
tids := make(map[int]struct{})
80+
files, err := ioutil.ReadDir("/proc/" + strconv.Itoa(pid) + "/task")
81+
if err != nil {
82+
return nil, err
83+
}
84+
for _, file := range files {
85+
tid, err := strconv.Atoi(file.Name())
86+
if err != nil {
87+
return nil, err
88+
}
89+
tids[tid] = struct{}{}
90+
}
91+
92+
return tids, nil
93+
}

pkg/coretag/coretag_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2022 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package coretag
16+
17+
import (
18+
"os"
19+
"testing"
20+
21+
"gvisor.dev/gvisor/pkg/hostos"
22+
)
23+
24+
func TestEnable(t *testing.T) {
25+
major, minor, err := hostos.KernelVersion()
26+
if err != nil {
27+
t.Fatalf("Unable to parse kernel version: %v", err)
28+
}
29+
// Skip running test when running on Linux kernel < 5.14 because core tagging
30+
// is not available.
31+
if major < 5 && minor < 14 {
32+
t.Skipf("Running on Linux kernel: %d.%d < 5.14. Core tagging not available. Skipping test.", major, minor)
33+
return
34+
}
35+
if err := Enable(); err != nil {
36+
t.Fatalf("Enable() got error %v, wanted nil", err)
37+
}
38+
39+
coreTags, err := GetAllCoreTags(os.Getpid())
40+
if err != nil {
41+
t.Fatalf("GetAllCoreTags() got error %v, wanted nil", err)
42+
}
43+
if len(coreTags) != 1 {
44+
t.Fatalf("Got coreTags %v, wanted len(coreTags)=1", coreTags)
45+
}
46+
}

pkg/coretag/coretag_unsafe.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2022 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package coretag
16+
17+
import (
18+
"fmt"
19+
"unsafe"
20+
21+
"golang.org/x/sys/unix"
22+
"gvisor.dev/gvisor/pkg/abi/linux"
23+
)
24+
25+
// getCoreTag returns the core tag of the tid. Only available on linux kernel >= 5.14.
26+
func getCoreTag(tid int) (uint64, error) {
27+
var cookie uint64
28+
if _, _, errno := unix.Syscall6(unix.SYS_PRCTL, unix.PR_SCHED_CORE,
29+
unix.PR_SCHED_CORE_GET, uintptr(tid), linux.PR_SCHED_CORE_SCOPE_THREAD,
30+
uintptr(unsafe.Pointer(&cookie)), 0); errno != 0 {
31+
return 0, fmt.Errorf("prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, %d, PR_SCHED_CORE_SCOPE_THREAD) (errno=%d)", tid, errno)
32+
}
33+
return cookie, nil
34+
}

pkg/cpuid/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ go_test(
4242
],
4343
library = ":cpuid",
4444
tags = ["manual"],
45-
deps = ["@org_golang_x_sys//unix:go_default_library"],
45+
deps = ["//pkg/hostos"],
4646
)

pkg/cpuid/cpuid_parse_test.go

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,14 @@
1515
package cpuid
1616

1717
import (
18-
"fmt"
1918
"io/ioutil"
2019
"regexp"
21-
"strconv"
2220
"strings"
2321
"testing"
2422

25-
"golang.org/x/sys/unix"
23+
"gvisor.dev/gvisor/pkg/hostos"
2624
)
2725

28-
func kernelVersion() (int, int, error) {
29-
var u unix.Utsname
30-
if err := unix.Uname(&u); err != nil {
31-
return 0, 0, err
32-
}
33-
34-
var sb strings.Builder
35-
for _, b := range u.Release {
36-
if b == 0 {
37-
break
38-
}
39-
sb.WriteByte(byte(b))
40-
}
41-
42-
s := strings.Split(sb.String(), ".")
43-
if len(s) < 2 {
44-
return 0, 0, fmt.Errorf("kernel release missing major and minor component: %s", sb.String())
45-
}
46-
47-
major, err := strconv.Atoi(s[0])
48-
if err != nil {
49-
return 0, 0, fmt.Errorf("error parsing major version %q in %q: %w", s[0], sb.String(), err)
50-
}
51-
52-
minor, err := strconv.Atoi(s[1])
53-
if err != nil {
54-
return 0, 0, fmt.Errorf("error parsing minor version %q in %q: %w", s[1], sb.String(), err)
55-
}
56-
57-
return major, minor, nil
58-
}
59-
6026
// TestHostFeatureFlags tests that all features detected by HostFeatureSet are
6127
// on the host.
6228
//
@@ -65,7 +31,7 @@ func kernelVersion() (int, int, error) {
6531
// analog in the actual CPUID feature set.
6632
func TestHostFeatureFlags(t *testing.T) {
6733
// Extract the kernel version.
68-
major, minor, err := kernelVersion()
34+
major, minor, err := hostos.KernelVersion()
6935
if err != nil {
7036
t.Fatalf("Unable to parse kernel version: %v", err)
7137
}

pkg/hostos/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
load("//tools:defs.bzl", "go_library")
2+
3+
package(licenses = ["notice"])
4+
5+
go_library(
6+
name = "hostos",
7+
srcs = ["hostos.go"],
8+
visibility = ["//:sandbox"],
9+
deps = ["@org_golang_x_sys//unix:go_default_library"],
10+
)

pkg/hostos/hostos.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2022 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package hostos contains utility functions for getting information about the host OS.
16+
package hostos
17+
18+
import (
19+
"fmt"
20+
"strconv"
21+
"strings"
22+
23+
"golang.org/x/sys/unix"
24+
)
25+
26+
// KernelVersion returns the major and minor release version of the kernel using uname().
27+
func KernelVersion() (int, int, error) {
28+
var u unix.Utsname
29+
if err := unix.Uname(&u); err != nil {
30+
return 0, 0, err
31+
}
32+
33+
var sb strings.Builder
34+
for _, b := range u.Release {
35+
if b == 0 {
36+
break
37+
}
38+
sb.WriteByte(byte(b))
39+
}
40+
41+
s := strings.Split(sb.String(), ".")
42+
if len(s) < 2 {
43+
return 0, 0, fmt.Errorf("kernel release missing major and minor component: %s", sb.String())
44+
}
45+
46+
major, err := strconv.Atoi(s[0])
47+
if err != nil {
48+
return 0, 0, fmt.Errorf("error parsing major version %q in %q: %w", s[0], sb.String(), err)
49+
}
50+
51+
minor, err := strconv.Atoi(s[1])
52+
if err != nil {
53+
return 0, 0, fmt.Errorf("error parsing minor version %q in %q: %w", s[1], sb.String(), err)
54+
}
55+
56+
return major, minor, nil
57+
}

runsc/cmd/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ go_library(
4545
"//runsc:__subpackages__",
4646
],
4747
deps = [
48+
"//pkg/coretag",
4849
"//pkg/coverage",
4950
"//pkg/log",
5051
"//pkg/p9",

0 commit comments

Comments
 (0)