Implement cgroupfs.

A skeleton implementation of cgroupfs. It supports trivial cpu and
memory controllers with no support for hierarchies.

PiperOrigin-RevId: 366561126
This commit is contained in:
Rahat Mahmood
2021-04-02 21:10:44 -07:00
committed by gVisor bot
parent a0c1674478
commit 932c8abd0f
42 changed files with 2352 additions and 44 deletions
+1
View File
@@ -144,6 +144,7 @@ dev: $(RUNTIME_BIN) ## Installs a set of local runtimes. Requires sudo.
@$(call configure_noreload,$(RUNTIME)-p,--net-raw --profile)
@$(call configure_noreload,$(RUNTIME)-vfs2-d,--net-raw --debug --strace --log-packets --vfs2)
@$(call configure_noreload,$(RUNTIME)-vfs2-fuse-d,--net-raw --debug --strace --log-packets --vfs2 --fuse)
@$(call configure_noreload,$(RUNTIME)-vfs2-cgroup-d,--net-raw --debug --strace --log-packets --vfs2 --cgroupfs)
@$(call reload_docker)
.PHONY: dev
+2 -1
View File
@@ -19,8 +19,10 @@ package linux
// See linux/magic.h.
const (
ANON_INODE_FS_MAGIC = 0x09041934
CGROUP_SUPER_MAGIC = 0x27e0eb
DEVPTS_SUPER_MAGIC = 0x00001cd1
EXT_SUPER_MAGIC = 0xef53
FUSE_SUPER_MAGIC = 0x65735546
OVERLAYFS_SUPER_MAGIC = 0x794c7630
PIPEFS_MAGIC = 0x50495045
PROC_SUPER_MAGIC = 0x9fa0
@@ -29,7 +31,6 @@ const (
SYSFS_MAGIC = 0x62656572
TMPFS_MAGIC = 0x01021994
V9FS_MAGIC = 0x01021997
FUSE_SUPER_MAGIC = 0x65735546
)
// Filesystem path limits, from uapi/linux/limits.h.
+47
View File
@@ -0,0 +1,47 @@
load("//tools:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template_instance")
licenses(["notice"])
go_template_instance(
name = "dir_refs",
out = "dir_refs.go",
package = "cgroupfs",
prefix = "dir",
template = "//pkg/refsvfs2:refs_template",
types = {
"T": "dir",
},
)
go_library(
name = "cgroupfs",
srcs = [
"base.go",
"cgroupfs.go",
"cpu.go",
"cpuacct.go",
"cpuset.go",
"dir_refs.go",
"memory.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/coverage",
"//pkg/log",
"//pkg/refs",
"//pkg/refsvfs2",
"//pkg/sentry/arch",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/memmap",
"//pkg/sentry/usage",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/syserror",
"//pkg/usermem",
],
)
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2021 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
//
// http://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 cgroupfs
import (
"bytes"
"fmt"
"sort"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/usermem"
)
// controllerCommon implements kernel.CgroupController.
//
// Must call init before use.
//
// +stateify savable
type controllerCommon struct {
ty kernel.CgroupControllerType
fs *filesystem
}
func (c *controllerCommon) init(ty kernel.CgroupControllerType, fs *filesystem) {
c.ty = ty
c.fs = fs
}
// Type implements kernel.CgroupController.Type.
func (c *controllerCommon) Type() kernel.CgroupControllerType {
return kernel.CgroupControllerType(c.ty)
}
// HierarchyID implements kernel.CgroupController.HierarchyID.
func (c *controllerCommon) HierarchyID() uint32 {
return c.fs.hierarchyID
}
// NumCgroups implements kernel.CgroupController.NumCgroups.
func (c *controllerCommon) NumCgroups() uint64 {
return atomic.LoadUint64(&c.fs.numCgroups)
}
// Enabled implements kernel.CgroupController.Enabled.
//
// Controllers are currently always enabled.
func (c *controllerCommon) Enabled() bool {
return true
}
// Filesystem implements kernel.CgroupController.Filesystem.
func (c *controllerCommon) Filesystem() *vfs.Filesystem {
return c.fs.VFSFilesystem()
}
// RootCgroup implements kernel.CgroupController.RootCgroup.
func (c *controllerCommon) RootCgroup() kernel.Cgroup {
return c.fs.rootCgroup()
}
// controller is an interface for common functionality related to all cgroups.
// It is an extension of the public cgroup interface, containing cgroup
// functionality private to cgroupfs.
type controller interface {
kernel.CgroupController
// AddControlFiles should extend the contents map with inodes representing
// control files defined by this controller.
AddControlFiles(ctx context.Context, creds *auth.Credentials, c *cgroupInode, contents map[string]kernfs.Inode)
}
// cgroupInode implements kernel.CgroupImpl and kernfs.Inode.
//
// +stateify savable
type cgroupInode struct {
dir
fs *filesystem
// ts is the list of tasks in this cgroup. The kernel is responsible for
// removing tasks from this list before they're destroyed, so any tasks on
// this list are always valid.
//
// ts, and cgroup membership in general is protected by fs.tasksMu.
ts map[*kernel.Task]struct{}
}
var _ kernel.CgroupImpl = (*cgroupInode)(nil)
func (fs *filesystem) newCgroupInode(ctx context.Context, creds *auth.Credentials) kernfs.Inode {
c := &cgroupInode{
fs: fs,
ts: make(map[*kernel.Task]struct{}),
}
contents := make(map[string]kernfs.Inode)
contents["cgroup.procs"] = fs.newControllerFile(ctx, creds, &cgroupProcsData{c})
contents["tasks"] = fs.newControllerFile(ctx, creds, &tasksData{c})
for _, ctl := range fs.controllers {
ctl.AddControlFiles(ctx, creds, c, contents)
}
c.dir.InodeAttrs.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|linux.FileMode(0555))
c.dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
c.dir.InitRefs()
c.dir.IncLinks(c.dir.OrderedChildren.Populate(contents))
atomic.AddUint64(&fs.numCgroups, 1)
return c
}
func (c *cgroupInode) HierarchyID() uint32 {
return c.fs.hierarchyID
}
// Controllers implements kernel.CgroupImpl.Controllers.
func (c *cgroupInode) Controllers() []kernel.CgroupController {
return c.fs.kcontrollers
}
// Enter implements kernel.CgroupImpl.Enter.
func (c *cgroupInode) Enter(t *kernel.Task) {
c.fs.tasksMu.Lock()
c.ts[t] = struct{}{}
c.fs.tasksMu.Unlock()
}
// Leave implements kernel.CgroupImpl.Leave.
func (c *cgroupInode) Leave(t *kernel.Task) {
c.fs.tasksMu.Lock()
delete(c.ts, t)
c.fs.tasksMu.Unlock()
}
func sortTIDs(tids []kernel.ThreadID) {
sort.Slice(tids, func(i, j int) bool { return tids[i] < tids[j] })
}
// +stateify savable
type cgroupProcsData struct {
*cgroupInode
}
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *cgroupProcsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
t := kernel.TaskFromContext(ctx)
currPidns := t.ThreadGroup().PIDNamespace()
pgids := make(map[kernel.ThreadID]struct{})
d.fs.tasksMu.Lock()
defer d.fs.tasksMu.Unlock()
for task := range d.ts {
// Map dedups pgid, since iterating over all tasks produces multiple
// entries for the group leaders.
if pgid := currPidns.IDOfThreadGroup(task.ThreadGroup()); pgid != 0 {
pgids[pgid] = struct{}{}
}
}
pgidList := make([]kernel.ThreadID, 0, len(pgids))
for pgid, _ := range pgids {
pgidList = append(pgidList, pgid)
}
sortTIDs(pgidList)
for _, pgid := range pgidList {
fmt.Fprintf(buf, "%d\n", pgid)
}
return nil
}
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *cgroupProcsData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
// TODO(b/183137098): Payload is the pid for a process to add to this cgroup.
return src.NumBytes(), nil
}
// +stateify savable
type tasksData struct {
*cgroupInode
}
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *tasksData) Generate(ctx context.Context, buf *bytes.Buffer) error {
t := kernel.TaskFromContext(ctx)
currPidns := t.ThreadGroup().PIDNamespace()
var pids []kernel.ThreadID
d.fs.tasksMu.Lock()
defer d.fs.tasksMu.Unlock()
for task := range d.ts {
if pid := currPidns.IDOfTask(task); pid != 0 {
pids = append(pids, pid)
}
}
sortTIDs(pids)
for _, pid := range pids {
fmt.Fprintf(buf, "%d\n", pid)
}
return nil
}
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *tasksData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
// TODO(b/183137098): Payload is the pid for a process to add to this cgroup.
return src.NumBytes(), nil
}
+392
View File
@@ -0,0 +1,392 @@
// Copyright 2021 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
//
// http://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 cgroupfs implements cgroupfs.
//
// A cgroup is a collection of tasks on the system, organized into a tree-like
// structure similar to a filesystem directory tree. In fact, each cgroup is
// represented by a directory on cgroupfs, and is manipulated through control
// files in the directory.
//
// All cgroups on a system are organized into hierarchies. Hierarchies are a
// distinct tree of cgroups, with a common set of controllers. One or more
// cgroupfs mounts may point to each hierarchy. These mounts provide a common
// view into the same tree of cgroups.
//
// A controller (also known as a "resource controller", or a cgroup "subsystem")
// determines the behaviour of each cgroup.
//
// In addition to cgroupfs, the kernel has a cgroup registry that tracks
// system-wide state related to cgroups such as active hierarchies and the
// controllers associated with them.
//
// Since cgroupfs doesn't allow hardlinks, there is a unique mapping between
// cgroupfs dentries and inodes.
//
// # Synchronization
//
// Cgroup hierarchy creation and destruction is protected by the
// kernel.CgroupRegistry.mu. Once created, a hierarchy's set of controllers, the
// filesystem associated with it, and the root cgroup for the hierarchy are
// immutable.
//
// Membership of tasks within cgroups is protected by
// cgroupfs.filesystem.tasksMu. Tasks also maintain a set of all cgroups they're
// in, and this list is protected by Task.mu.
//
// Lock order:
//
// kernel.CgroupRegistry.mu
// cgroupfs.filesystem.mu
// Task.mu
// cgroupfs.filesystem.tasksMu.
package cgroupfs
import (
"fmt"
"sort"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
const (
// Name is the default filesystem name.
Name = "cgroup"
readonlyFileMode = linux.FileMode(0444)
writableFileMode = linux.FileMode(0644)
defaultMaxCachedDentries = uint64(1000)
)
const (
controllerCPU = kernel.CgroupControllerType("cpu")
controllerCPUAcct = kernel.CgroupControllerType("cpuacct")
controllerCPUSet = kernel.CgroupControllerType("cpuset")
controllerMemory = kernel.CgroupControllerType("memory")
)
var allControllers = []kernel.CgroupControllerType{controllerCPU, controllerCPUAcct, controllerCPUSet, controllerMemory}
// SupportedMountOptions is the set of supported mount options for cgroupfs.
var SupportedMountOptions = []string{"all", "cpu", "cpuacct", "cpuset", "memory"}
// FilesystemType implements vfs.FilesystemType.
//
// +stateify savable
type FilesystemType struct{}
// filesystem implements vfs.FilesystemImpl.
//
// +stateify savable
type filesystem struct {
kernfs.Filesystem
devMinor uint32
// hierarchyID is the id the cgroup registry assigns to this hierarchy. Has
// the value kernel.InvalidCgroupHierarchyID until the FS is fully
// initialized.
//
// hierarchyID is immutable after initialization.
hierarchyID uint32
// controllers and kcontrollers are both the list of controllers attached to
// this cgroupfs. Both lists are the same set of controllers, but typecast
// to different interfaces for convenience. Both must stay in sync, and are
// immutable.
controllers []controller
kcontrollers []kernel.CgroupController
numCgroups uint64 // Protected by atomic ops.
root *kernfs.Dentry
// tasksMu serializes task membership changes across all cgroups within a
// filesystem.
tasksMu sync.Mutex `state:"nosave"`
}
// Name implements vfs.FilesystemType.Name.
func (FilesystemType) Name() string {
return Name
}
// Release implements vfs.FilesystemType.Release.
func (FilesystemType) Release(ctx context.Context) {}
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
devMinor, err := vfsObj.GetAnonBlockDevMinor()
if err != nil {
return nil, nil, err
}
mopts := vfs.GenericParseMountOptions(opts.Data)
maxCachedDentries := defaultMaxCachedDentries
if str, ok := mopts["dentry_cache_limit"]; ok {
delete(mopts, "dentry_cache_limit")
maxCachedDentries, err = strconv.ParseUint(str, 10, 64)
if err != nil {
ctx.Warningf("sys.FilesystemType.GetFilesystem: invalid dentry cache limit: dentry_cache_limit=%s", str)
return nil, nil, syserror.EINVAL
}
}
var wantControllers []kernel.CgroupControllerType
if _, ok := mopts["cpu"]; ok {
delete(mopts, "cpu")
wantControllers = append(wantControllers, controllerCPU)
}
if _, ok := mopts["cpuacct"]; ok {
delete(mopts, "cpuacct")
wantControllers = append(wantControllers, controllerCPUAcct)
}
if _, ok := mopts["cpuset"]; ok {
delete(mopts, "cpuset")
wantControllers = append(wantControllers, controllerCPUSet)
}
if _, ok := mopts["memory"]; ok {
delete(mopts, "memory")
wantControllers = append(wantControllers, controllerMemory)
}
if _, ok := mopts["all"]; ok {
if len(wantControllers) > 0 {
ctx.Debugf("cgroupfs.FilesystemType.GetFilesystem: other controllers specified with all: %v", wantControllers)
return nil, nil, syserror.EINVAL
}
delete(mopts, "all")
wantControllers = allControllers
}
if len(wantControllers) == 0 {
// Specifying no controllers implies all controllers.
wantControllers = allControllers
}
if len(mopts) != 0 {
ctx.Debugf("cgroupfs.FilesystemType.GetFilesystem: unknown options: %v", mopts)
return nil, nil, syserror.EINVAL
}
k := kernel.KernelFromContext(ctx)
r := k.CgroupRegistry()
// "It is not possible to mount the same controller against multiple
// cgroup hierarchies. For example, it is not possible to mount both
// the cpu and cpuacct controllers against one hierarchy, and to mount
// the cpu controller alone against another hierarchy." - man cgroups(7)
//
// Is there a hierarchy available with all the controllers we want? If so,
// this mount is a view into the same hierarchy.
//
// Note: we're guaranteed to have at least one requested controller, since
// no explicit controller name implies all controllers.
if vfsfs := r.FindHierarchy(wantControllers); vfsfs != nil {
fs := vfsfs.Impl().(*filesystem)
ctx.Debugf("cgroupfs.FilesystemType.GetFilesystem: mounting new view to hierarchy %v", fs.hierarchyID)
fs.root.IncRef()
return vfsfs, fs.root.VFSDentry(), nil
}
// No existing hierarchy with the exactly controllers found. Make a new
// one. Note that it's possible this mount creation is unsatisfiable, if one
// or more of the requested controllers are already on existing
// hierarchies. We'll find out about such collisions when we try to register
// the new hierarchy later.
fs := &filesystem{
devMinor: devMinor,
}
fs.MaxCachedDentries = maxCachedDentries
fs.VFSFilesystem().Init(vfsObj, &fsType, fs)
for _, ty := range wantControllers {
var c controller
switch ty {
case controllerMemory:
c = newMemoryController(fs)
case controllerCPU:
c = newCPUController(fs)
case controllerCPUAcct:
c = newCPUAcctController(fs)
case controllerCPUSet:
c = newCPUSetController(fs)
default:
panic(fmt.Sprintf("Unreachable: unknown cgroup controller %q", ty))
}
fs.controllers = append(fs.controllers, c)
}
// Controllers usually appear in alphabetical order when displayed. Sort it
// here now, so it never needs to be sorted elsewhere.
sort.Slice(fs.controllers, func(i, j int) bool { return fs.controllers[i].Type() < fs.controllers[j].Type() })
fs.kcontrollers = make([]kernel.CgroupController, 0, len(fs.controllers))
for _, c := range fs.controllers {
fs.kcontrollers = append(fs.kcontrollers, c)
}
root := fs.newCgroupInode(ctx, creds)
var rootD kernfs.Dentry
rootD.InitRoot(&fs.Filesystem, root)
fs.root = &rootD
// Register controllers. The registry may be modified concurrently, so if we
// get an error, we raced with someone else who registered the same
// controllers first.
hid, err := r.Register(fs.kcontrollers)
if err != nil {
ctx.Infof("cgroupfs.FilesystemType.GetFilesystem: failed to register new hierarchy with controllers %v: %v", wantControllers, err)
rootD.DecRef(ctx)
fs.VFSFilesystem().DecRef(ctx)
return nil, nil, syserror.EBUSY
}
fs.hierarchyID = hid
// Move all existing tasks to the root of the new hierarchy.
k.PopulateNewCgroupHierarchy(fs.rootCgroup())
return fs.VFSFilesystem(), rootD.VFSDentry(), nil
}
func (fs *filesystem) rootCgroup() kernel.Cgroup {
return kernel.Cgroup{
Dentry: fs.root,
CgroupImpl: fs.root.Inode().(kernel.CgroupImpl),
}
}
// Release implements vfs.FilesystemImpl.Release.
func (fs *filesystem) Release(ctx context.Context) {
k := kernel.KernelFromContext(ctx)
r := k.CgroupRegistry()
if fs.hierarchyID != kernel.InvalidCgroupHierarchyID {
k.ReleaseCgroupHierarchy(fs.hierarchyID)
r.Unregister(fs.hierarchyID)
}
fs.Filesystem.VFSFilesystem().VirtualFilesystem().PutAnonBlockDevMinor(fs.devMinor)
fs.Filesystem.Release(ctx)
}
// MountOptions implements vfs.FilesystemImpl.MountOptions.
func (fs *filesystem) MountOptions() string {
var cnames []string
for _, c := range fs.controllers {
cnames = append(cnames, string(c.Type()))
}
return strings.Join(cnames, ",")
}
// +stateify savable
type implStatFS struct{}
// StatFS implements kernfs.Inode.StatFS.
func (*implStatFS) StatFS(context.Context, *vfs.Filesystem) (linux.Statfs, error) {
return vfs.GenericStatFS(linux.CGROUP_SUPER_MAGIC), nil
}
// dir implements kernfs.Inode for a generic cgroup resource controller
// directory. Specific controllers extend this to add their own functionality.
//
// +stateify savable
type dir struct {
dirRefs
kernfs.InodeAlwaysValid
kernfs.InodeAttrs
kernfs.InodeNotSymlink
kernfs.InodeDirectoryNoNewChildren // TODO(b/183137098): Implement mkdir.
kernfs.OrderedChildren
implStatFS
locks vfs.FileLocks
}
// Keep implements kernfs.Inode.Keep.
func (*dir) Keep() bool {
return true
}
// SetStat implements kernfs.Inode.SetStat not allowing inode attributes to be changed.
func (*dir) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error {
return syserror.EPERM
}
// Open implements kernfs.Inode.Open.
func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{
SeekEnd: kernfs.SeekEndStaticEntries,
})
if err != nil {
return nil, err
}
return fd.VFSFileDescription(), nil
}
// DecRef implements kernfs.Inode.DecRef.
func (d *dir) DecRef(ctx context.Context) {
d.dirRefs.DecRef(func() { d.Destroy(ctx) })
}
// StatFS implements kernfs.Inode.StatFS.
func (d *dir) StatFS(ctx context.Context, fs *vfs.Filesystem) (linux.Statfs, error) {
return vfs.GenericStatFS(linux.CGROUP_SUPER_MAGIC), nil
}
// controllerFile represents a generic control file that appears within a cgroup
// directory.
//
// +stateify savable
type controllerFile struct {
kernfs.DynamicBytesFile
}
func (fs *filesystem) newControllerFile(ctx context.Context, creds *auth.Credentials, data vfs.DynamicBytesSource) kernfs.Inode {
f := &controllerFile{}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), data, readonlyFileMode)
return f
}
func (fs *filesystem) newControllerWritableFile(ctx context.Context, creds *auth.Credentials, data vfs.WritableDynamicBytesSource) kernfs.Inode {
f := &controllerFile{}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), data, writableFileMode)
return f
}
// staticControllerFile represents a generic control file that appears within a
// cgroup directory which always returns the same data when read.
// staticControllerFiles are not writable.
//
// +stateify savable
type staticControllerFile struct {
kernfs.DynamicBytesFile
vfs.StaticData
}
// Note: We let the caller provide the mode so that static files may be used to
// fake both readable and writable control files. However, static files are
// effectively readonly, as attempting to write to them will return EIO
// regardless of the mode.
func (fs *filesystem) newStaticControllerFile(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, data string) kernfs.Inode {
f := &staticControllerFile{StaticData: vfs.StaticData{Data: data}}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), f, mode)
return f
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2021 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
//
// http://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 cgroupfs
import (
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
)
// +stateify savable
type cpuController struct {
controllerCommon
// CFS bandwidth control parameters, values in microseconds.
cfsPeriod uint64
cfsQuota int64
// CPU shares, values should be (num core * 1024).
shares uint64
}
var _ controller = (*cpuController)(nil)
func newCPUController(fs *filesystem) *cpuController {
// Default values for controller parameters from Linux.
c := &cpuController{
cfsPeriod: 100000,
cfsQuota: -1,
shares: 1024,
}
c.controllerCommon.init(controllerCPU, fs)
return c
}
// AddControlFiles implements controller.AddControlFiles.
func (c *cpuController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["cpu.cfs_period_us"] = c.fs.newStaticControllerFile(ctx, creds, linux.FileMode(0644), fmt.Sprintf("%d\n", c.cfsPeriod))
contents["cpu.cfs_quota_us"] = c.fs.newStaticControllerFile(ctx, creds, linux.FileMode(0644), fmt.Sprintf("%d\n", c.cfsQuota))
contents["cpu.shares"] = c.fs.newStaticControllerFile(ctx, creds, linux.FileMode(0644), fmt.Sprintf("%d\n", c.shares))
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2021 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
//
// http://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 cgroupfs
import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
)
// +stateify savable
type cpuacctController struct {
controllerCommon
}
var _ controller = (*cpuacctController)(nil)
func newCPUAcctController(fs *filesystem) *cpuacctController {
c := &cpuacctController{}
c.controllerCommon.init(controllerCPUAcct, fs)
return c
}
// AddControlFiles implements controller.AddControlFiles.
func (c *cpuacctController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
// This controller is currently intentionally empty.
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2021 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
//
// http://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 cgroupfs
import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
)
// +stateify savable
type cpusetController struct {
controllerCommon
}
var _ controller = (*cpusetController)(nil)
func newCPUSetController(fs *filesystem) *cpusetController {
c := &cpusetController{}
c.controllerCommon.init(controllerCPUSet, fs)
return c
}
// AddControlFiles implements controller.AddControlFiles.
func (c *cpusetController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
// This controller is currently intentionally empty.
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2021 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
//
// http://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 cgroupfs
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usage"
)
// +stateify savable
type memoryController struct {
controllerCommon
}
var _ controller = (*memoryController)(nil)
func newMemoryController(fs *filesystem) *memoryController {
c := &memoryController{}
c.controllerCommon.init(controllerMemory, fs)
return c
}
// AddControlFiles implements controller.AddControlFiles.
func (c *memoryController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["memory.usage_in_bytes"] = c.fs.newControllerFile(ctx, creds, &memoryUsageInBytesData{})
}
// +stateify savable
type memoryUsageInBytesData struct{}
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *memoryUsageInBytesData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/183151557): This is a giant hack, we're using system-wide
// accounting since we know there is only one cgroup.
k := kernel.KernelFromContext(ctx)
mf := k.MemoryFile()
mf.UpdateUsage()
_, totalBytes := usage.MemoryAccounting.Copy()
fmt.Fprintf(buf, "%d\n", totalBytes)
return nil
}
@@ -25,8 +25,10 @@ import (
"gvisor.dev/gvisor/pkg/usermem"
)
// DynamicBytesFile implements kernfs.Inode and represents a read-only
// file whose contents are backed by a vfs.DynamicBytesSource.
// DynamicBytesFile implements kernfs.Inode and represents a read-only file
// whose contents are backed by a vfs.DynamicBytesSource. If data additionally
// implements vfs.WritableDynamicBytesSource, the file also supports dispatching
// writes to the implementer, but note that this will not update the source data.
//
// Must be instantiated with NewDynamicBytesFile or initialized with Init
// before first use.
@@ -40,7 +42,9 @@ type DynamicBytesFile struct {
InodeNotSymlink
locks vfs.FileLocks
data vfs.DynamicBytesSource
// data can additionally implement vfs.WritableDynamicBytesSource to support
// writes.
data vfs.DynamicBytesSource
}
var _ Inode = (*DynamicBytesFile)(nil)
+10
View File
@@ -61,6 +61,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/refsvfs2"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -508,6 +509,15 @@ func (d *Dentry) Inode() Inode {
return d.inode
}
// FSLocalPath returns an absolute path to d, relative to the root of its
// filesystem.
func (d *Dentry) FSLocalPath() string {
var b fspath.Builder
_ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b)
b.PrependByte('/')
return b.String()
}
// The Inode interface maps filesystem-level operations that operate on paths to
// equivalent operations on specific filesystem nodes.
//
+3 -3
View File
@@ -86,13 +86,13 @@ func (ft FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualF
procfs.MaxCachedDentries = maxCachedDentries
procfs.VFSFilesystem().Init(vfsObj, &ft, procfs)
var cgroups map[string]string
var fakeCgroupControllers map[string]string
if opts.InternalData != nil {
data := opts.InternalData.(*InternalData)
cgroups = data.Cgroups
fakeCgroupControllers = data.Cgroups
}
inode := procfs.newTasksInode(ctx, k, pidns, cgroups)
inode := procfs.newTasksInode(ctx, k, pidns, fakeCgroupControllers)
var dentry kernfs.Dentry
dentry.InitRoot(&procfs.Filesystem, inode)
return procfs.VFSFilesystem(), dentry.VFSDentry(), nil
+14 -9
View File
@@ -47,7 +47,7 @@ type taskInode struct {
var _ kernfs.Inode = (*taskInode)(nil)
func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns *kernel.PIDNamespace, isThreadGroup bool, cgroupControllers map[string]string) (kernfs.Inode, error) {
func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns *kernel.PIDNamespace, isThreadGroup bool, fakeCgroupControllers map[string]string) (kernfs.Inode, error) {
if task.ExitState() == kernel.TaskExitDead {
return nil, syserror.ESRCH
}
@@ -82,10 +82,12 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
"uid_map": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &idMapData{task: task, gids: false}),
}
if isThreadGroup {
contents["task"] = fs.newSubtasks(ctx, task, pidns, cgroupControllers)
contents["task"] = fs.newSubtasks(ctx, task, pidns, fakeCgroupControllers)
}
if len(cgroupControllers) > 0 {
contents["cgroup"] = fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, newCgroupData(cgroupControllers))
if len(fakeCgroupControllers) > 0 {
contents["cgroup"] = fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, newFakeCgroupData(fakeCgroupControllers))
} else {
contents["cgroup"] = fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &taskCgroupData{task: task})
}
taskInode := &taskInode{task: task}
@@ -226,11 +228,14 @@ func newIO(t *kernel.Task, isThreadGroup bool) *ioData {
return &ioData{ioUsage: t}
}
// newCgroupData creates inode that shows cgroup information.
// From man 7 cgroups: "For each cgroup hierarchy of which the process is a
// member, there is one entry containing three colon-separated fields:
// hierarchy-ID:controller-list:cgroup-path"
func newCgroupData(controllers map[string]string) dynamicInode {
// newFakeCgroupData creates an inode that shows fake cgroup
// information passed in as mount options. From man 7 cgroups: "For
// each cgroup hierarchy of which the process is a member, there is
// one entry containing three colon-separated fields:
// hierarchy-ID:controller-list:cgroup-path"
//
// TODO(b/182488796): Remove once all users adopt cgroupfs.
func newFakeCgroupData(controllers map[string]string) dynamicInode {
var buf bytes.Buffer
// The hierarchy ids must be positive integers (for cgroup v1), but the
+29
View File
@@ -1100,3 +1100,32 @@ func (fd *namespaceFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) err
func (fd *namespaceFD) Release(ctx context.Context) {
fd.inode.DecRef(ctx)
}
// taskCgroupData generates data for /proc/[pid]/cgroup.
//
// +stateify savable
type taskCgroupData struct {
dynamicBytesFileSetAttr
task *kernel.Task
}
var _ dynamicInode = (*taskCgroupData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *taskCgroupData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// When a task is existing on Linux, a task's cgroup set is cleared and
// reset to the initial cgroup set, which is essentially the set of root
// cgroups. Because of this, the /proc/<pid>/cgroup file is always readable
// on Linux throughout a task's lifetime.
//
// The sentry removes tasks from cgroups during the exit process, but
// doesn't move them into an initial cgroup set, so partway through task
// exit this file show a task is in no cgroups, which is incorrect. Instead,
// once a task has left its cgroups, we return an error.
if d.task.ExitState() >= kernel.TaskExitInitiated {
return syserror.ESRCH
}
d.task.GenerateProcTaskCgroup(buf)
return nil
}
+12 -7
View File
@@ -54,15 +54,15 @@ type tasksInode struct {
// '/proc/self' and '/proc/thread-self' have custom directory offsets in
// Linux. So handle them outside of OrderedChildren.
// cgroupControllers is a map of controller name to directory in the
// fakeCgroupControllers is a map of controller name to directory in the
// cgroup hierarchy. These controllers are immutable and will be listed
// in /proc/pid/cgroup if not nil.
cgroupControllers map[string]string
fakeCgroupControllers map[string]string
}
var _ kernfs.Inode = (*tasksInode)(nil)
func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns *kernel.PIDNamespace, cgroupControllers map[string]string) *tasksInode {
func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns *kernel.PIDNamespace, fakeCgroupControllers map[string]string) *tasksInode {
root := auth.NewRootCredentials(pidns.UserNamespace())
contents := map[string]kernfs.Inode{
"cpuinfo": fs.newInode(ctx, root, 0444, newStaticFileSetStat(cpuInfoData(k))),
@@ -76,11 +76,16 @@ func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns
"uptime": fs.newInode(ctx, root, 0444, &uptimeData{}),
"version": fs.newInode(ctx, root, 0444, &versionData{}),
}
// If fakeCgroupControllers are provided, don't create a cgroupfs backed
// /proc/cgroup as it will not match the fake controllers.
if len(fakeCgroupControllers) == 0 {
contents["cgroups"] = fs.newInode(ctx, root, 0444, &cgroupsData{})
}
inode := &tasksInode{
pidns: pidns,
fs: fs,
cgroupControllers: cgroupControllers,
pidns: pidns,
fs: fs,
fakeCgroupControllers: fakeCgroupControllers,
}
inode.InodeAttrs.Init(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0555)
inode.InitRefs()
@@ -118,7 +123,7 @@ func (i *tasksInode) Lookup(ctx context.Context, name string) (kernfs.Inode, err
return nil, syserror.ENOENT
}
return i.fs.newTaskInode(ctx, task, i.pidns, true, i.cgroupControllers)
return i.fs.newTaskInode(ctx, task, i.pidns, true, i.fakeCgroupControllers)
}
// IterDirents implements kernfs.inodeDirectory.IterDirents.
+16
View File
@@ -384,3 +384,19 @@ func (d *filesystemsData) Generate(ctx context.Context, buf *bytes.Buffer) error
k.VFS().GenerateProcFilesystems(buf)
return nil
}
// cgroupsData backs /proc/cgroups.
//
// +stateify savable
type cgroupsData struct {
dynamicBytesFileSetAttr
}
var _ dynamicInode = (*cgroupsData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (*cgroupsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
r := kernel.KernelFromContext(ctx).CgroupRegistry()
r.GenerateProcCgroups(buf)
return nil
}
+3
View File
@@ -141,6 +141,7 @@ go_library(
srcs = [
"abstract_socket_namespace.go",
"aio.go",
"cgroup.go",
"context.go",
"fd_table.go",
"fd_table_refs.go",
@@ -178,6 +179,7 @@ go_library(
"task.go",
"task_acct.go",
"task_block.go",
"task_cgroup.go",
"task_clone.go",
"task_context.go",
"task_exec.go",
@@ -241,6 +243,7 @@ go_library(
"//pkg/sentry/fs/lock",
"//pkg/sentry/fs/timerfd",
"//pkg/sentry/fsbridge",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/fsimpl/pipefs",
"//pkg/sentry/fsimpl/sockfs",
"//pkg/sentry/fsimpl/timerfd",
+281
View File
@@ -0,0 +1,281 @@
// Copyright 2021 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
//
// http://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 kernel
import (
"bytes"
"fmt"
"sort"
"sync/atomic"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
)
// InvalidCgroupHierarchyID indicates an uninitialized hierarchy ID.
const InvalidCgroupHierarchyID uint32 = 0
// CgroupControllerType is the name of a cgroup controller.
type CgroupControllerType string
// CgroupController is the common interface to cgroup controllers available to
// the entire sentry. The controllers themselves are defined by cgroupfs.
//
// Callers of this interface are often unable access synchronization needed to
// ensure returned values remain valid. Some of values returned from this
// interface are thus snapshots in time, and may become stale. This is ok for
// many callers like procfs.
type CgroupController interface {
// Returns the type of this cgroup controller (ex "memory", "cpu"). Returned
// value is valid for the lifetime of the controller.
Type() CgroupControllerType
// Hierarchy returns the ID of the hierarchy this cgroup controller is
// attached to. Returned value is valid for the lifetime of the controller.
HierarchyID() uint32
// Filesystem returns the filesystem this controller is attached to.
// Returned value is valid for the lifetime of the controller.
Filesystem() *vfs.Filesystem
// RootCgroup returns the root cgroup for this controller. Returned value is
// valid for the lifetime of the controller.
RootCgroup() Cgroup
// NumCgroups returns the number of cgroups managed by this controller.
// Returned value is a snapshot in time.
NumCgroups() uint64
// Enabled returns whether this controller is enabled. Returned value is a
// snapshot in time.
Enabled() bool
}
// Cgroup represents a named pointer to a cgroup in cgroupfs. When a task enters
// a cgroup, it holds a reference on the underlying dentry pointing to the
// cgroup.
//
// +stateify savable
type Cgroup struct {
*kernfs.Dentry
CgroupImpl
}
func (c *Cgroup) decRef() {
c.Dentry.DecRef(context.Background())
}
// Path returns the absolute path of c, relative to its hierarchy root.
func (c *Cgroup) Path() string {
return c.FSLocalPath()
}
// HierarchyID returns the id of the hierarchy that contains this cgroup.
func (c *Cgroup) HierarchyID() uint32 {
// Note: a cgroup is guaranteed to have at least one controller.
return c.Controllers()[0].HierarchyID()
}
// CgroupImpl is the common interface to cgroups.
type CgroupImpl interface {
Controllers() []CgroupController
Enter(t *Task)
Leave(t *Task)
}
// hierarchy represents a cgroupfs filesystem instance, with a unique set of
// controllers attached to it. Multiple cgroupfs mounts may reference the same
// hierarchy.
//
// +stateify savable
type hierarchy struct {
id uint32
// These are a subset of the controllers in CgroupRegistry.controllers,
// grouped here by hierarchy for conveninent lookup.
controllers map[CgroupControllerType]CgroupController
// fs is not owned by hierarchy. The FS is responsible for unregistering the
// hierarchy on destruction, which removes this association.
fs *vfs.Filesystem
}
func (h *hierarchy) match(ctypes []CgroupControllerType) bool {
if len(ctypes) != len(h.controllers) {
return false
}
for _, ty := range ctypes {
if _, ok := h.controllers[ty]; !ok {
return false
}
}
return true
}
// CgroupRegistry tracks the active set of cgroup controllers on the system.
//
// +stateify savable
type CgroupRegistry struct {
// lastHierarchyID is the id of the last allocated cgroup hierarchy. Valid
// ids are from 1 to math.MaxUint32. Must be accessed through atomic ops.
//
lastHierarchyID uint32
mu sync.Mutex `state:"nosave"`
// controllers is the set of currently known cgroup controllers on the
// system. Protected by mu.
//
// +checklocks:mu
controllers map[CgroupControllerType]CgroupController
// hierarchies is the active set of cgroup hierarchies. Protected by mu.
//
// +checklocks:mu
hierarchies map[uint32]hierarchy
}
func newCgroupRegistry() *CgroupRegistry {
return &CgroupRegistry{
controllers: make(map[CgroupControllerType]CgroupController),
hierarchies: make(map[uint32]hierarchy),
}
}
// nextHierarchyID returns a newly allocated, unique hierarchy ID.
func (r *CgroupRegistry) nextHierarchyID() (uint32, error) {
if hid := atomic.AddUint32(&r.lastHierarchyID, 1); hid != 0 {
return hid, nil
}
return InvalidCgroupHierarchyID, fmt.Errorf("cgroup hierarchy ID overflow")
}
// FindHierarchy returns a cgroup filesystem containing exactly the set of
// controllers named in names. If no such FS is found, FindHierarchy return
// nil. FindHierarchy takes a reference on the returned FS, which is transferred
// to the caller.
func (r *CgroupRegistry) FindHierarchy(ctypes []CgroupControllerType) *vfs.Filesystem {
r.mu.Lock()
defer r.mu.Unlock()
for _, h := range r.hierarchies {
if h.match(ctypes) {
h.fs.IncRef()
return h.fs
}
}
return nil
}
// Register registers the provided set of controllers with the registry as a new
// hierarchy. If any controller is already registered, the function returns an
// error without modifying the registry. The hierarchy can be later referenced
// by the returned id.
func (r *CgroupRegistry) Register(cs []CgroupController) (uint32, error) {
r.mu.Lock()
defer r.mu.Unlock()
if len(cs) == 0 {
return InvalidCgroupHierarchyID, fmt.Errorf("can't register hierarchy with no controllers")
}
for _, c := range cs {
if _, ok := r.controllers[c.Type()]; ok {
return InvalidCgroupHierarchyID, fmt.Errorf("controllers may only be mounted on a single hierarchy")
}
}
hid, err := r.nextHierarchyID()
if err != nil {
return hid, err
}
h := hierarchy{
id: hid,
controllers: make(map[CgroupControllerType]CgroupController),
fs: cs[0].Filesystem(),
}
for _, c := range cs {
n := c.Type()
r.controllers[n] = c
h.controllers[n] = c
}
r.hierarchies[hid] = h
return hid, nil
}
// Unregister removes a previously registered hierarchy from the registry. If
// the controller was not previously registered, Unregister is a no-op.
func (r *CgroupRegistry) Unregister(hid uint32) {
r.mu.Lock()
defer r.mu.Unlock()
if h, ok := r.hierarchies[hid]; ok {
for name, _ := range h.controllers {
delete(r.controllers, name)
}
delete(r.hierarchies, hid)
}
}
// computeInitialGroups takes a reference on each of the returned cgroups. The
// caller takes ownership of this returned reference.
func (r *CgroupRegistry) computeInitialGroups(inherit map[Cgroup]struct{}) map[Cgroup]struct{} {
r.mu.Lock()
defer r.mu.Unlock()
ctlSet := make(map[CgroupControllerType]CgroupController)
cgset := make(map[Cgroup]struct{})
// Remember controllers from the inherited cgroups set...
for cg, _ := range inherit {
cg.IncRef() // Ref transferred to caller.
for _, ctl := range cg.Controllers() {
ctlSet[ctl.Type()] = ctl
cgset[cg] = struct{}{}
}
}
// ... and add the root cgroups of all the missing controllers.
for name, ctl := range r.controllers {
if _, ok := ctlSet[name]; !ok {
cg := ctl.RootCgroup()
cg.IncRef() // Ref transferred to caller.
cgset[cg] = struct{}{}
}
}
return cgset
}
// GenerateProcCgroups writes the contents of /proc/cgroups to buf.
func (r *CgroupRegistry) GenerateProcCgroups(buf *bytes.Buffer) {
r.mu.Lock()
entries := make([]string, 0, len(r.controllers))
for _, c := range r.controllers {
en := 0
if c.Enabled() {
en = 1
}
entries = append(entries, fmt.Sprintf("%s\t%d\t%d\t%d\n", c.Type(), c.HierarchyID(), c.NumCgroups(), en))
}
r.mu.Unlock()
sort.Strings(entries)
fmt.Fprint(buf, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n")
for _, e := range entries {
fmt.Fprint(buf, e)
}
}
+52
View File
@@ -294,6 +294,11 @@ type Kernel struct {
// YAMAPtraceScope is the current level of YAMA ptrace restrictions.
YAMAPtraceScope int32
// cgroupRegistry contains the set of active cgroup controllers on the
// system. It is controller by cgroupfs. Nil if cgroupfs is unavailable on
// the system.
cgroupRegistry *CgroupRegistry
}
// InitKernelArgs holds arguments to Init.
@@ -438,6 +443,8 @@ func (k *Kernel) Init(args InitKernelArgs) error {
k.socketMount = socketMount
k.socketsVFS2 = make(map[*vfs.FileDescription]*SocketRecord)
k.cgroupRegistry = newCgroupRegistry()
}
return nil
}
@@ -1815,6 +1822,11 @@ func (k *Kernel) SocketMount() *vfs.Mount {
return k.socketMount
}
// CgroupRegistry returns the cgroup registry.
func (k *Kernel) CgroupRegistry() *CgroupRegistry {
return k.cgroupRegistry
}
// Release releases resources owned by k.
//
// Precondition: This should only be called after the kernel is fully
@@ -1831,3 +1843,43 @@ func (k *Kernel) Release() {
k.timekeeper.Destroy()
k.vdso.Release(ctx)
}
// PopulateNewCgroupHierarchy moves all tasks into a newly created cgroup
// hierarchy.
//
// Precondition: root must be a new cgroup with no tasks. This implies the
// controllers for root are also new and currently manage no task, which in turn
// implies the new cgroup can be populated without migrating tasks between
// cgroups.
func (k *Kernel) PopulateNewCgroupHierarchy(root Cgroup) {
k.tasks.mu.RLock()
k.tasks.forEachTaskLocked(func(t *Task) {
if t.ExitState() != TaskExitNone {
return
}
t.mu.Lock()
t.enterCgroupLocked(root)
t.mu.Unlock()
})
k.tasks.mu.RUnlock()
}
// ReleaseCgroupHierarchy moves all tasks out of all cgroups belonging to the
// hierarchy with the provided id. This is intended for use during hierarchy
// teardown, as otherwise the tasks would be orphaned w.r.t to some controllers.
func (k *Kernel) ReleaseCgroupHierarchy(hid uint32) {
k.tasks.mu.RLock()
k.tasks.forEachTaskLocked(func(t *Task) {
if t.ExitState() != TaskExitNone {
return
}
t.mu.Lock()
for cg, _ := range t.cgroups {
if cg.HierarchyID() == hid {
t.leaveCgroupLocked(cg)
}
}
t.mu.Unlock()
})
k.tasks.mu.RUnlock()
}
+6
View File
@@ -587,6 +587,12 @@ type Task struct {
//
// kcov is exclusive to the task goroutine.
kcov *Kcov
// cgroups is the set of cgroups this task belongs to. This may be empty if
// no cgroup controllers are enabled. Protected by mu.
//
// +checklocks:mu
cgroups map[Cgroup]struct{}
}
func (t *Task) savePtraceTracer() *Task {

Some files were not shown because too many files have changed in this diff Show More