mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
FDTable.descriptorTable is a slice of unsafe.Pointer-s and its maximum length is MaxInt32. It requires up to 16GB of memory. A process can use just a few descriptors but sets one or more of them to high numbers. In this case, FDTable.descriptorTable is extended to the maximum size. The problem here is that go-runtime zeros memory regions when they are reused. In the case of fdtable, the memory region is 16GB, so it is a time consuming operation. Second, it forces the kernel to allocate physical pages to the entire region. This change adds another level to descriptorTable, so the first level is a slice of buckets where each bucket is a slice of descriptors. The bucket size is fixed to 512 entries to fit one page. Before: BenchmarkFDLookupAndDecRef-12 50834290 23.70 ns/op BenchmarkCreateWithMaxFD-12 2 7194873988 ns/op BenchmarkFDLookupAndDecRefConcurrent-12 23775555 49.68 ns/op BenchmarkTableLookup-12 412888780 2.835 ns/op BenchmarkTableMapLookup-12 87944782 12.84 ns/op After: BenchmarkFDLookupAndDecRef-12 46229940 25.03 ns/op BenchmarkCreateWithMaxFD-12 13 82573899 ns/op BenchmarkFDLookupAndDecRefConcurrent-12 21889380 54.13 ns/op BenchmarkTableLookup-12 415851230 2.821 ns/op BenchmarkTableMapLookup-12 97236267 11.89 ns/op Reported-by: [email protected] PiperOrigin-RevId: 539138632
148 lines
4.0 KiB
Go
148 lines
4.0 KiB
Go
// Copyright 2018 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 (
|
|
"math"
|
|
|
|
"gvisor.dev/gvisor/pkg/bitmap"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
)
|
|
|
|
type descriptorBucket [fdsPerBucket]descriptorAtomicPtr
|
|
type descriptorBucketSlice []descriptorBucketAtomicPtr
|
|
|
|
// descriptorTable is a two level table. The first level is a slice of
|
|
// *descriptorBucket where each bucket is a slice of *descriptor.
|
|
//
|
|
// All objects are updated atomically.
|
|
type descriptorTable struct {
|
|
// Changes to the slice itself requiring holding FDTable.mu.
|
|
slice descriptorBucketSliceAtomicPtr `state:".(map[int32]*descriptor)"`
|
|
}
|
|
|
|
// initNoLeakCheck initializes the table without enabling leak checking.
|
|
//
|
|
// This is used when loading an FDTable after S/R, during which the ref count
|
|
// object itself will enable leak checking if necessary.
|
|
func (f *FDTable) initNoLeakCheck() {
|
|
var slice descriptorBucketSlice // Empty slice.
|
|
f.slice.Store(&slice)
|
|
}
|
|
|
|
// init initializes the table with leak checking.
|
|
func (f *FDTable) init() {
|
|
f.initNoLeakCheck()
|
|
f.InitRefs()
|
|
f.fdBitmap = bitmap.New(uint32(math.MaxUint16))
|
|
}
|
|
|
|
const (
|
|
// fdsPerBucketShift is chosen in such a way that the size of bucket is
|
|
// equal to one page.
|
|
fdsPerBucketShift = 9
|
|
fdsPerBucket = 1 << fdsPerBucketShift
|
|
fdsPerBucketMask = fdsPerBucket - 1
|
|
)
|
|
|
|
// get gets a file entry.
|
|
//
|
|
// The boolean indicates whether this was in range.
|
|
//
|
|
//go:nosplit
|
|
func (f *FDTable) get(fd int32) (*vfs.FileDescription, FDFlags, bool) {
|
|
slice := *f.slice.Load()
|
|
bucketN := fd >> fdsPerBucketShift
|
|
if bucketN >= int32(len(slice)) {
|
|
return nil, FDFlags{}, false
|
|
}
|
|
bucket := slice[bucketN].Load()
|
|
if bucket == nil {
|
|
return nil, FDFlags{}, false
|
|
}
|
|
d := bucket[fd&fdsPerBucketMask].Load()
|
|
if d == nil {
|
|
return nil, FDFlags{}, true
|
|
}
|
|
return d.file, d.flags, true
|
|
}
|
|
|
|
// CurrentMaxFDs returns the number of file descriptors that may be stored in f
|
|
// without reallocation.
|
|
func (f *FDTable) CurrentMaxFDs() int {
|
|
slice := *f.slice.Load()
|
|
return len(slice) * fdsPerBucket
|
|
}
|
|
|
|
// set sets the file description referred to by fd to file. If
|
|
// file is non-nil, it takes a reference on them. If setAll replaces
|
|
// an existing file description, it returns it with the FDTable's reference
|
|
// transferred to the caller, which must call f.drop on the returned
|
|
// file after unlocking f.mu.
|
|
//
|
|
// Precondition: mu must be held.
|
|
func (f *FDTable) set(fd int32, file *vfs.FileDescription, flags FDFlags) *vfs.FileDescription {
|
|
slicePtr := f.slice.Load()
|
|
|
|
bucketN := fd >> fdsPerBucketShift
|
|
// Grow the table as required.
|
|
if length := len(*slicePtr); int(bucketN) >= length {
|
|
newLen := int(bucketN) + 1
|
|
if newLen < 2*length {
|
|
// Ensure the table at least doubles in size without going over the limit.
|
|
newLen = 2 * length
|
|
if newLen > int(MaxFdLimit) {
|
|
newLen = int(MaxFdLimit)
|
|
}
|
|
}
|
|
newSlice := append(*slicePtr, make([]descriptorBucketAtomicPtr, newLen-length)...)
|
|
slicePtr = &newSlice
|
|
f.slice.Store(slicePtr)
|
|
}
|
|
|
|
slice := *slicePtr
|
|
|
|
bucket := slice[bucketN].Load()
|
|
if bucket == nil {
|
|
bucket = &descriptorBucket{}
|
|
slice[bucketN].Store(bucket)
|
|
}
|
|
|
|
var desc *descriptor
|
|
if file != nil {
|
|
desc = &descriptor{
|
|
file: file,
|
|
flags: flags,
|
|
}
|
|
}
|
|
|
|
// Update the single element.
|
|
orig := bucket[fd%fdsPerBucket].Swap(desc)
|
|
|
|
// Acquire a table reference.
|
|
if desc != nil && desc.file != nil {
|
|
if orig == nil || desc.file != orig.file {
|
|
desc.file.IncRef()
|
|
}
|
|
}
|
|
|
|
if orig != nil && orig.file != nil {
|
|
if desc == nil || desc.file != orig.file {
|
|
return orig.file
|
|
}
|
|
}
|
|
return nil
|
|
}
|