mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user