Files

586 lines
15 KiB
Go
Raw Permalink Normal View History

// Copyright 2018 The gVisor Authors.
2018-04-27 10:37:02 -07:00
//
// 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 pgalloc
2018-04-27 10:37:02 -07:00
import (
"testing"
2021-03-29 13:28:32 -07:00
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/memmap"
2018-04-27 10:37:02 -07:00
)
const (
2021-03-29 13:28:32 -07:00
page = hostarch.PageSize
hugepage = hostarch.HugePageSize
2018-04-27 10:37:02 -07:00
)
// existingSegment represents a range of pages in a test MemoryFile that is not
// void or free.
type existingSegment struct {
start uint64
end uint64
state int
}
// Possible values for existingSegment.state:
const (
existingUnspecified = iota
existingUsed
existingWaste
existingReleasing // or sub-releasing
)
func TestFindAllocatable(t *testing.T) {
2018-04-27 10:37:02 -07:00
for _, test := range []struct {
name string
// Initial state:
chunkHuge []bool
existing []existingSegment
// Allocation parameters:
length uint64
huge bool
recycle bool
dir Direction
// Expected outcome:
want uint64
2018-04-27 10:37:02 -07:00
}{
{
name: "initial small allocation, bottom-up",
length: page,
want: 0,
2018-04-27 10:37:02 -07:00
},
{
name: "initial small allocation, top-down",
length: page,
dir: TopDown,
want: chunkSize - page,
2021-11-10 17:10:18 -08:00
},
{
name: "initial small allocation, multiple pages, top-down",
length: 2 * page,
dir: TopDown,
want: chunkSize - 2*page,
2021-11-10 17:10:18 -08:00
},
{
name: "initial small allocation, recycling enabled, bottom-up",
length: page,
recycle: true,
want: 0,
2020-06-05 15:38:38 -07:00
},
{
name: "initial huge allocation, bottom-up",
length: hugepage,
huge: true,
want: 0,
2018-04-27 10:37:02 -07:00
},
{
name: "initial huge allocation, top-down",
length: hugepage,
huge: true,
dir: TopDown,
want: chunkSize - hugepage,
2021-11-10 17:10:18 -08:00
},
{
name: "initial huge allocation, multiple pages, top-down",
length: 2 * hugepage,
huge: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
2018-04-27 10:37:02 -07:00
},
{
name: "initial huge allocation, recycling enabled, bottom-up",
length: hugepage,
huge: true,
recycle: true,
want: 0,
2021-11-10 17:10:18 -08:00
},
{
name: "huge allocation uses huge pages in new chunk",
chunkHuge: []bool{false},
2021-11-10 17:10:18 -08:00
length: hugepage,
huge: true,
want: chunkSize,
2021-11-10 17:10:18 -08:00
},
{
name: "huge allocation uses huge pages in existing chunk",
chunkHuge: []bool{false, true},
2020-06-05 15:38:38 -07:00
length: hugepage,
huge: true,
want: chunkSize,
},
{
name: "hugepage-sized non-huge allocation uses small pages in new chunk",
chunkHuge: []bool{true},
2020-06-05 15:38:38 -07:00
length: hugepage,
want: chunkSize,
2020-06-05 15:38:38 -07:00
},
{
name: "hugepage-sized non-huge allocation uses small pages in existing chunk",
chunkHuge: []bool{true, false},
length: hugepage,
want: chunkSize,
},
2020-06-05 15:38:38 -07:00
{
name: "bottom-up small allocation begins at start of file",
chunkHuge: []bool{false},
existing: []existingSegment{
{page, 2 * page, existingUsed},
},
length: page,
want: 0,
},
{
name: "top-down small allocation begins at end of last chunk",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - 2*page, chunkSize - page, existingUsed},
2020-06-05 15:38:38 -07:00
},
length: page,
dir: TopDown,
want: chunkSize - page,
2020-06-05 15:38:38 -07:00
},
{
name: "bottom-up huge allocation begins at start of file",
chunkHuge: []bool{true},
existing: []existingSegment{
{hugepage, 2 * hugepage, existingUsed},
2020-06-05 15:38:38 -07:00
},
length: hugepage,
huge: true,
want: 0,
2020-06-05 15:38:38 -07:00
},
{
name: "top-down huge allocation begins at end of last chunk",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - 2*hugepage, chunkSize - hugepage, existingUsed},
},
length: hugepage,
huge: true,
dir: TopDown,
want: chunkSize - hugepage,
2020-06-05 15:38:38 -07:00
},
{
name: "bottom-up small allocation can extend multiple chunks",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize/2 - page, chunkSize / 2, existingUsed},
2020-06-05 15:38:38 -07:00
},
length: 2*chunkSize + page,
want: chunkSize / 2,
2020-06-05 15:38:38 -07:00
},
{
name: "top-down small allocation can extend multiple chunks",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize/2 - page, chunkSize / 2, existingUsed},
2020-06-05 15:38:38 -07:00
},
length: 2*chunkSize + page,
dir: TopDown,
want: chunkSize - page,
2018-04-27 10:37:02 -07:00
},
2021-11-10 17:10:18 -08:00
{
name: "bottom-up huge allocation can extend multiple chunks",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize/2 - hugepage, chunkSize / 2, existingUsed},
2021-11-10 17:10:18 -08:00
},
length: 2*chunkSize + hugepage,
huge: true,
want: chunkSize / 2,
2021-11-10 17:10:18 -08:00
},
{
name: "top-down huge allocation can extend multiple chunks",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize/2 - hugepage, chunkSize / 2, existingUsed},
2021-11-10 17:10:18 -08:00
},
length: 2*chunkSize + hugepage,
huge: true,
dir: TopDown,
want: chunkSize - hugepage,
},
{
name: "bottom-up small allocation finds first free gap",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingUsed},
{2 * page, 3 * page, existingUsed},
},
length: page,
want: page,
},
{
name: "top-down small allocation finds last free gap",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingUsed},
{chunkSize - 3*page, chunkSize - 2*page, existingUsed},
},
length: page,
dir: TopDown,
want: chunkSize - 2*page,
},
{
name: "bottom-up huge allocation finds first free gap",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingUsed},
{2 * hugepage, 3 * hugepage, existingUsed},
},
length: hugepage,
huge: true,
want: hugepage,
},
{
name: "top-down huge allocation finds last free gap",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingUsed},
{chunkSize - 3*hugepage, chunkSize - 2*hugepage, existingUsed},
},
length: hugepage,
huge: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
},
{
name: "bottom-up small allocation skips undersized free gap",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingUsed},
{2 * page, 3 * page, existingUsed},
},
length: 2 * page,
want: 3 * page,
},
{
name: "top-down small allocation skips undersized free gap",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingUsed},
{chunkSize - 3*page, chunkSize - 2*page, existingUsed},
},
length: 2 * page,
dir: TopDown,
want: chunkSize - 5*page,
},
{
name: "bottom-up huge allocation skips undersized free gap",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingUsed},
{2 * hugepage, 3 * hugepage, existingUsed},
},
length: 2 * hugepage,
huge: true,
want: 3 * hugepage,
},
{
name: "top-down huge allocation skips undersized free gap",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingUsed},
{chunkSize - 3*hugepage, chunkSize - 2*hugepage, existingUsed},
},
length: 2 * hugepage,
huge: true,
dir: TopDown,
want: chunkSize - 5*hugepage,
},
{
name: "recycling bottom-up small allocation skips used pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingUsed},
},
length: page,
recycle: true,
want: page,
},
{
name: "recycling top-down small allocation skips used pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingUsed},
},
length: page,
recycle: true,
dir: TopDown,
want: chunkSize - 2*page,
},
{
name: "recycling bottom-up huge allocation skips used pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingUsed},
},
length: hugepage,
huge: true,
recycle: true,
want: hugepage,
},
{
name: "recycling top-down huge allocation skips used pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingUsed},
},
length: hugepage,
huge: true,
recycle: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
},
{
name: "non-recycling bottom-up small allocation skips waste pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingWaste},
},
length: page,
want: page,
},
{
name: "non-recycling top-down small allocation skips waste pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingWaste},
},
length: page,
dir: TopDown,
want: chunkSize - 2*page,
},
{
name: "non-recycling bottom-up huge allocation skips waste pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingWaste},
},
length: hugepage,
huge: true,
want: hugepage,
},
{
name: "non-recycling top-down huge allocation skips waste pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingWaste},
},
length: hugepage,
huge: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
},
{
name: "recycling bottom-up small allocation recycles waste pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingWaste},
},
length: page,
recycle: true,
want: 0,
},
{
name: "recycling top-down small allocation recycles waste pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingWaste},
},
length: page,
recycle: true,
dir: TopDown,
want: chunkSize - page,
},
{
name: "recycling bottom-up huge allocation recycles waste pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingWaste},
},
length: hugepage,
huge: true,
recycle: true,
want: 0,
},
{
name: "recycling top-down huge allocation recycles waste pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingWaste},
},
length: hugepage,
huge: true,
recycle: true,
dir: TopDown,
want: chunkSize - hugepage,
},
{
name: "non-recycling bottom-up small allocation skips releasing pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingReleasing},
},
length: page,
want: page,
},
{
name: "non-recycling top-down small allocation skips releasing pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingReleasing},
},
length: page,
dir: TopDown,
want: chunkSize - 2*page,
},
{
name: "non-recycling bottom-up huge allocation skips releasing pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingReleasing},
},
length: hugepage,
huge: true,
want: hugepage,
},
{
name: "non-recycling top-down huge allocation skips releasing pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingReleasing},
},
length: hugepage,
huge: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
},
{
name: "recycling bottom-up small allocation skips releasing pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{0, page, existingReleasing},
},
length: page,
recycle: true,
want: page,
},
{
name: "recycling top-down small allocation skips releasing pages",
chunkHuge: []bool{false},
existing: []existingSegment{
{chunkSize - page, chunkSize, existingReleasing},
},
length: page,
recycle: true,
dir: TopDown,
want: chunkSize - 2*page,
},
{
name: "recycling bottom-up huge allocation skips releasing pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{0, hugepage, existingReleasing},
},
length: hugepage,
huge: true,
recycle: true,
want: hugepage,
},
{
name: "recycling top-down huge allocation skips releasing pages",
chunkHuge: []bool{true},
existing: []existingSegment{
{chunkSize - hugepage, chunkSize, existingReleasing},
},
length: hugepage,
huge: true,
recycle: true,
dir: TopDown,
want: chunkSize - 2*hugepage,
2021-11-10 17:10:18 -08:00
},
2018-04-27 10:37:02 -07:00
} {
t.Run(test.name, func(t *testing.T) {
// Build the fake MemoryFile.
f := &MemoryFile{
opts: MemoryFileOpts{
ExpectHugepages: true,
DisableMemoryAccounting: true,
},
2018-04-27 10:37:02 -07:00
}
f.initFields()
chunks := make([]chunkInfo, len(test.chunkHuge))
for i, huge := range test.chunkHuge {
chunks[i].huge = huge
chunkFR := memmap.FileRange{uint64(i) * chunkSize, uint64(i+1) * chunkSize}
if huge {
f.unfreeHuge.RemoveRange(chunkFR)
} else {
f.unfreeSmall.RemoveRange(chunkFR)
2021-11-10 17:10:18 -08:00
}
}
f.chunks.Store(&chunks)
for _, es := range test.existing {
f.forEachChunk(memmap.FileRange{es.start, es.end}, func(chunk *chunkInfo, chunkFR memmap.FileRange) bool {
unwaste, unfree := &f.unwasteSmall, &f.unfreeSmall
if chunk.huge {
unwaste, unfree = &f.unwasteHuge, &f.unfreeHuge
}
switch es.state {
case existingUsed:
unfree.InsertRange(chunkFR, unfreeInfo{refs: 1})
case existingWaste:
unfree.InsertRange(chunkFR, unfreeInfo{refs: 0})
unwaste.RemoveRange(chunkFR)
case existingReleasing:
unfree.InsertRange(chunkFR, unfreeInfo{refs: 0})
default:
t.Fatalf("existingSegment %+v has unknown state", es)
}
f.memAcct.InsertRange(chunkFR, memAcctInfo{
wasteOrReleasing: es.state != existingUsed,
})
return true
})
}
// Perform the test allocation.
alloc := allocState{
length: test.length,
opts: AllocOpts{
Huge: test.huge,
Dir: test.dir,
},
huge: test.huge,
}
if test.recycle {
alloc.opts.Mode = AllocateCallerIndirectCommit
alloc.willCommit = true
}
fr, err := f.findAllocatableAndMarkUsed(&alloc)
if err != nil {
t.Fatalf("findAllocatableAndMarkUsed(%+v): failed: %v, want: %#x\n%v", alloc, err, test.want, f)
}
if fr.Start != test.want {
t.Errorf("findAllocatableAndMarkUsed(%+v): got: start=%#x, want: %#x\n%v", alloc, fr.Start, test.want, f)
}
if wantEnd := test.want + test.length; fr.End != wantEnd {
t.Errorf("findAllocatableAndMarkUsed(%+v): got: end=%#x, want: %#x\n%v", alloc, fr.End, wantEnd, f)
2018-04-27 10:37:02 -07:00
}
})
}
}