apply bitmap for fd_table

Apply bitmap in fd_table to record open file fd. It can
accelerate the speed of allocating or removing fd from
fdtable.

Signed-off-by: Howard Zhang <howard.zhang@arm.com>
This commit is contained in:
Howard Zhang
2021-07-13 14:16:07 +08:00
parent 79b7fb3348
commit c8d252466f
3 changed files with 118 additions and 108 deletions
+1
View File
@@ -220,6 +220,7 @@ go_library(
"//pkg/abi/linux",
"//pkg/abi/linux/errno",
"//pkg/amutex",
"//pkg/bitmap",
"//pkg/bits",
"//pkg/bpf",
"//pkg/cleanup",
+114 -100
View File
@@ -18,10 +18,10 @@ import (
"fmt"
"math"
"strings"
"sync/atomic"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bitmap"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/sentry/fs"
@@ -84,13 +84,8 @@ type FDTable struct {
// mu protects below.
mu sync.Mutex `state:"nosave"`
// next is start position to find fd.
next int32
// used contains the number of non-nil entries. It must be accessed
// atomically. It may be read atomically without holding mu (but not
// written).
used int32
// fdBitmap shows which fds are already in use.
fdBitmap bitmap.Bitmap `state:"nosave"`
// descriptorTable holds descriptors.
descriptorTable `state:".(map[int32]descriptor)"`
@@ -98,6 +93,8 @@ type FDTable struct {
func (f *FDTable) saveDescriptorTable() map[int32]descriptor {
m := make(map[int32]descriptor)
f.mu.Lock()
defer f.mu.Unlock()
f.forEach(context.Background(), func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
m[fd] = descriptor{
file: file,
@@ -111,12 +108,16 @@ func (f *FDTable) saveDescriptorTable() map[int32]descriptor {
func (f *FDTable) loadDescriptorTable(m map[int32]descriptor) {
ctx := context.Background()
f.initNoLeakCheck() // Initialize table.
f.used = 0
f.fdBitmap = bitmap.BitmapWithSize(uint32(math.MaxUint16))
for fd, d := range m {
if fd < 0 {
panic(fmt.Sprintf("FD is not supposed to be negative. FD: %d", fd))
}
if file, fileVFS2 := f.setAll(ctx, fd, d.file, d.fileVFS2, d.flags); file != nil || fileVFS2 != nil {
panic("VFS1 or VFS2 files set")
}
f.fdBitmap.Add(uint32(fd))
// Note that we do _not_ need to acquire a extra table reference here. The
// table reference will already be accounted for in the file, so we drop the
// reference taken by set above.
@@ -189,8 +190,10 @@ func (f *FDTable) DecRef(ctx context.Context) {
func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) {
// retries tracks the number of failed TryIncRef attempts for the same FD.
retries := 0
fd := int32(0)
for {
fds := f.fdBitmap.ToSlice()
// Iterate through the fdBitmap.
for _, ufd := range fds {
fd := int32(ufd)
file, fileVFS2, flags, ok := f.getAll(fd)
if !ok {
break
@@ -218,7 +221,6 @@ func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File,
fileVFS2.DecRef(ctx)
}
retries = 0
fd++
}
}
@@ -226,6 +228,8 @@ func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File,
func (f *FDTable) String() string {
var buf strings.Builder
ctx := context.Background()
f.mu.Lock()
defer f.mu.Unlock()
f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
switch {
case file != nil:
@@ -250,10 +254,10 @@ func (f *FDTable) String() string {
}
// NewFDs allocates new FDs guaranteed to be the lowest number available
// greater than or equal to the fd parameter. All files will share the set
// greater than or equal to the minFD parameter. All files will share the set
// flags. Success is guaranteed to be all or none.
func (f *FDTable) NewFDs(ctx context.Context, fd int32, files []*fs.File, flags FDFlags) (fds []int32, err error) {
if fd < 0 {
func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*fs.File, flags FDFlags) (fds []int32, err error) {
if minFD < 0 {
// Don't accept negative FDs.
return nil, unix.EINVAL
}
@@ -267,24 +271,49 @@ func (f *FDTable) NewFDs(ctx context.Context, fd int32, files []*fs.File, flags
if lim.Cur != limits.Infinity {
end = int32(lim.Cur)
}
if fd >= end {
if minFD >= end {
return nil, unix.EMFILE
}
}
f.mu.Lock()
// From f.next to find available fd.
if fd < f.next {
fd = f.next
// max is used as the largest number in fdBitmap + 1.
max := int32(0)
// flipFdBitmap is flip of fdBitmap which is used to find free fd in fdBitmap.
flipFdBitmap := f.fdBitmap.Clone()
if !f.fdBitmap.IsEmpty() {
max = int32(f.fdBitmap.Maximum())
max++
flipFdBitmap.FlipRange(uint32(0), uint32(max))
// Clear 0 to (minFD-1) bit in flipFdBitmap, thus it will not use fd that is less than minFD.
if minFD > 0 {
flipFdBitmap.ClearRange(uint32(0), uint32(minFD))
}
}
// Adjust max in case it is less than minFD.
if max < minFD {
max = minFD
}
// Install all entries.
for i := fd; i < end && len(fds) < len(files); i++ {
if d, _, _ := f.get(i); d == nil {
// Set the descriptor.
f.set(ctx, i, files[len(fds)], flags)
fds = append(fds, i) // Record the file descriptor.
for len(fds) < len(files) {
// Try to use free bit in fdBitmap.
// If all bits in fdBitmap are used, expand fd to the max.
if !flipFdBitmap.IsEmpty() {
fd := flipFdBitmap.Minimum()
f.fdBitmap.Add(fd)
flipFdBitmap.Remove(fd)
f.set(ctx, int32(fd), files[len(fds)], flags)
fds = append(fds, int32(fd))
} else if max < end {
f.fdBitmap.Add(uint32(max))
f.set(ctx, max, files[len(fds)], flags)
fds = append(fds, max)
max++
} else {
break
}
}
@@ -292,6 +321,7 @@ func (f *FDTable) NewFDs(ctx context.Context, fd int32, files []*fs.File, flags
if len(fds) < len(files) {
for _, i := range fds {
f.set(ctx, i, nil, FDFlags{})
f.fdBitmap.Remove(uint32(i))
}
f.mu.Unlock()
@@ -305,20 +335,15 @@ func (f *FDTable) NewFDs(ctx context.Context, fd int32, files []*fs.File, flags
return nil, unix.EMFILE
}
if fd == f.next {
// Update next search start position.
f.next = fds[len(fds)-1] + 1
}
f.mu.Unlock()
return fds, nil
}
// NewFDsVFS2 allocates new FDs guaranteed to be the lowest number available
// greater than or equal to the fd parameter. All files will share the set
// greater than or equal to the minFD parameter. All files will share the set
// flags. Success is guaranteed to be all or none.
func (f *FDTable) NewFDsVFS2(ctx context.Context, fd int32, files []*vfs.FileDescription, flags FDFlags) (fds []int32, err error) {
if fd < 0 {
func (f *FDTable) NewFDsVFS2(ctx context.Context, minFD int32, files []*vfs.FileDescription, flags FDFlags) (fds []int32, err error) {
if minFD < 0 {
// Don't accept negative FDs.
return nil, unix.EINVAL
}
@@ -332,31 +357,56 @@ func (f *FDTable) NewFDsVFS2(ctx context.Context, fd int32, files []*vfs.FileDes
if lim.Cur != limits.Infinity {
end = int32(lim.Cur)
}
if fd >= end {
if minFD >= end {
return nil, unix.EMFILE
}
}
f.mu.Lock()
// From f.next to find available fd.
if fd < f.next {
fd = f.next
}
// max is used as the largest number in fdBitmap + 1.
max := int32(0)
// flipFdBitmap is flip of fdBitmap which is used to find free fd in fdBitmap.
flipFdBitmap := f.fdBitmap.Clone()
// Install all entries.
for i := fd; i < end && len(fds) < len(files); i++ {
if d, _, _ := f.getVFS2(i); d == nil {
// Set the descriptor.
f.setVFS2(ctx, i, files[len(fds)], flags)
fds = append(fds, i) // Record the file descriptor.
if !f.fdBitmap.IsEmpty() {
max = int32(f.fdBitmap.Maximum())
max++
flipFdBitmap.FlipRange(uint32(0), uint32(max))
// Clear 0 to (minFD-1) bit in flipFdBitmap, thus it will not use fd that is less than minFD.
if minFD > 0 {
flipFdBitmap.ClearRange(uint32(0), uint32(minFD))
}
}
// Adjust max in case it is less than minFD.
if max < minFD {
max = minFD
}
for len(fds) < len(files) {
// Try to use free bit in fdBitmap.
// If all bits in fdBitmap are used, expand fd to the max.
if !flipFdBitmap.IsEmpty() {
fd := flipFdBitmap.Minimum()
f.fdBitmap.Add(fd)
flipFdBitmap.Remove(fd)
f.setVFS2(ctx, int32(fd), files[len(fds)], flags)
fds = append(fds, int32(fd))
} else if max < end {
f.fdBitmap.Add(uint32(max))
f.setVFS2(ctx, max, files[len(fds)], flags)
fds = append(fds, max)
max++
} else {
break
}
}
// Failure? Unwind existing FDs.
if len(fds) < len(files) {
for _, i := range fds {
f.setVFS2(ctx, i, nil, FDFlags{})
f.fdBitmap.Remove(uint32(i))
}
f.mu.Unlock()
@@ -370,57 +420,19 @@ func (f *FDTable) NewFDsVFS2(ctx context.Context, fd int32, files []*vfs.FileDes
return nil, unix.EMFILE
}
if fd == f.next {
// Update next search start position.
f.next = fds[len(fds)-1] + 1
}
f.mu.Unlock()
return fds, nil
}
// NewFDVFS2 allocates a file descriptor greater than or equal to minfd for
// NewFDVFS2 allocates a file descriptor greater than or equal to minFD for
// the given file description. If it succeeds, it takes a reference on file.
func (f *FDTable) NewFDVFS2(ctx context.Context, minfd int32, file *vfs.FileDescription, flags FDFlags) (int32, error) {
if minfd < 0 {
// Don't accept negative FDs.
return -1, unix.EINVAL
func (f *FDTable) NewFDVFS2(ctx context.Context, minFD int32, file *vfs.FileDescription, flags FDFlags) (int32, error) {
files := []*vfs.FileDescription{file}
fileSlice, error := f.NewFDsVFS2(ctx, minFD, files, flags)
if error != nil {
return -1, error
}
// Default limit.
end := int32(math.MaxInt32)
// Ensure we don't get past the provided limit.
if limitSet := limits.FromContext(ctx); limitSet != nil {
lim := limitSet.Get(limits.NumberOfFiles)
if lim.Cur != limits.Infinity {
end = int32(lim.Cur)
}
if minfd >= end {
return -1, unix.EMFILE
}
}
f.mu.Lock()
defer f.mu.Unlock()
// From f.next to find available fd.
fd := minfd
if fd < f.next {
fd = f.next
}
for fd < end {
if d, _, _ := f.getVFS2(fd); d == nil {
f.setVFS2(ctx, fd, file, flags)
if fd == f.next {
// Update next search start position.
f.next = fd + 1
}
return fd, nil
}
fd++
}
return -1, unix.EMFILE
return fileSlice[0], nil
}
// NewFDAt sets the file reference for the given FD. If there is an active
@@ -469,6 +481,11 @@ func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *fs.File, fileVFS2
defer f.mu.Unlock()
df, dfVFS2 := f.setAll(ctx, fd, file, fileVFS2, flags)
// Add fd to fdBitmap.
if file != nil || fileVFS2 != nil {
f.fdBitmap.Add(uint32(fd))
}
return df, dfVFS2, nil
}
@@ -573,7 +590,9 @@ func (f *FDTable) GetVFS2(fd int32) (*vfs.FileDescription, FDFlags) {
// Precondition: The caller must be running on the task goroutine, or Task.mu
// must be locked.
func (f *FDTable) GetFDs(ctx context.Context) []int32 {
fds := make([]int32, 0, int(atomic.LoadInt32(&f.used)))
f.mu.Lock()
defer f.mu.Unlock()
fds := make([]int32, 0, int(f.fdBitmap.GetNumOnes()))
f.forEach(ctx, func(fd int32, _ *fs.File, _ *vfs.FileDescription, _ FDFlags) {
fds = append(fds, fd)
})
@@ -583,13 +602,15 @@ func (f *FDTable) GetFDs(ctx context.Context) []int32 {
// Fork returns an independent FDTable.
func (f *FDTable) Fork(ctx context.Context) *FDTable {
clone := f.k.NewFDTable()
f.mu.Lock()
defer f.mu.Unlock()
f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
// The set function here will acquire an appropriate table
// reference for the clone. We don't need anything else.
if df, dfVFS2 := clone.setAll(ctx, fd, file, fileVFS2, flags); df != nil || dfVFS2 != nil {
panic("VFS1 or VFS2 files set")
}
clone.fdBitmap.Add(uint32(fd))
})
return clone
}
@@ -604,11 +625,6 @@ func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDesc
f.mu.Lock()
// Update current available position.
if fd < f.next {
f.next = fd
}
orig, orig2, _, _ := f.getAll(fd)
// Add reference for caller.
@@ -621,6 +637,7 @@ func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDesc
if orig != nil || orig2 != nil {
orig, orig2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry.
f.fdBitmap.Remove(uint32(fd))
}
f.mu.Unlock()
@@ -644,16 +661,13 @@ func (f *FDTable) RemoveIf(ctx context.Context, cond func(*fs.File, *vfs.FileDes
f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
if cond(file, fileVFS2, flags) {
df, dfVFS2 := f.setAll(ctx, fd, nil, nil, FDFlags{}) // Clear from table.
f.fdBitmap.Remove(uint32(fd))
if df != nil {
files = append(files, df)
}
if dfVFS2 != nil {
filesVFS2 = append(filesVFS2, dfVFS2)
}
// Update current available position.
if fd < f.next {
f.next = fd
}
}
})
f.mu.Unlock()
+3 -8
View File
@@ -15,9 +15,11 @@
package kernel
import (
"math"
"sync/atomic"
"unsafe"
"gvisor.dev/gvisor/pkg/bitmap"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -44,6 +46,7 @@ func (f *FDTable) initNoLeakCheck() {
func (f *FDTable) init() {
f.initNoLeakCheck()
f.InitRefs()
f.fdBitmap = bitmap.BitmapWithSize(uint32(math.MaxUint16))
}
// get gets a file entry.
@@ -162,14 +165,6 @@ func (f *FDTable) setAll(ctx context.Context, fd int32, file *fs.File, fileVFS2
}
}
// Adjust used.
switch {
case orig == nil && desc != nil:
atomic.AddInt32(&f.used, 1)
case orig != nil && desc == nil:
atomic.AddInt32(&f.used, -1)
}
if orig != nil {
switch {
case orig.file != nil: