mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
a0c1674478
commit
932c8abd0f
@@ -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
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user