summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/vfs/vfs.go
diff options
context:
space:
mode:
authorJamie Liu <jamieliu@google.com>2019-07-18 15:09:14 -0700
committergVisor bot <gvisor-bot@google.com>2019-07-18 15:10:29 -0700
commit163ab5e9bab4f14923433967656d20f169d0f904 (patch)
tree5e51b1573e48fe87fe0e277a32f13c78b0c2f058 /pkg/sentry/vfs/vfs.go
parent6f7e2bb388cb29a355dece8921671c0085f53ea9 (diff)
Sentry virtual filesystem, v2
Major differences from the current ("v1") sentry VFS: - Path resolution is Filesystem-driven (FilesystemImpl methods call vfs.ResolvingPath methods) rather than VFS-driven (fs package owns a Dirent tree and calls fs.InodeOperations methods to populate it). This drastically improves performance, primarily by reducing overhead from inefficient synchronization and indirection. It also makes it possible to implement remote filesystem protocols that translate FS system calls into single RPCs, rather than having to make (at least) one RPC per path component, significantly reducing the latency of remote filesystems (especially during cold starts and for uncacheable shared filesystems). - Mounts are correctly represented as a separate check based on contextual state (current mount) rather than direct replacement in a fs.Dirent tree. This makes it possible to support (non-recursive) bind mounts and mount namespaces. Included in this CL is fsimpl/memfs, an incomplete in-memory filesystem that exists primarily to demonstrate intended filesystem implementation patterns and for benchmarking: BenchmarkVFS1TmpfsStat/1-6 3000000 497 ns/op BenchmarkVFS1TmpfsStat/2-6 2000000 676 ns/op BenchmarkVFS1TmpfsStat/3-6 2000000 904 ns/op BenchmarkVFS1TmpfsStat/8-6 1000000 1944 ns/op BenchmarkVFS1TmpfsStat/64-6 100000 14067 ns/op BenchmarkVFS1TmpfsStat/100-6 50000 21700 ns/op BenchmarkVFS2MemfsStat/1-6 10000000 197 ns/op BenchmarkVFS2MemfsStat/2-6 5000000 233 ns/op BenchmarkVFS2MemfsStat/3-6 5000000 268 ns/op BenchmarkVFS2MemfsStat/8-6 3000000 477 ns/op BenchmarkVFS2MemfsStat/64-6 500000 2592 ns/op BenchmarkVFS2MemfsStat/100-6 300000 4045 ns/op BenchmarkVFS1TmpfsMountStat/1-6 2000000 679 ns/op BenchmarkVFS1TmpfsMountStat/2-6 2000000 912 ns/op BenchmarkVFS1TmpfsMountStat/3-6 1000000 1113 ns/op BenchmarkVFS1TmpfsMountStat/8-6 1000000 2118 ns/op BenchmarkVFS1TmpfsMountStat/64-6 100000 14251 ns/op BenchmarkVFS1TmpfsMountStat/100-6 100000 22397 ns/op BenchmarkVFS2MemfsMountStat/1-6 5000000 317 ns/op BenchmarkVFS2MemfsMountStat/2-6 5000000 361 ns/op BenchmarkVFS2MemfsMountStat/3-6 5000000 387 ns/op BenchmarkVFS2MemfsMountStat/8-6 3000000 582 ns/op BenchmarkVFS2MemfsMountStat/64-6 500000 2699 ns/op BenchmarkVFS2MemfsMountStat/100-6 300000 4133 ns/op From this we can infer that, on this machine: - Constant cost for tmpfs stat() is ~160ns in VFS2 and ~280ns in VFS1. - Per-path-component cost is ~35ns in VFS2 and ~215ns in VFS1, a difference of about 6x. - The cost of crossing a mount boundary is about 80ns in VFS2 (MemfsMountStat/1 does approximately the same amount of work as MemfsStat/2, except that it also crosses a mount boundary). This is an inescapable cost of the separate mount lookup needed to support bind mounts and mount namespaces. PiperOrigin-RevId: 258853946
Diffstat (limited to 'pkg/sentry/vfs/vfs.go')
-rw-r--r--pkg/sentry/vfs/vfs.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/pkg/sentry/vfs/vfs.go b/pkg/sentry/vfs/vfs.go
new file mode 100644
index 000000000..4a8a69540
--- /dev/null
+++ b/pkg/sentry/vfs/vfs.go
@@ -0,0 +1,135 @@
+// Copyright 2019 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 vfs implements a virtual filesystem layer.
+//
+// Lock order:
+//
+// Filesystem implementation locks
+// VirtualFilesystem.mountMu
+// VirtualFilesystem.fsTypesMu
+package vfs
+
+import (
+ "sync"
+)
+
+// A VirtualFilesystem (VFS for short) combines Filesystems in trees of Mounts.
+//
+// There is no analogue to the VirtualFilesystem type in Linux, as the
+// equivalent state in Linux is global.
+type VirtualFilesystem struct {
+ // mountMu serializes mount mutations.
+ //
+ // mountMu is analogous to Linux's namespace_sem.
+ mountMu sync.RWMutex
+
+ // mounts maps (mount parent, mount point) pairs to mounts. (Since mounts
+ // are uniquely namespaced, including mount parent in the key correctly
+ // handles both bind mounts and mount namespaces; Linux does the same.)
+ // Synchronization between mutators and readers is provided by mounts.seq;
+ // synchronization between mutators is provided by mountMu.
+ //
+ // mounts is used to follow mount points during path traversal. We use a
+ // single table rather than per-Dentry tables to reduce size (and therefore
+ // cache footprint) for the vast majority of Dentries that are not mount
+ // points.
+ //
+ // mounts is analogous to Linux's mount_hashtable.
+ mounts mountTable
+
+ // mountpoints maps mount points to mounts at those points in all
+ // namespaces. mountpoints is protected by mountMu.
+ //
+ // mountpoints is used to find mounts that must be unmounted due to
+ // removal of a mount point Dentry from another mount namespace. ("A file
+ // or directory that is a mount point in one namespace that is not a mount
+ // point in another namespace, may be renamed, unlinked, or removed
+ // (rmdir(2)) in the mount namespace in which it is not a mount point
+ // (subject to the usual permission checks)." - mount_namespaces(7))
+ //
+ // mountpoints is analogous to Linux's mountpoint_hashtable.
+ mountpoints map[*Dentry]map[*Mount]struct{}
+
+ // fsTypes contains all FilesystemTypes that are usable in the
+ // VirtualFilesystem. fsTypes is protected by fsTypesMu.
+ fsTypesMu sync.RWMutex
+ fsTypes map[string]FilesystemType
+}
+
+// New returns a new VirtualFilesystem with no mounts or FilesystemTypes.
+func New() *VirtualFilesystem {
+ vfs := &VirtualFilesystem{
+ mountpoints: make(map[*Dentry]map[*Mount]struct{}),
+ fsTypes: make(map[string]FilesystemType),
+ }
+ vfs.mounts.Init()
+ return vfs
+}
+
+// A VirtualDentry represents a node in a VFS tree, by combining a Dentry
+// (which represents a node in a Filesystem's tree) and a Mount (which
+// represents the Filesystem's position in a VFS mount tree).
+//
+// VirtualDentry's semantics are similar to that of a Go interface object
+// representing a pointer: it is a copyable value type that represents
+// references to another entity. The zero value of VirtualDentry is an "empty
+// VirtualDentry", directly analogous to a nil interface object.
+// VirtualDentry.Ok() checks that a VirtualDentry is not zero-valued; unless
+// otherwise specified, all other VirtualDentry methods require
+// VirtualDentry.Ok() == true.
+//
+// Mounts and Dentries are reference-counted, requiring that users call
+// VirtualDentry.{Inc,Dec}Ref() as appropriate. We often colloquially refer to
+// references on the Mount and Dentry referred to by a VirtualDentry as
+// references on the VirtualDentry itself. Unless otherwise specified, all
+// VirtualDentry methods require that a reference is held on the VirtualDentry.
+//
+// VirtualDentry is analogous to Linux's struct path.
+type VirtualDentry struct {
+ mount *Mount
+ dentry *Dentry
+}
+
+// Ok returns true if vd is not empty. It does not require that a reference is
+// held.
+func (vd VirtualDentry) Ok() bool {
+ return vd.mount != nil
+}
+
+// IncRef increments the reference counts on the Mount and Dentry represented
+// by vd.
+func (vd VirtualDentry) IncRef() {
+ vd.mount.incRef()
+ vd.dentry.incRef(vd.mount.fs)
+}
+
+// DecRef decrements the reference counts on the Mount and Dentry represented
+// by vd.
+func (vd VirtualDentry) DecRef() {
+ vd.dentry.decRef(vd.mount.fs)
+ vd.mount.decRef()
+}
+
+// Mount returns the Mount associated with vd. It does not take a reference on
+// the returned Mount.
+func (vd VirtualDentry) Mount() *Mount {
+ return vd.mount
+}
+
+// Dentry returns the Dentry associated with vd. It does not take a reference
+// on the returned Dentry.
+func (vd VirtualDentry) Dentry() *Dentry {
+ return vd.dentry
+}